• Advertisement
  • entries
  • comments
  • views

It Lives!

Sign in to follow this  



Its been a busy week for both me and my game. In real life news, I have finally managed to find myself a nice flat in a very nice part of our town, for a reasonable rent and will be back to having my own space again in two weeks, after a few years of living in shared accommodation. You take things like sitting on a sofa and watching TV for granted until you don't have them, and I can't wait.

So back to the game. I have reimplemented my animation controller stuff and started building the state machine for the main character, so we are no longer guiding a capsule shape around the level, but now a fully skinned, animation-blended character, who's codename is Orc.

Obligatory poor quality CamStudio footage:

For my more recent readers, a catch up required here. A few years ago I started work on my own 3D modelling package, designed to be to 3DSMax and Blender what Notepad is to MS Word smile.png.

That said, it has become fairly feature-rich over the years and supports modelling, animation and so on in a pretty user-friendly way, with some features I always felt were missing from Milkshape, mainly related to mirroring symmetrical models.

I'll have to post up some more information on this package (Charm, as in CHARacter Modeller) in the future. Would be nice if other people wanted to try it.


So Orc was built for my previous iteration of this game and I had already designed a few animations - Idle, Run and Fall are all I require for this game at the moment.

I use a standard vertex shader approach to pose the model which I find very powerful as the model can live entirely on the GPU and only the array (palette) of matrices need to be uploaded each frame.

CPU-side, we have a collection of classes to support this. [font='courier new']Skeleton[/font] holds the bone information and takes care of generating the matrix palette from a [font='courier new']KeyFrame[/font]. [font='courier new']KeyFrames [/font]are returned by an [font='courier new']Animation[/font] class which holds the actual animation details exported from Charm. Finally there is an [font='courier new']AnimationController[/font] which maintains a separate track for each supported [font='courier new']Animation[/font] and looks after blending the animations together.

The [font='courier new']AnimationController[/font] can blend as many tracks together at any point so you have a smooth transition from one state to another, with a "fire and forget" approach of changing state, since it doesn't matter if the controller is already in a transition when you set a new state - the three states just blend together and move towards the latest one set.

All of this is managed by a state machine for the player character. Each [font='courier new']PcState [/font]implements an abstract interface that the [font='courier new']PcStateMachine [/font]manages the states through, for example [font='courier new']begin()[/font], [font='courier new']update(float delta) [/font]and so on. Each state can also signal the state machine to change to another state, and common operations are bundled into protected methods defined in [font='courier new']PcState [/font]so you have a bit of replicated code in each concrete state, but this makes each state very standalone and not dependant on the others.

One detail to note here is that when you start to run, you have to take at least one complete step before Orc stops again, even if you only tap the key, since otherwise it looks very bad. The way I tackled this is to take advantage of the events system that Charm supports. If you look at the screenshot of Charm above, you can see on the time line for the animation, the diamonds represent animation events. These are placed at specific points on the time line and can have string data associated with them if required.

In game, when the animation passes one of these points, the [font='courier new']AnimationController [/font]emits an [font='courier new']Event[/font] signal (via my varadic templates signals/slots system). The [font='courier new']PcStateMachine [/font]passes this signal on to the current state.

In the case of running, the RunState only checks to see if it can return to IdleState (i.e. stopped) during the firing of the animation event. This means that this check is actually controlled by the artist in the animation sequence rather than anything hardcoded - in the case of running, the animation events are placed at the point that legs are together with one on the ground through the animation, which is the correct place to stop the run if no keys are pressed.

The code for this (out of context but might make sense) looks like this:[code=:0]void RunState::update(FrameParams ¶ms, Physics &physics, float delta){ if(data.controls.anyMovementDown()) { v = inputVector(params); } data.kcc.setWalkScale(a.update(delta)); data.kcc.move(debugFly(v), physics, delta); updateRotation(v); if(!data.kcc.grounded()) { stateMachine.setNextState(PcState::Fall); }}void RunState::animationEvent(const AnimationEventData &event){ if(!data.controls.anyMovementDown()) { stateMachine.setNextState(PcState::Idle); }}
So if you tap the key, Orc will take one complete step forward. And if I change the run animation, I define the correct points as part of the animation in Charm rather than it being defined in the program, which I quite like. The run animation only emits events for this case at the moment so no need to examine the data, but any text added in Charm can be retrieved from the [font='courier new']AnimationEventData [/font]in the call above.

You can use these for lots of synchronizing of actions and animations. For example, if doing a downward bob before a jump, you can trigger the actual jump by an event at the end of the bob animation and so on. Its good to push this synchronization out to the same process that defines the animations and its pretty flexible.

So that's probably enough ramblings for now. Hope you enjoyed and thanks for reading.
Sign in to follow this  


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement