Game Development Community

Adding Walking to TGE

by Stephen · in Torque Game Engine · 10/06/2005 (4:27 pm) · 20 replies

I have searched the forums for anything about walking. I have came up with a few things.

setActionThread

Sprint Resource

New positions resource

I need some help with adding the walk, how would I go about this. Could someone lend a hand?

Thanks,
Stephen

#1
10/06/2005 (4:34 pm)
Hmmm well... there's little difference between walking and running. I know there's a varible called move speed or something adjusting that will slow the character down. then you need an animation for walking.. that you'll have to figure out how to do on your own. Other thing.. maybe you'll want to set a "Walk" button that you can use to turn it all on.

Walking is something i'll implement eventually... but in RPGs.. no one ever walks unless it's a cut scene it seems :)
#2
10/06/2005 (4:48 pm)
Well, I'm making a FPS and walking is one of the big things in my project. I have a walk animation already, I just need to know how to make it where you can press and hold the shift key to run.
#3
10/06/2005 (5:47 pm)
Well you can just use the sprint resource and have it so the speed multiplier is 0.5 or something so you'll move slower in stead of faster.
#4
10/06/2005 (6:12 pm)
Okay, I will try that out. Thanks
#5
10/15/2005 (11:57 am)
Stephen,

You and I are in the same boat and I don't think this one came with an instruction manual. I propose we share findings as we try to sort this out because it just doesn't look like it's been done before.

For my part, I'm currently combing the engine to try and get a handle on this. I'm currently going through TSShape, which is responsible for animating shapes in general. I've budgeted a whole month for just this one thing because it's critical to my game and I have a feeling it's going to be one of those deep down tweak and hack things. (Plus I'm still new to C++, so I have to learn much of that as I go along...)

At any rate, you are not alone in your quest for extending character animations.

-MJL
#6
10/15/2005 (2:16 pm)
I thought I was the only one going though this problem but now I'm not alone. Well, what I have done is added the New positions resource with the enhancements resource to my project and I'm using the AdamPack. I have made new animations for him in crouch and prone (just the root, not the crouch and prone forward animation). Then I tried looking for any resources for walking and I have found nothing (which is sad because there are so many resources and you would think "Walking" would be one of them). So right now I have made a simple fix, I have just made a new PlayerDatablock and just changed the forward speed. So when you press shift, it uses that datablock and the same thing to run. Btw, I haven't got the sprint resource working to. So I guess for right now I'll just wait until someone makes a Walking resource.

Good Luck, Mike

-Stephen
#7
10/28/2005 (6:29 am)
Looks like there's three of us in that boat.

I have gotten the sprint resource working. Its been about a month since I got it in but, I believe all I did was follow the resource's instructions, then applied the fix that Sabrecyd posted and the fix that Mike Stoddart posted. That's all I did to get it working, at least I think, I don't think I did any other tweaking to get it to work.
#8
10/28/2005 (7:40 am)
Okay, I'll try adding the resource again but I'll add the fixes. Thanks
#9
10/28/2005 (8:50 am)
I got walking in with minimal fuss, by using a trigger (trigger[4], in my case) to control wheter the character is walking or not, and switch the forward animation and forward speed based on it. Lemme see if I can post the changes...
(Ah, as a bonus, you get the walk animation to play when the character is rotating in place)

This requires changes in 3 files (player.h, player.cc and shapeImage.cc).

In player.h, find:
F32 minRunEnergy;               ///< Minimum energy required to run

And add the following line just after it:
F32 maxAltForwardSpeed; //Alternative max forward speed

Locate this code block:
// These are set explicitly based on player actions
      FallAnim,
      JumpAnim,
      StandJumpAnim,
      LandAnim,

And change it to:
// These are set explicitly based on player actions
      FallAnim,
      JumpAnim,
      StandJumpAnim,
      RotationAnim, //Animation to play while rotating in place
      AltForwardAnim, //Alternative forward animation
      LandAnim,

Now after this line:
Point3F mLastWaterPos;        ///< Same as mLastPos, but for water
Add this:
F32 mRotDirection; //How fast the player is rotating
   S32 mRotationTicks; //How long has the player been rotating

   //This specities the player alternate movement mode
   bool mAltMovement;

Continues in the next post...
#10
10/28/2005 (8:51 am)
Now open player.cc. After the line:

