Revamping Player animations
by Daniel Buckmaster · 02/27/2009 (6:51 am) · 14 comments
Right now, the Player class looks up a static table of animation data to pick animations for things like movement and jumping. This system works well, but I wanted something a little more flexible. My animation definition table was getting clogged up with a large number of static animations I had to keep track of, and I eventually decided that I wanted to be able to add multiple styles of the same animation for some characters (for example, two slightly different mantling animations, or a normal walk animation and a stealthy walk animation). In addition, I've implemented multiple stances and movement speeds, adding an additional level of complication. The current system would require that I add a definition for each variation of each animation I wanted. This was not acceptable, given that not all Players would require all variations of all these animations.
The solution is quite simple in concept - replace the static table of animation data with a dynamic table loaded from a script. In addition, there should be multiple tables for different characters' requirements. Then a Player datablock can link itself to one table to have that table define its animations.
The resulting table script looks a little like this:
These names were chosen to be compatible with the way the standard Player animations are named, but in actual use I'll be shortening then to a handful of characters each. You can see basically how the information is stored. The Player datablock then uses these values to fill up its own actionList, just as it does with the static table. I have plans to replace the actionList with something more lightweight, to reduce data duplication - we don't need to store ground transforms in the actionList if they're already in our definition table!
Now that the static list is gone, animations must be set differently than they are currently. Right now, we rely on setting an action thread by looking up the action we want (we just say 'setActionThread(PlayerData::Jump...)). Now there is no easy way to index specific animations, since they've been user-defined. Instead, I added a search function to the Player class. Now when you want an animation, you fill information into a little 'search status' struct within the Player object, fire off the search, and it returns the index of the animation that closest matches the search criteria. Here's a made-up example of that:
A word about the 'identifier' parameter. I implemented this to allow user-defined ways of categorising animations. Taking my stealthy walking versus normal walking example, we might define normal walking animations as having an identifier of 0, and stealthy animations as an identifier of 1. Then when we want to move stealthily, we just set the identifier search parameter to 1, and stealthy animations are returned in preference to normal ones.
This behaviour could be something different for each animation data table, or even for each type of action (for example, movement-action identifiers might signify stealthy/normal, whereas jump-action identifiers might signify healthy versus wounded jumping).
Users can set identifiers for different action types per-Player via script, so the engine doesn't have to know anything about the situations where you might prefer one identifier over another.
One piece of data not used in the search, but defined in the script, is velocityHandling. This tells the engine how you want to deal with the Player's velocity when playing that animation. The most common setting of this enumerated value is Scale, which means that the animation will scale with the character's movement speed, like movement animations do currently. Other settings include the ability to have the animation's ground transform set the character's velocity, like death animations do currently. I plan to utilise this for movement types such as rolling, where the character must complete an entire roll, and be moving all the while, instead of just having them roll while they hold down the movement button.
I haven't put any pictures in this blog, mainly because they would be quite boring. This animation system looks exactly like Torque's default system - I'm proud to say. The development path was a little crazy: I implemented my new dynamic table alongside the static table, then when I was convinced it was running the way I wanted it to, I ripped out the static table, compiled, and set about fixing the errors it threw me. Probably not the best method, but it worked in the end.
Question: is there interest in a resource for this? It's a useful feature for some people, but I'll only invest time in cleaning up the code and writing a resource if enough people want it.
The solution is quite simple in concept - replace the static table of animation data with a dynamic table loaded from a script. In addition, there should be multiple tables for different characters' requirements. Then a Player datablock can link itself to one table to have that table define its animations.
The resulting table script looks a little like this:
datablock ActorAnimationData(HumanAnimationData)
{
name[0] = "standForward";
actionType[0] = Move;
stance[0] = Stand;
moveSpeed[0] = Walk;
velocityHandling[0] = Scale;
groundTransform[0] = "0.0 1.0 0.0";
identifier[0] = 0;
name[1] = "standBack";
actionType[1] = Move;
stance[1] = Stand;
moveSpeed[1] = Walk;
velocityHandling[1] = Scale;
groundTransform[1] = "0.0 -1.0 0.0";
identifier[1] = 0;
name[2] = "standSideLeft";
actionType[2] = Move;
stance[2] = Stand;
moveSpeed[2] = Walk;
velocityHandling[2] = Scale;
groundTransform[2] = "-1.0 0.0 0.0";
identifier[2] = 0;
name[3] = "standSideRight";
actionType[3] = Move;
stance[3] = Stand;
moveSpeed[3] = Walk;
velocityHandling[3] = Scale;
groundTransform[3] = "1.0 0.0 0.0";
identifier[3] = 0;
name[4] = "standIdle";
actionType[4] = Root;
stance[4] = Stand;
name[5] = "jump";
actionType[5] = Jump;
stance[5] = Stand;
velocityHandling[5] = None;
name[6] = "mantle";
actionType[6] = Mantle;
stance[6] = Stand;
velocityHandling[6] = AtLeast;
moveSpeed[6] = Run;
groundTransform[6] = "0 1 0";
};These names were chosen to be compatible with the way the standard Player animations are named, but in actual use I'll be shortening then to a handful of characters each. You can see basically how the information is stored. The Player datablock then uses these values to fill up its own actionList, just as it does with the static table. I have plans to replace the actionList with something more lightweight, to reduce data duplication - we don't need to store ground transforms in the actionList if they're already in our definition table!
Now that the static list is gone, animations must be set differently than they are currently. Right now, we rely on setting an action thread by looking up the action we want (we just say 'setActionThread(PlayerData::Jump...)). Now there is no easy way to index specific animations, since they've been user-defined. Instead, I added a search function to the Player class. Now when you want an animation, you fill information into a little 'search status' struct within the Player object, fire off the search, and it returns the index of the animation that closest matches the search criteria. Here's a made-up example of that:
mAnimSearchState.dir = currentLocalVelocity; mAnimSearchState.maxDirAngleDiff = M_PI_F / 2; mAnimSearchState.stance = Stand; mAnimSearchState.speed = Run; mAnimSearchState.identifier = 0; U32 action = findActionAnimationID(PlayerAnimationData::Move); mAnimSearchState.cleanState();The values you fill in define the parameters of the search, and the argument of the search method (in this case, it's Move) tells the search what type of animation you're looking for. In this case, we're looking for a Move animation in the Stand stance and Run speed whose ground transform direction matches our current movement direction. (We will accept any animation with a direction within pi/2 radians of our current direction.)
A word about the 'identifier' parameter. I implemented this to allow user-defined ways of categorising animations. Taking my stealthy walking versus normal walking example, we might define normal walking animations as having an identifier of 0, and stealthy animations as an identifier of 1. Then when we want to move stealthily, we just set the identifier search parameter to 1, and stealthy animations are returned in preference to normal ones.
This behaviour could be something different for each animation data table, or even for each type of action (for example, movement-action identifiers might signify stealthy/normal, whereas jump-action identifiers might signify healthy versus wounded jumping).
Users can set identifiers for different action types per-Player via script, so the engine doesn't have to know anything about the situations where you might prefer one identifier over another.
One piece of data not used in the search, but defined in the script, is velocityHandling. This tells the engine how you want to deal with the Player's velocity when playing that animation. The most common setting of this enumerated value is Scale, which means that the animation will scale with the character's movement speed, like movement animations do currently. Other settings include the ability to have the animation's ground transform set the character's velocity, like death animations do currently. I plan to utilise this for movement types such as rolling, where the character must complete an entire roll, and be moving all the while, instead of just having them roll while they hold down the movement button.
I haven't put any pictures in this blog, mainly because they would be quite boring. This animation system looks exactly like Torque's default system - I'm proud to say. The development path was a little crazy: I implemented my new dynamic table alongside the static table, then when I was convinced it was running the way I wanted it to, I ripped out the static table, compiled, and set about fixing the errors it threw me. Probably not the best method, but it worked in the end.
Question: is there interest in a resource for this? It's a useful feature for some people, but I'll only invest time in cleaning up the code and writing a resource if enough people want it.
About the author
Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!
#2
If this works without any issues...
Absolutely Daniel, actually I've been meaning to try and figure something like this out myself. I've been learning character modeling and thought it would suck just to have the default animations. Your methods sound great and seem to take what I wanted one step further and keep every thing neat and tidy. So yes please by all means create this as a resource.
02/27/2009 (7:35 am)
I've only skimmed through your blog so far, but probably going to bookmark this. With that being said...If this works without any issues...
Absolutely Daniel, actually I've been meaning to try and figure something like this out myself. I've been learning character modeling and thought it would suck just to have the default animations. Your methods sound great and seem to take what I wanted one step further and keep every thing neat and tidy. So yes please by all means create this as a resource.
#3
02/27/2009 (8:41 am)
This is nice. I hope they put this sort of thing into T3D.
#4
there sure is Daniel, I would even like to pay for it as it fits perfectly with what I'm aiming for as well in our app.
02/27/2009 (8:49 am)
Quote:is there interest in a resource for this?
there sure is Daniel, I would even like to pay for it as it fits perfectly with what I'm aiming for as well in our app.
#5
02/27/2009 (9:32 am)
I've had constant trouble with this. I'd definitely like to see a resource or a pack!
#6
02/27/2009 (9:39 am)
I like the idea, I'd love to have this resourced
#7
02/27/2009 (10:44 am)
Resource, yes please :)
#8
02/27/2009 (11:05 am)
I'll get into some specific responses later, but for now I'll say that there won't be any question of a paid-for pack - we have free resources here for a reason, and I support that ;).
#9
02/27/2009 (11:21 am)
yes yes resource! this would be awesome!
#10
02/27/2009 (2:32 pm)
This would be a fantastic resource, it would be incredibly useful to me.
#11
It would be possible to put the action table into an object rather than a datablock, and update it in real-time. However, this would take a lot of effort and more modifications to the Player class, so it's a feature I won't be implementing.
One idea, though, might be to have different animation datablocs for characters with differing abilities, and to switch between these by reloading the player datablock.
Scott: Well, you can have more than the default animations in stock TGE, but the engine won't recognise them and play them automatically for things like movement.
Jaimi: I'm hoping there'll be lots of modifications to the Player class for T3D... it's pretty archaic ;P. But for now, I'm working on it...
Everyone: Okay, my question has definitely been answered! I don't have a timetable for this - there's still a few features I have to implement myself. A futher question: since my code contains a lot of stuff specific to my engine (i.e., stances and movement speeds), how much of that should be included in the resource? I was thinking I'd strip the actual code down to the very minimum, but provide some good examples of how to extend the code to accomodate whatever features your game requies.
The alternative, of course, is just resourcefying my entire build, which I plan to do at some point anyway :P.
I'm making progress on fully integrating the new system - I've just done away with the Player's death struct, since the new animation velocity handling precludes it. Now I just need to fix that velocity handling logic :P.
02/28/2009 (11:21 pm)
Johnny: Maybe my description was a little misleading, but the system isn't truly 'dynamic' in real-time. It is script-defined and loaded when the engine runs like other datablocks. I just described it as dynamic to differentiate it from the static list that is compiled into the engine.It would be possible to put the action table into an object rather than a datablock, and update it in real-time. However, this would take a lot of effort and more modifications to the Player class, so it's a feature I won't be implementing.
One idea, though, might be to have different animation datablocs for characters with differing abilities, and to switch between these by reloading the player datablock.
Scott: Well, you can have more than the default animations in stock TGE, but the engine won't recognise them and play them automatically for things like movement.
Jaimi: I'm hoping there'll be lots of modifications to the Player class for T3D... it's pretty archaic ;P. But for now, I'm working on it...
Everyone: Okay, my question has definitely been answered! I don't have a timetable for this - there's still a few features I have to implement myself. A futher question: since my code contains a lot of stuff specific to my engine (i.e., stances and movement speeds), how much of that should be included in the resource? I was thinking I'd strip the actual code down to the very minimum, but provide some good examples of how to extend the code to accomodate whatever features your game requies.
The alternative, of course, is just resourcefying my entire build, which I plan to do at some point anyway :P.
I'm making progress on fully integrating the new system - I've just done away with the Player's death struct, since the new animation velocity handling precludes it. Now I just need to fix that velocity handling logic :P.
#12
03/01/2009 (6:15 am)
This sounds like an excellent resource. I'm especially interested in the ground transform to velocity feature, as I've been wrestling with this myself the last few days. One question, though. Since you're removing the Death struct, does your system still handle aligning the corpse to the ground?
#13
03/01/2009 (8:57 am)
Ooh, that's a good point. I'd already implemented aligning the character to the ground with another modification I had made, so I didn't need that feature. Well, it's easily resolved - the death struct can be left in with no harm done, and it can provide its conform-to-ground features. There's just a bit from updatePos that needs to be removed, so that you don't end up adding death velocity twice.
#14
03/01/2009 (8:30 pm)
Couple days late on this but I will throw my vote in as well. I think this would make an excellent resource that many people could and would make very good use of. :) 
Torque Owner Johnny Hill