Game Development Community

dev|Pro Game Development Curriculum

Ai Poses Redux

by Steve Acaster · 05/07/2013 (6:29 am) · 6 comments

Pose change (stand, crouch, prone, sprint) has always been input driven, based off whatever the player is pressing on the keyboard at any one time (apart from swim and fall obviously).

Originally I had a system to mimick the player input, using the Player Class Trigger movement system and opening this up to the Ai during it's getAiMove() state and setting via script.

The most significant issue with this was that the Ai could fall out of it's scripted pose quite easily by slipping down terrain, falling, swimming, etc - and then reset itself in the default stand root pose. With the scripted event past and it's set triggers changed/cleared, it had no reason to revert to the chosen pose.

//stock code from Player.ccp getMove()
else if ( runSurface && move->trigger[sCrouchTrigger] && canCrouch() )
//but if sCrouchTrigger has already been accidentally cleared during movement ... then it's not going to call for crouch ... :(

To prevent this I decided on a stored variable mAiPose, so that the Ai could always check it's current pose was the correct one set via script rather than an accidental clearing of move triggers.

Because I wanted this to affect ONLY AiPlayer's I started in ShapeBase, so that during the player's UpdateMove() function, I could differentiate between the AiPlayer and a human Player. Player uses input for pose changing, and we don't want our Ai system to affect that.

First up, slap in our new mAiPose variable.

//shapeBase.cpp
ShapeBase::ShapeBase()
 : mDrag( 0.0f ),
   mBuoyancy( 0.0f ),
   mWaterCoverage( 0.0f ),
//...
   mMoveMotion( false ),
   mIsAiControlled( false ),//added comma yorks
	mAiPose( 0 )//added yorks
{
// ...

//shapeBase.h
//...
   /// @name Physical Properties
   /// @{
	F32 mAiPose; //yorks new
   F32 mEnergy;                     ///< Current enery level.
   F32 mRechargeRate;               ///< Energy recharge rate (in units/tick).
//...

Next up, we split up Player's UpdateMove() function so that this can never affect a player, and so that Ai's ignore their move triggers and use our stored AiPose variable. Replace the standard trigger->desiredPose system with this.

//Player.cpp
//at the end of UpdateMove() function

//...
   // Update the PlayerPose
   Pose desiredPose = mPose;
	
	if (!mIsAiControlled)//yorks new from here - leave the Player alone!
	{
	   if ( mSwimming )
		  desiredPose = SwimPose; 
	   else if ( runSurface && move->trigger[sCrouchTrigger] && canCrouch() )     
		  desiredPose = CrouchPose;
	   else if ( runSurface && move->trigger[sProneTrigger] && canProne() )
		  desiredPose = PronePose;
	   else if ( move->trigger[sSprintTrigger] && canSprint() )
		  desiredPose = SprintPose;
	   else if ( canStand() )
		  desiredPose = StandPose;
	}
	else//yorks select for Ai using mAiPose
	{
	   if ( mSwimming )
		  desiredPose = SwimPose; 
	   else if ( runSurface && mAiPose == 1 && canCrouch() ) 
				desiredPose = CrouchPose;
		   else if ( runSurface && mAiPose == 2 && canProne() )
					desiredPose = PronePose;
				else if ( mAiPose == 3 && canSprint() )
						desiredPose = SprintPose;
					else if ( canStand() )
							desiredPose = StandPose;
	}//yorks end of changes

   setPose( desiredPose );
}

Next for AiPlayer.cpp and adding new functions to control mAiPose.

//AiPlayer.cpp
//below getAiMove() function

//yorks start
void AIPlayer::setAiPose( F32 pose )
{
   mAiPose = pose;
}

F32 AIPlayer::getAiPose()
{
   return mAiPose; 
}
//yorks end

//...

//and somewhere below in the engine defines

//...

//yorks start
DefineEngineMethod( AIPlayer, setAiPose, void, ( F32 pose ),,
   "@brief Sets the AiPose for an AI object.nn"

   "@param pose StandPose=0,CrouchPose=1,PronePose=2,SprintPose=3.  "
   "Uses the new AiPose variable from shapebase (as defined in "
   "its PlayerData datablock)nn")
{
	object->setAiPose(pose);
}

DefineEngineMethod( AIPlayer, getAiPose, F32, (),,
   "@brief Get the object's current AiPose.nn"

   "@return StandPose=0,CrouchPose=1,PronePose=2,SprintPose=3n")
{
   return object->getAiPose();
}
//yorks end

//AiPlayer.h
//in public at the bottom
//...
   Point3F getMoveDestination() const { return mMoveDestination; }
   void stopMove();
	void setAiPose( const F32 pose );//yorks
	F32  getAiPose();//yorks
};

#endif

And there you go, recompile the whole thing. The Ai will now go into different move poses correctly, changing speed and bounds to suit, and they won't "bounce" out of it back into default stand root pose using the new setAiPose() function.

In script it works like this:
//0 = stand
//1 = crouch
//2 = prone ... which has no stock animations
//3 = sprint
//fall, swim, etc are taken care of automatically by the engine

%your_aiplayer_name_or_id.setAiPose(%pose_number);

Unfortunately ...

Of course this does not help with the minor/major ghosting/Ai objects actually animating correctly. Not being a real programmer I'm currently having to call the pose every tick during pack and unpack to make sure that the Ai animated correctly.

So ... caution ahead for this next piece of code.

//player.cpp
//in packUpdate() function
//...
   if (stream->writeFlag(mask & MoveMask))
   {
      stream->writeFlag(mFalling);

stream->writeFlag(mSwimming);//yorks start
//stream->writeFlag(mJetting);
stream->writeInt(mPose, NumPoseBits);//yorks end

      stream->writeInt(mState,NumStateBits);
      if (stream->writeFlag(mState == RecoverState))
//...

//player.cpp
//unPackUpdate() function
//...
   // MoveMask
   if (stream->readFlag()) {
      mPredictionCount = sMaxPredictionTicks;
      mFalling = stream->readFlag();

mSwimming = stream->readFlag();//yorks start
mJetting = stream->readFlag();
Pose newPose = (Pose)(stream->readInt(NumPoseBits));
setPose(newPose);//yorks end

      ActionState actionState = (ActionState)stream->readInt(NumStateBits);
//...

And now it will actually play the correct animations to go with the already correctly updated pose state and move speed/bounds/etc.

If anyone has a better solution, feel free to post it.

#1
05/07/2013 (4:30 pm)
Nice one Steve :-D
#2
05/08/2013 (9:54 am)
very interesting ! Thanks for sharing.
#3
05/08/2013 (7:52 pm)
I see you got more redux....
#4
05/09/2013 (8:15 am)
After two days of being thwarted ... I've actually got github to play nice and created a branch.

github.com/SteveYorkshire/Torque3D/tree/york

Not sure about making a pull request though, as I'm uncertain whether the un/packUpdate stuff is the best way of going about fixing the Ai animation problems ... :

edit: 16May2013 updated the link to the new, cleaned up fork, and made pull request
#5
05/09/2013 (4:56 pm)
Works with Torque3D 3.0! after I fixed an embarrassing typo ... :S
#6
05/15/2013 (1:46 pm)
Quote:Not being a real programmer...

You started to sound like a real programmer tho. Nevertheless this might be a nice solution. Thanks.