const F32 sAnchorMaxDistance = 32;
Add:
//Constant used to control the rotation animation scaling
const F32 sRotationAnimationAdjust = 7;

Change this block:
// These are set explicitly based on player actions
   { "fall" },       // FallAnim
   { "jump" },       // JumpAnim
   { "standjump" },  // StandJumpAnim
   { "land" },       // LandAnim
To:
// These are set explicitly based on player actions
   { "fall" },       // FallAnim
   { "jump" },       // JumpAnim
   { "standjump" },  // StandJumpAnim
   { "run" },        //RotationAnim  //hack, should be rotate
   { "run2" },       // AltForwardAnim, 
   { "land" },       // LandAnim
(note that the RotationAnim has been set to "run", this is a hack, actually, because our walk animations is called "run", and your actual sprint animation is called "run2", and our code just plays the walk animation while rotating, Resident Evil-style - a proper approach would be having a separate rotating animation)

In PlayerData::PlayerData(), after this line:
minRunEnergy = 0;
Add:
maxAltForwardSpeed = 5; //Alternative max forward speed

In PlayerData::initPersistFields(), after this line:
addField("minRunEnergy", TypeF32, Offset(minRunEnergy, PlayerData));
Add:
addField("maxAltForwardSpeed", TypeF32, Offset(maxAltForwardSpeed, PlayerData));

Find the line:
stream->write(minRunEnergy);
And add below it:
stream->write(maxAltForwardSpeed);

Find the line:
stream->read(&minRunEnergy);
And add below it:
stream->read(&maxAltForwardSpeed);

At the end of Player::Player(), add:
mRotDirection = 0.0f;
   mRotationTicks = 0;

   //This specities the player alternate movement mode
   mAltMovement = false;

Continues in the next post...
#11
10/28/2005 (8:53 am)
In Player::updateMove(), find this block:
F32 y = move->yaw;
      if (y > M_PI) y -= M_2PI;
And add this after it:
if (mFabs(y) > mFabs(mRotDirection))
		  mRotDirection = y; 			
	  if (mRotDirection < 0.001f && mRotDirection > -0.001f)
		  mRotDirection = 0.0f;			 
	  mRotDirection = getMax(getMin(mRotDirection, 0.05f*(F32)M_PI), 0.05f*(F32)(-M_PI));
	  
	  if (mFabs(y) > 0.01f)
		  mRotationTicks = 3;
	  else 
		  mRotationTicks--;

	  if (mRotationTicks < 0) {
		  mRotationTicks = 0;
		  mRotDirection = 0.0f;
	  }
(Sorry for the magical numbers)

Find this code block:
zRot.getColumn(0,&moveVec);
      moveVec *= move->x;
      VectorF tv;
      zRot.getColumn(1,&tv);
      moveVec += tv * move->y;
And add this after it:
//Change the speed based on movement mode	  
      mAltMovement = move->trigger[4];
	  F32 maxSpeed = mDataBlock->maxForwardSpeed;
	  if (mAltMovement)
		  maxSpeed = mDataBlock->maxAltForwardSpeed;

Right below, change this block:
// Clamp water movement
      if (move->y > 0)
      {
         if( mWaterCoverage >= 0.9 )
            moveSpeed = getMax(mDataBlock->maxUnderwaterForwardSpeed * move->y,
                               mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x));
         else
            moveSpeed = getMax(mDataBlock->maxForwardSpeed * move->y,
                               mDataBlock->maxSideSpeed * mFabs(move->x));
      }
To:
if (move->y > 0)
      {
         if( mWaterCoverage >= 0.9 )
            moveSpeed = getMax(mDataBlock->maxUnderwaterForwardSpeed * move->y,
                               mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x));
         else
            moveSpeed = getMax(maxSpeed /*mDataBlock->maxForwardSpeed*/ * move->y, 
                               mDataBlock->maxSideSpeed * mFabs(move->x));
      }

