Game Development Community

AIplayer standjump or any jump animation not working(Code fix)

by Terence Tan · in Torque Game Engine · 02/10/2007 (8:57 pm) · 6 replies

Been working on how to make our AI jump, and it seems the jump animations are working properly for AI players. It works fine for player characters but doesn't work (or get's overriden by the root animations).

I spotted a thread that discusses something similiar but it doesn't seem to conclude anything. www.garagegames.com/mg/forums/result.thread.php?qt=24848.

I am trigger the jump animation by putting a trigger[2] = true; in my getAIMove() function for AI Player.

My investigation on the jump animation being 'set' are:

In player.cc ( updateMove ):
if (move->trigger[2] && !isMounted() && canJump() && !isAnimationLocked())
   /* ORIGINAL CODE
   if (move->trigger[2] && !isMounted() && canJump())
    */
   // AFX CODE BLOCK (anim-clip) >>
   {
      // Scale the jump impulse base on maxJumpSpeed
      F32 zSpeedScale = mVelocity.z;
      if (zSpeedScale <= mDataBlock->maxJumpSpeed)
      {
         zSpeedScale = (zSpeedScale <= mDataBlock->minJumpSpeed)? 1:
            1 - (zSpeedScale - mDataBlock->minJumpSpeed) /
            (mDataBlock->maxJumpSpeed - mDataBlock->minJumpSpeed);

         // Desired jump direction
         VectorF pv = moveVec;
         F32 len = pv.len();
         if (len > 0)
            pv *= 1 / len;

         // We want to scale the jump size by the player size, somewhat
         // in reduced ratio so a smaller player can jump higher in
         // proportion to his size, than a larger player.
         F32 scaleZ = (getScale().z * 0.25) + 0.75;

         // If we are facing into the surface jump up, otherwise
         // jump away from surface.
         F32 dot = mDot(pv,mJumpSurfaceNormal);
         F32 impulse = mDataBlock->jumpForce / mMass;
         if (dot <= 0)
            acc.z += mJumpSurfaceNormal.z * scaleZ * impulse * zSpeedScale;
         else
         {
            acc.x += pv.x * impulse * dot;
            acc.y += pv.y * impulse * dot;
            acc.z += mJumpSurfaceNormal.z * scaleZ * impulse * zSpeedScale;
         }

         mJumpDelay = mDataBlock->jumpDelay;
         mEnergy -= mDataBlock->jumpEnergyDrain;

         setActionThread((mVelocity.len() < 0.5)?
            PlayerData::StandJumpAnim: PlayerData::JumpAnim, true, false, true);
         mJumpSurfaceLastContact = JumpSkipContactsMax;
      }

is being called properly. The 'setActionThread' is also called properly, setting the animation to StandJumpAnim if there is no x,y movement or JumpAnim if there is. The odd thing I have found out that in 'pickActionAnimation':
if (mContactTimer >= sContactTickTime) {
         // Nothing under our feet
         action = PlayerData::RootAnim;
      }

This part of the code is never executed. This leads me to believe that some how the client is NOT detecting it is 'in the air' and not picking the correct animation. This is re-enforced by the fact that when I DO supply a forward movement when setting the animation, the legs seems to 'move' on the player leading me to believe that the code thinks it is still on the ground.

My current question are:
1) Is the action animation solely driven by the positional information sent/interpolated by the client? I am assuming pickActionAnimation has a large role in it.
2) Why does the a player object seem to play the right animation while the AIPlayer which uses the same code and trigger mechanism NOT play the animation. It this something to do with the fact that the 'trigger' although set on the server, is not being forwarded to the client ( I am assuming since the player triggers the jump, it is being set properly on the client, and then replicated back to the server).

Will continue digging, but somebody who understands the animation system better may give me some better clues where to look.

#1
02/11/2007 (3:02 am)
A bit of an update on trying to figure this out.

I tested jumping and found out that the trigger state are not being sent when set on the server to the client which I believe is the primary issue with why my current AIPlayer implementation doesn't jump. Looking back, there used to be a script function setTrigger which did set the trigger states but that function is now deprecated.

Using setImageTrigger doesn't seem to update the state because I believe it is primary used to pulse mounted images(i.e. guns) rather than update the trigger state. I am now looking at a way to 'send' the trigger setting over to the client when it is set on the server so that both client/server are triggered which should be way things worked before.
#2
02/11/2007 (4:54 am)
.
#3
02/11/2007 (4:23 pm)
Yeah, things I have tried before:

1) The 'trigger' is being set on the server in the getAIMove function which is a 'isServerObject' executed function. The issue is that the 'movement values' of a jump are being applied on the server, and the 'action' animation 'seems' to be set but not replicated (or otherwise overridden by the client interpolation code).
2) setActionThread + applyImpulse won't work for similar reasons as the client interpolation knocks the ai back to the root animation.
3) A playThread(0, "animation") seems to work..but you have to either set the trigger or applyImpulse to it. You also need to 'clean it up' after you land plus the 'fall' animation is now 'broken' as basically I am blending a full bodied action animation on top of a the root animation. So I need to figure out how to switch it off if I follow this route.
4) I even considered manual using setTranform to 'plot' a jump path but soon gave this up as it would break too many things (and fight against the physics in the system) plus the math would be too complex to 'syncronize'.

I can't seem to find any code that represents how triggers are 'sent' down to the client. I am unsure that they even are as the 'guns' mounted on an AI just need to be pulse and the subsequent animation keyed down to the client.

My next best bet would be to override the read/writePackUpdates OR add to it to possible send down a mJumpFlag so the AI knows that a jump has occurred and is able to apply the same animation (and possible physics changes, although I am doubtful as the server won't necessary sync with the client with some jump interpolation).

I have noticed a variable called contactTimer, has a really high threshold of 30 game ticks. Not sure what it and ContactInfo have that can be made to work.
#4
02/11/2007 (8:48 pm)
.
#5
02/12/2007 (1:06 am)
These are my changes to player.cc to allow both jump and land animations to play correctly for AI.

First in player.h after mFalling:
bool mFalling;                   ///< Falling in mid-air?
   bool mJumpFlag;                  ///< Set to tell 'client' that a jump is in progress. For AI
   bool mJumping;                   ///< Set to enable, jump state
   bool mLandedFlag;                ///< Set to tell 'client' that a land is in progress
   bool mLanded;                    ///< Set to enable, landed state

Then in player.cc, instantiation
Player::Player()
{
...
   mMountPending = 0;

   // Added to support AI jumping
   mJumpFlag = false;
   mJumping = false;
   mLandedFlag = false;
   mLanded = false;
}

In updateMove:
void Player::updateMove(const Move* move)
{
...
      if (jumpSurface) {
         if (mJumpDelay > 0)
            mJumpDelay--;

         if (mJumpSurfaceLastContact) // Added
            mLandedFlag = true;   // Added

         mJumpSurfaceLastContact = 0;
      }

Further down in pack/unpack update

U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{
...
   if (stream->writeFlag(mask & MoveMask))
   {
      stream->writeFlag(mFalling);

      // Added to support jump animation
      stream->writeFlag(mJumpFlag);
      mJumpFlag = false;

      stream->writeFlag(mLandedFlag);
      mLandedFlag = false;

and unpack Update:
void Player::unpackUpdate(NetConnection *con, BitStream *stream)
{
...
   if (stream->readFlag()) {
      mPredictionCount = sMaxPredictionTicks;
      mFalling = stream->readFlag();
      // Added to support jump animation
      if (stream->readFlag())
         mJumping=true;

      if (stream->readFlag())
         mLanded=true;

and finally in pickAction animation.

void Player::pickActionAnimation()
{
...
   if (mFalling)
   {
      // Not in contact with any surface and falling
      action = PlayerData::FallAnim;
      mJumping=false;
   }
   else
   if (mLanded)
   {
      mLanded = false;
      mJumping = false;
      action = PlayerData::LandAnim;

      setActionThread(action,forward,false,true);
      return;
   }
   else
   if (mJumping)
   {
      // Based on velocity, choose appropriate jump
      action = (mVelocity.len() < 0.5)? PlayerData::StandJumpAnim: PlayerData::JumpAnim;
   }

The land animation still seems a bit broken as the blend to root seems a bit too quick. At later date I may migrate the code to it's own class so that the extra bits don't clog up network traffic.

To activate it just put this some where at the end of getAIMove in AIPlayer:
if (mJumpFlag)
   movePtr->trigger[2]=true;


and 

void AIPlayer::jump()
{
   mJumpFlag = true;
}

ConsoleMethod(AIPlayer,jump, void,2,2,"Jump")
{
   obj->jump();
}
#6
02/12/2007 (12:19 pm)
.