Animation Systems
by Ken Johnston · in Torque 3D Professional · 08/06/2010 (9:24 pm) · 10 replies
So I have been spending what little free time I have digging into torques animation systems, and I am finding that it is quite easy to get lost. While the new API docs are great for figuring out the "plug and play" systems however, I am interested in understanding the "under the hood" functionality.
The primary question that I have been mulling over and getting a bit lost in is where/how are the different animation/movement actions stored and declared?
If i wanted go about extending the player class to support more then move, crouch swim and jet, whats the best way to go about it, and more specifically what action and classes are associated with them. Going through player.cpp and player.h i see where to add additional animations to the enumerators and such but lets say for hypothetically I wanted to add a player who had tongue control (like a frog) in addition to arm leg and head, whats the best way of going about extending the class to for the new actions?
Can any give an indepth explanation of move->trigger and its subsequent relationship to mvtrigger[i] and were these statements are declared/exposed within the engine, and how they are intended to be used?
The primary question that I have been mulling over and getting a bit lost in is where/how are the different animation/movement actions stored and declared?
If i wanted go about extending the player class to support more then move, crouch swim and jet, whats the best way to go about it, and more specifically what action and classes are associated with them. Going through player.cpp and player.h i see where to add additional animations to the enumerators and such but lets say for hypothetically I wanted to add a player who had tongue control (like a frog) in addition to arm leg and head, whats the best way of going about extending the class to for the new actions?
Can any give an indepth explanation of move->trigger and its subsequent relationship to mvtrigger[i] and were these statements are declared/exposed within the engine, and how they are intended to be used?
#2
08/16/2010 (9:13 pm)
Thanks for the help on the move manager Tim....Still confused on how to set up move triggers though
#3
08/17/2010 (2:49 am)
Its actually within the code itself. Look at the swim animation which is already built it btw, to see how it works. Basically it does checks like in water to determine swim, speed to determine walk/run, etc etc.
#4
The first thing to do is to fix up the enum in player.h to suit your needs. Keep in mind the animation table is divided into three parts - MoveActions, TableActions, and everything else. MoveActions are basically picked based on the player's current direction of movement and velocity. The Table Actions are actions that are still a part of default movement, and include such things as jump, fall, fall land, etc. And the remaining action animations are for whatever you like - your frog tongue animation could be a one of these, and triggered from script. Here is what our currently looks like:
Notice we've done away with crouch, prone, and swimming, because our game doesn't support those actions. But we've added armed and mounted variations of the action animations.
Continued..
08/18/2010 (3:10 pm)
Hey Ken.. We've done quite a bit of modification to the animation system, and while this may not answer all of your questions, hopefully it can at least point you in the right direction. It sounds like what you mostly want to do is to extend the existing animation, and if that's the case, then it's not too bad.The first thing to do is to fix up the enum in player.h to suit your needs. Keep in mind the animation table is divided into three parts - MoveActions, TableActions, and everything else. MoveActions are basically picked based on the player's current direction of movement and velocity. The Table Actions are actions that are still a part of default movement, and include such things as jump, fall, fall land, etc. And the remaining action animations are for whatever you like - your frog tongue animation could be a one of these, and triggered from script. Here is what our currently looks like:
enum {
// *** WARNING ***
// These enum values are used to index the ActionAnimationList
// array instantiated in player.cc
// The first several are selected in the move state based on velocity
RootAnim,
RunForwardAnim,
BackBackwardAnim,
SideLeftAnim,
// WS START
// Crouch, Swim, & Prone animations are out. We don't
// use them.
/*
CrouchRootAnim,
CrouchForwardAnim,
ProneRootAnim,
ProneForwardAnim,
SwimRootAnim,
SwimForwardAnim,
SwimBackwardAnim,
SwimLeftAnim,
SwimRightAnim,
*/
// Added is the SideRightAnim, and mounted and armed animation variations.
SideRightAnim,
// WS END
// These are set explicitly based on player actions
FallAnim,
JumpAnim,
StandJumpAnim,
LandAnim,
// WS START - and WalkAnim
//JetAnim,
WalkAnim,
// WS - Additionally, we add all of the armed & mounted variations..
RootArmedAnim,
RunForwardArmedAnim,
BackBackwardArmedAnim,
SideLeftArmedAnim,
SideRightArmedAnim,
FallArmedAnim,
JumpArmedAnim,
StandJumpArmedAnim,
LandArmedAnim,
WalkArmedAnim,
// And now the Mounted list
RootMountedAnim,
RunForwardMountedAnim,
BackBackwardMountedAnim,
SideLeftMountedAnim,
SideRightMountedAnim,
FallMountedAnim,
JumpMountedAnim,
StandJumpMountedAnim,
LandMountedAnim,
WalkMountedAnim,
//NumMoveActionAnims = SideLeftAnim + 1,
//NumTableActionAnims = JetAnim + 1,
NumMoveActionAnims = SideRightAnim + 1,
NumTableActionAnims = WalkMountedAnim + 1,
ArmedAnimOffset = RootArmedAnim,
WalkAnimOffset = WalkAnim - RunForwardAnim,
MountedAnimOffset = RootMountedAnim,
// WS END
NumExtraActionAnims = 512 - NumTableActionAnims,
NumActionAnims = NumTableActionAnims + NumExtraActionAnims,
ActionAnimBits = 9,
NullAnimation = (1 << ActionAnimBits) - 1
}; Notice we've done away with crouch, prone, and swimming, because our game doesn't support those actions. But we've added armed and mounted variations of the action animations.
Continued..
#5
The next major place to go to is the Player::pickActionAnimation function. This is where the logic resides to select the appropriate animations from the action table, based on what the player is currently doing. You'll want to add and/or modifiy the logic here to support your version of the action table. In our case, again we removed support for crouch, prone, and swimming, and added support for armed vs. unarmed, and mounted. Here's what our pickActionAnimation looks like:
Continued..
08/18/2010 (3:23 pm)
[Updating the Animation system continued..]The next major place to go to is the Player::pickActionAnimation function. This is where the logic resides to select the appropriate animations from the action table, based on what the player is currently doing. You'll want to add and/or modifiy the logic here to support your version of the action table. In our case, again we removed support for crouch, prone, and swimming, and added support for armed vs. unarmed, and mounted. Here's what our pickActionAnimation looks like:
void Player::pickActionAnimation()
{
// Only select animations in our normal move state.
if (mState != MoveState || mDamageState != Enabled)
return;
// WS START - Remove the Mounted restriction
/*
if (isMounted())
{
// Go into root position unless something was set explicitly
// from a script.
if (mActionAnimation.action != PlayerData::RootAnim &&
mActionAnimation.action < PlayerData::NumTableActionAnims)
setActionThread(PlayerData::RootAnim,true,false,false);
return;
}
*/
// WS END
bool forward = true;
U32 action = PlayerData::RootAnim;
bool fsp = false;
// WS START - Removed the jetting stuff. Might eventually
// return it, but for now we have jet bikes!
/*
// Jetting overrides the fall animation condition
if (mJetting)
{
// Play the jetting animation
action = PlayerData::JetAnim;
}
else
*/
// WS END
if (mFalling)
{
// Not in contact with any surface and falling
action = PlayerData::FallAnim;
}
// WS START - Thar be no swimming in our game!
/*
else if ( mSwimming )
{
action = PlayerData::SwimRootAnim;
F32 curMax = 0.1f;
VectorF vel;
mWorldToObj.mulV(mVelocity,&vel);
for (U32 i = PlayerData::SwimForwardAnim; i <= PlayerData::SwimRightAnim; i++)
{
PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
if (anim.sequence != -1 && anim.speed)
{
// Bias towards picking the forward/backward anims over
// the side to prevent oscillation between anims. This seems to work
// ok but the real fix is to have 8 way directional animations.
VectorF biasedDir = anim.dir * VectorF(0.5f,1.0f,0.5f);
F32 d = mDot(vel, biasedDir);
if (d > curMax)
{
curMax = d;
action = i;
forward = true;
}
}
}
}
*/
// else if ( mPose == StandPose )
// and we care not what pose we're in!
else /* if ( mPose == StandPose ) */
{
// WS START - allow us to pick an animation if we're mounted..
if (mContactTimer >= sContactTickTime && !isMounted()) {
// WS END
// Nothing under our feet
action = PlayerData::RootAnim;
}
else
{
// Our feet are on something
// Pick animation that is the best fit for our current velocity.
// Assumes that root is the first animation in the list.
F32 curMax = 0.1f;
VectorF vel;
mWorldToObj.mulV(mVelocity,&vel);
for (U32 i = 1; i < PlayerData::NumMoveActionAnims; i++)
{
PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
if (anim.sequence != -1 && anim.speed)
{
// We bias towards picking the forward/backward anims over
// the side to prevent oscillation between anims. This seems
// to work ok but the real fix is to have 8 way directional
// animations.
VectorF biasedDir = anim.dir * VectorF(0.5f,1.0f,0.5f);
F32 d = mDot(vel, biasedDir);
if (d > curMax)
{
curMax = d;
action = i;
forward = true;
}
// WS START - We don't do this anymore because we support
// separate right and left side anims..
/*
else
{
// Special case, re-use slide left animation to slide right
if (i == PlayerData::SideLeftAnim && -d > curMax)
{
curMax = -d;
action = i;
forward = false;
}
}
*/
// WS END
}
}
}
}Continued..
#6
There are other places that you will need to fix up as well, depending on how far you deviate from the standard action vs. extra animation paradigm that Torque uses. But those are the main spots. Hope that helps!
Dusty Monk
08/18/2010 (3:24 pm)
[continued pickactionanimation code..]// WS START - And we don't support ye stinking crouching & prone shite either!
/*
else if ( mPose == CrouchPose )
{
VectorF vel;
mWorldToObj.mulV(mVelocity,&vel);
action = PlayerData::CrouchRootAnim;
fsp = true;
if ( vel.y > 0.1f )
{
action = PlayerData::CrouchForwardAnim;
forward = true;
}
else if ( vel.y < -0.1f )
{
action = PlayerData::CrouchForwardAnim;
forward = false;
}
}
else if ( mPose == PronePose )
{
VectorF vel;
mWorldToObj.mulV(mVelocity,&vel);
action = PlayerData::ProneRootAnim;
fsp = true;
if ( vel.y > 0.1f )
{
action = PlayerData::ProneForwardAnim;
forward = true;
}
else if ( vel.y < -0.1f )
{
action = PlayerData::ProneForwardAnim;
forward = false;
}
}
*/
// Instead, we offset appropriately if we're armed or mounted!
// WS - 4/21/07 -Okay we've picked a "basic" animation. However, if we have a weapon equipped,
// then increase the index into the "armed" variations.
if (action < PlayerData::ArmedAnimOffset)
{
// If we're moving forward and walking, then offset into the walk anim.
if (action == PlayerData::RunForwardAnim && mWalking)
action += PlayerData::WalkAnimOffset;
if (isMounted())
action += PlayerData::MountedAnimOffset;
else if (isArmed())
{
action += PlayerData::ArmedAnimOffset;
}
}
// WS END
setActionThread(action,forward,false,false,fsp);
}There are other places that you will need to fix up as well, depending on how far you deviate from the standard action vs. extra animation paradigm that Torque uses. But those are the main spots. Hope that helps!
Dusty Monk
#7
08/18/2010 (4:25 pm)
Awesome Dusty, your explanation and code examples are a real help and gets me to 90 percent of what i am trying to understand. I guess my next question lies in the move->trigger[i] code when triggering the animation in the code via script. What is the relationship between the index? if i set an animation to mvtriggercount3 in the default binds what am i really doing under the hood. At first i was assuming it was the related to the animation table index, but i am now i am not too sure, I know i can trigger some of my custom animations with a button linked to mvtriggercount3 by replacing the pose change animations and it works, i am just not too sure on why it works and what is really happening.
#8
Now the engine assigns some uses to those move triggers, but it's pretty easy to rejigger them to do whatever you want. Here's how they work right now.
Right now the engine reserves room for up to 6 move triggers. I believe Torque is only using 5 of these at current, but I may be mistaken.
Trigger[0] and Trigger[1] are by default bound to left and right mouse click, in default.bind.cs as such:
These triggers are by default fed into the shapeBaseImage state machine, and if you make use of shapeBaseImages states to handle firing a gun, then those activate the appropriate states to fire said gun. You can find the code that feeds those trigger's to the states in Player::UpdateMove, along with some other locations (like in vehicle):
Trigger[2] is typically used for jumping. It's bound to the space bar in default.bind.cs. If you look at the bottom of Player::updateMove, you can see where they look at trigger[2], and activate the jump..
and later on they set the jump animation..
Triggers 3 & 4, more recent additions, are associated with the crouch pose and the prone pose, respectively. I believe crouch is by default mapped to 'x' in default.bind.cs, I don't know what prone is mapped to. The way those trigger's work is simply that at the bottom of Player::udpateMove, they detect for the presence of those triggers, and set the player's pose appropriately..
Later on that same update, in Player::pickActionAnimation (the code I listed previously), it will detect the appropriate pose, and then set the action animation from the table that's associated with that pose.
So if you want your animation to be used as one of the action animations, such as running, walking, backing up, etc., then you need to fix up the enum and Player::pickActionAnimation as I described above.
If you want to your animation to play based on a specific key press, such as 'space' to jump, then you should feel free to hijack the movement trigger system to do whatever you want. I wouldn't necessarily *just* substitute 'prone root' animation for 'my arbitrary' animation and rely on the existing code to do the right thing. But I would certainly recommend something along the lines of adding a new member variable to player, adding code in update move to detect the presence of whichever trigger you decide to use, and then update player::PickActionAnimation to looke for your new member variable and play the appropriate animation from the table.
And finally, if you're just looking to play any 'ol animation from script, where there's always SetActionThread.
Good luck!
Dusty
08/18/2010 (9:04 pm)
Heheh well 'trigger' is a term way overbound in the Torque Engine, because they have animation triggers, move triggers, and script triggers. But the move triggers, which I believe are the variation that you're talking about, is really just an arbitrary system the torque developers set up to capture particular key presses for the action system. Now the engine assigns some uses to those move triggers, but it's pretty easy to rejigger them to do whatever you want. Here's how they work right now.
Right now the engine reserves room for up to 6 move triggers. I believe Torque is only using 5 of these at current, but I may be mistaken.
Trigger[0] and Trigger[1] are by default bound to left and right mouse click, in default.bind.cs as such:
//------------------------------------------------------------------------------
// Mouse Trigger
//------------------------------------------------------------------------------
function mouseFire(%val)
{
$mvTriggerCount0++;
}
function altTrigger(%val)
{
$mvTriggerCount1++;
}
moveMap.bind( mouse, button0, mouseFire );
moveMap.bind( mouse, button1, altTrigger );These triggers are by default fed into the shapeBaseImage state machine, and if you make use of shapeBaseImages states to handle firing a gun, then those activate the appropriate states to fire said gun. You can find the code that feeds those trigger's to the states in Player::UpdateMove, along with some other locations (like in vehicle):
// Trigger images
if (mDamageState == Enabled)
{
setImageTriggerState( 0, move->trigger[0] );
// If you have a secondary mounted image then
// send the second trigger to it. Else give it
// to the first image as an alt fire.
if ( getMountedImage( 1 ) )
setImageTriggerState( 1, move->trigger[1] );
else
setImageAltTriggerState( 0, move->trigger[1] );
}Trigger[2] is typically used for jumping. It's bound to the space bar in default.bind.cs. If you look at the bottom of Player::updateMove, you can see where they look at trigger[2], and activate the jump..
// Acceleration from Jumping
if (move->trigger[2] && !isMounted() && canJump())
{
// Scale the jump impulse base on maxJumpSpeed
F32 zSpeedScale = mVelocity.z;and later on they set the jump animation..
// If we don't have a StandJumpAnim, just play the JumpAnim...
S32 seq = (mVelocity.len() < 0.5) ? PlayerData::StandJumpAnim: PlayerData::JumpAnim;
if ( mDataBlock->actionList[seq].sequence == -1 )
seq = PlayerData::JumpAnim;
setActionThread( seq, true, false, true );Triggers 3 & 4, more recent additions, are associated with the crouch pose and the prone pose, respectively. I believe crouch is by default mapped to 'x' in default.bind.cs, I don't know what prone is mapped to. The way those trigger's work is simply that at the bottom of Player::udpateMove, they detect for the presence of those triggers, and set the player's pose appropriately..
// Update the PlayerPose
Pose desiredPose = mPose;
if ( mSwimming )
desiredPose = SwimPose;
else if ( runSurface && move->trigger[3] && canCrouch() )
desiredPose = CrouchPose;
else if ( runSurface && move->trigger[4] && canProne() )
desiredPose = PronePose;
else if ( canStand() )
desiredPose = StandPose;
setPose( desiredPose );Later on that same update, in Player::pickActionAnimation (the code I listed previously), it will detect the appropriate pose, and then set the action animation from the table that's associated with that pose.
So if you want your animation to be used as one of the action animations, such as running, walking, backing up, etc., then you need to fix up the enum and Player::pickActionAnimation as I described above.
If you want to your animation to play based on a specific key press, such as 'space' to jump, then you should feel free to hijack the movement trigger system to do whatever you want. I wouldn't necessarily *just* substitute 'prone root' animation for 'my arbitrary' animation and rely on the existing code to do the right thing. But I would certainly recommend something along the lines of adding a new member variable to player, adding code in update move to detect the presence of whichever trigger you decide to use, and then update player::PickActionAnimation to looke for your new member variable and play the appropriate animation from the table.
And finally, if you're just looking to play any 'ol animation from script, where there's always SetActionThread.
Good luck!
Dusty
#9
So if i understand this right essentially the move triggers are index based on the type and intent of the movement, where as 1 is fire, 2 is jump, 3 and 4 is crouch/pose changes, and 5, and 6 are open, so i can then set additional states on the open triggers, also is there any reason that prone and crouch cant share the same move trigger? being that they are both a pose change and could potentially share the same bind to change stance my reasoning says they should probably both be on trigger 3 instead of 3 and 4....
This is exactly what i was attempting to do but could only get what i wanted to work by hijacking other "built in" animation calls, I will dig in a lot deeper now with your thorough tour of the animation system (i am sure i will have more questions as i go)....Thanks again.
08/18/2010 (9:28 pm)
Dusty, you are totally awesome, thanks so much for the explanation,So if i understand this right essentially the move triggers are index based on the type and intent of the movement, where as 1 is fire, 2 is jump, 3 and 4 is crouch/pose changes, and 5, and 6 are open, so i can then set additional states on the open triggers, also is there any reason that prone and crouch cant share the same move trigger? being that they are both a pose change and could potentially share the same bind to change stance my reasoning says they should probably both be on trigger 3 instead of 3 and 4....
Quote:But I would certainly recommend something along the lines of adding a new member variable to player, adding code in update move to detect the presence of whichever trigger you decide to use, and then update player::PickActionAnimation to looke for your new member variable and play the appropriate animation from the table.
This is exactly what i was attempting to do but could only get what i wanted to work by hijacking other "built in" animation calls, I will dig in a lot deeper now with your thorough tour of the animation system (i am sure i will have more questions as i go)....Thanks again.
#10
08/19/2010 (2:11 pm)
Something to keep in mind about the move triggers is that they can be shared -- if a bit trickily, so probably not a good idea unless managed carefully. An example of this would be having a Player datablock configured to have jetting enabled which by stock code uses the same mvTrigger as alt-fire or 2nd weapon trigger. Kind of annoying to fire up the old jetpack and a few seconds later you get the 3 round burst from the rocketlauncher ;)
Torque 3D Owner Tim Dix (Raverix)
As for animation, don't know that one. I know you can hook up DSQs for the various animations, and I know there's a resource on how to add swimming... might be a good place to start.