Now, in Player::updateActionThread(), find this IF block right at the end:
if ( (mActionAnimation.action != PlayerData::LandAnim) &&
        (mActionAnimation.action != PlayerData::NullAnimation) )
   {
      // Update action animation time scale to match ground velocity
      PlayerData::ActionAnimation &anim = 
         mDataBlock->actionList[mActionAnimation.action];
      F32 scale = 1;
      if (anim.velocityScale && anim.speed) {
         VectorF vel;
         mWorldToObj.mulV(mVelocity,&vel);
         scale = mFabs(mDot(vel, anim.dir) / anim.speed);

         if (scale > mDataBlock->maxTimeScale)
            scale = mDataBlock->maxTimeScale;
      }
And add this after it:
if (mActionAnimation.action == PlayerData::RotationAnim)
		  scale = mFabs(mRotDirection)*sRotationAnimationAdjust; //Magical Number

Continues in the next post...
#12
10/28/2005 (8:56 am)
Now replace the whole Player::pickActionAnimation() (because I'm too lazy to point all changes) with this:
void Player::pickActionAnimation()
{
   // Only select animations in our normal move state.
   if (mState != MoveState || mDamageState != Enabled)
      return;

   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;
   }
   
   bool forward = true;
   U32 action = PlayerData::RootAnim;
   if (mFalling) 
   {
      // Not in contact with any surface and falling
      action = PlayerData::FallAnim;
   }
   else
   {
      if (mContactTimer >= sContactTickTime) {
         // 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.1;
         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*/) { 
					F32 d = mDot(vel, anim.dir);
					if (d > curMax) { // PRLD_Player_Walk
						curMax = /* d */ anim.speed;
						action = i;
						forward = true;

					}
					else
					{
						// Special case, re-use slide left animation to slide right
						if (i == PlayerData::SideLeftAnim && -d > curMax) 
						{
							curMax = -d;
							action = i;
							forward = false;
						}
					}
				}
         }


		
		
		 //Force the player animation if specified		
		 if (action == PlayerData::RunForwardAnim && mAltMovement)		 			 
			 action = PlayerData::AltForwardAnim;			 		 		 
		  
		 //Select rotation animation
		 if ((mFabs(mRotDirection) > 0.005f) && action == 0) {
			 PlayerData::ActionAnimation &anim = mDataBlock->actionList[PlayerData::RotationAnim];
			 if (anim.sequence != -1)
				 action = PlayerData::RotationAnim;						 
		 }	 
		
	  }
   }


   setActionThread(action,forward,false,false);
}

Continues in the next post...
#13
10/28/2005 (8:56 am)
Now search for:
stream->writeFlag(mFalling);
And add this after it:
stream->write(mRotDirection);

Now search for:
mFalling = stream->readFlag();
And add this after it:
stream->read(&mRotDirection);

At the end of Player::unpackUpdate(), where the big IF ends, add this ELSE:
else {
	   mRotDirection = 0.0f;
   }

Now, let's open shapeImage.cc. You only need to change it if you want your AiPlayers to be able to switch between walking/running too. This removes a limitation where setImageTriggerState didn't do anything to image slots that had no images mounted. So, you couldn't even make a bot jump (bot.setImageTriggerState(2, 1)) without mounting a dummy shapBaseImage to slot 2. With this change, the mounted shapeBaseImage requirement is removed (it doesn't break anything in the engine).

Inside ShapeBase::getImageTriggerState(), change
if (isGhost() || !mMountedImageList[imageSlot].dataBlock)
To:
if (isGhost()/* || !mMountedImageList[imageSlot].dataBlock*/)

Replace the whole ShapeBase::setImageTriggerState() with this:
void ShapeBase::setImageTriggerState(U32 imageSlot,bool trigger)
{
   if (isGhost() || /*!mMountedImageList[imageSlot].dataBlock*/)
      return;
   MountedImage& image = mMountedImageList[imageSlot];

   if (trigger) {
      if (!image.triggerDown /*&& image.dataBlock*/) {
         image.triggerDown = true;
         if (!isGhost()) {
            setMaskBits(ImageMaskN << imageSlot);			
			if (image.dataBlock)
				updateImageState(imageSlot,0);
         }
      }
   }
   else
      if (image.triggerDown) {
         image.triggerDown = false;
         if (!isGhost()) {
            setMaskBits(ImageMaskN << imageSlot);
			if (image.dataBlock)
				updateImageState(imageSlot,0);
         }
      }
}

That should be it. I hope I didn't forget any changes (I had to filter out some other custom code we have in our Player class). Now, you'll need to change your player DTS CS file, to load an animation called "run2". This is the alternative walking animation. Also note that there is a new playerData value, to set the alternative forward speed, that must be defined too. To switch between run and run2, just set $mvTriggerCount4 to true in the client. Which animation will be the running and which one will be the walking is up to you to decide. You could have "run" as the walking and "run2" as running, and vice-versa.
#14
10/28/2005 (11:56 am)
@Stephen
No problem, let me know if you have any problems with it.

@Manoel
Thanks a lot. I had actually had my eureka moment shortly before you posted this. You definitely helped confirm some of my theories on how things worked. I ended up doing things a little differently, but the basic idea is still the same.

I actually added the new animation to the move state section of the animations in player.h.

// These enum values are used to index the ActionAnimationList
      // array instantiated in player.cc
      // The first five are selected in the move state based on velocity
      RootAnim,
      RunForwardAnim,
      SprintForwardAnim,
      BackBackwardAnim,
      SideLeftAnim,

Now the SprintForwardAnim will be my run animation and the RunForwardAnim will be my walking.

Then player.cc:

// These are selected in the move state based on velocity
   { "walk",  { 0,+1,0 } },       // RunForwardAnim,
   { "run",  { 0,+1,0 } },        // SprintForwardAnim,
   { "back", { 0,-1,0 } },       // BackBackwardAnim
   { "side", { -1,0,0 } },       // SideLeftAnim,

An important thing here to know, which Manoel's post helped me confirm, is that the names here like walk and run are the names of the transform node in the animation. If the name of the transform node doesn't match up then it won't work.

Now I already had the sprint resource in so I didn't need to worry about changing my speed, I just needed to play a different animation. So I started by added a static bool near the top of player.cc

//bool for walk/run animations - SB 10/28/05
  static bool bIsSprinting = false;

I then assign this to move->sprint's value in UpdateMove:

//for walk/run animations -SB 10/28/05
   bIsSprinting = move->sprint;

Then in pickActionAnimation I check for it.
for (U32 i = 1; i < PlayerData::NumMoveActionAnims; i++) 
         {
            PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
            if (anim.sequence != -1 && anim.speed) {
               F32 d = mDot(vel, anim.dir);
               if (d > curMax) 
               {
                  curMax = d;
                  action = i;
                  forward = true;
                  
                  if((i == PlayerData::RunForwardAnim) && bIsSprinting)
                  {
                  	action = PlayerData::SprintForwardAnim;
                  }
               }
.......

And that's all I had to do. I definitely like that using the animation while rotating. I'll probably end up using that. I have noticed one small bug with my implementation, when my energy is depleted I return to walking speed like I'm supposed to but the animation is still the run.
#15
10/28/2005 (4:27 pm)
Quote:
And that's all I had to do. I definitely like that using the animation while rotating. I'll probably end up using that. I have noticed one small bug with my implementation, when my energy is depleted I return to walking speed like I'm supposed to but the animation is still the run

Make sure that you are both setting the new animation, and also setting the correct mask bit setMaskBits(ActionAnim) I would guess, but search for the correct mask bit define.
#16
11/09/2005 (10:45 am)
Quote:
Make sure that you are both setting the new animation, and also setting the correct mask bit setMaskBits(ActionAnim) I would guess, but search for the correct mask bit define.

Where would I do that at?

I actually think that particular bug is probably due to the fact that I'm basing it off of the sprint bool, so if the key is down its true, which means it'll play the sprint animation. I've discovered a new bug recently that makes me think there may be more wrong with my implementation than that. I've noticed that when I'm in first person, walking and I look down some my arms and gun suddenly vanish will stay gone unless I move backward, side to side or look around. Could this be because of the mask bit? I've also notice that when they disappear the frame rate start to spasm some.
#17
11/09/2005 (11:10 am)
Thanks for the help just was getting ready to ask the same question
#18
11/09/2005 (11:32 am)
Thanks Morrie, at least this helps confirm there is something wrong with my code. I had also tested it with the original run animation, which I renamed walk to see if it was the animation itself and I still got the disappearing effect.
#19
11/09/2005 (3:36 pm)
Morrie,

You are getting the disappearing bug right? After I reread your post I wasn't sure if you meant you were or you were just refering to the question about the maskbits.
#20
11/10/2005 (7:23 am)
Ok, nevermind, I just discovered that the disappearing weapon image was an unrelated issue. So my solution isn't broke like I thought, there just remains the issue of where the setMaskBits should go. I've tried it in the pickActionAnimation where I set the SprintForwardAnim but it doesn't seem to have an effect.