Game Development Community

New Player Type - Update Jump Animation

by Jesse Allen · in Torque 3D Professional · 10/02/2014 (8:57 am) · 14 replies

Hi all, I've been designing a new player type in Torque that is basically a hybrid of the Player and AIPlayer. I'm doing this due to control object issues in my current project. Basically, what I'm doing is creating an AIPlayer that can still perform actions as if it were a normal player. i.e. Run, Jump, Shoot on key press etc. The good part is I've been largely successful in this venture. I can move, jump, attack, etc. just as I had envisioned. I'm learning a ton about Torque's source and C++ usage in general as I progress :)

The only problem I'm having currently has to do with animation states. It is my understanding that the base Player will perform certain movement actions based on velocity, etc. automatically. For example, if the player's movement speed increases the player will automatically play the run animation. If the player is detected as falling, the fall animation will automatically play. These actions are different from the standard 'Trigger' type movements, since these will occur automatically based on the character's movement state. Alright, with all of that out of the way, here's the issue:

Jumping upwards
I can make my AIPlayer jump on demand on a key press. When he is falling, his animation will update automatically. When he lands, his land animations updates. All of this is perfect! The issue arises when jumping upwards, especially when holding a movement direction. Firstly, there is no animation played when the player jumps. It just kinda gets pushed upwards. How can I setup the player so that it will update to a jump animation when it jumps upwards?

When holding a direction key and jumping the player will jump and move fine, but he will continue to play his run animation while in the air. This gives the appearance of the player running in mid-air. How can I setup the player to update to jump animation and also ignore movement animations if a direction key is pressed?

It seems as if the jump animation isn't triggering properly for some reason, despite the fall and land(and everything else) working flawlessly.

Edit: I may have found a workaround for this, but it will require a bit more work before I have any results. I'll report back if it doesn't work out..

#1
10/02/2014 (1:43 pm)
How much of the existing Player animation code are you using? Here's part of the code that causes the jump animation to play in stock Player - do you use a similar method?
#2
10/02/2014 (2:15 pm)
Hi Danny, thanks for the reply.

I've got an enum setup for the AIPlayer's jump states:
Jump if it's time to jump.
Rest if it's time to rest.

Then, in the AIPlayer's AIPlayer::getAIMove() I added at the bottom:
if(mAiJump == Jump)
   {
      movePtr->trigger[2] = true;
      mAiJump = Rest;
   }

That's about it really. What I'd really like to do is bypass the automatic move states altogether and just call to setActionThread() manually. Due to the way that AIPlayer is inherited from Player, though, it's tough.

Do you have any thoughts on how I might update my AIPlayer class so that I can call to setActionThread() directly? Even those that are called automatically by the code in Player (based on velocity)? Any way I can just kinda disable the auto-animation and handle that on my own?

Edit: I forgot to say that I'm handling the movement with keyboard input just by setting the moveDestination for the AI way far off in the direction the AI is moving. In this game, with the blocks and what-not, the AI will never actually go that far without needing to jump or let off the movement key at some point. So what we've got is a hacked movement that's constantly playing the walk animation in the air lol (until the key is released). What's odd is that all the normal player animation updates are happening, just not the jump one.
#3
10/02/2014 (7:25 pm)
You can override the default move animations in
void Player::pickActionAnimation() found in player.cpp. For example, look at how jetting overrides the default animation when the mjetting flag is set:

// Jetting overrides the fall animation condition
   if (mJetting)
   {
      // Play the jetting animation
      action = PlayerData::JetAnim;
   }

You could set up your JumpAnim to do the same thing by creating a mJumping flag, then switching it on or off when jumping. Something like this:

// Jumping overrides the fall animation condition
   if (mJumping)
   {
      // Play the jumping animation
      action = PlayerData::JumpAnim;
   }

If you do a search on mJetting in the source code, you can use that as a template for how to set up the flag. Once you have it set up, you can add a public function to the player in player.h. Find this section:
public:
   DECLARE_CONOBJECT(Player);

Then, right under
void allowSwimming(bool state) { mAllowSwimming = state; }
add this:
void isJumping(bool state) { mjumping = state; }
This function will allow you to call AIPlayer.isJumping(true) from script when you want to override the defaults, and AIPlayer.isJumping(false) to switch back.

You also will have to define it in player.cpp as an engine method:
DefineEngineMethod( Player, isJumping, void, (bool state),,
   "@brief Set if Player is in a jumping staten")
{
	object->isJumping(state);
}

I had to do something similar in my game, when my player gets ejected from the boat.
#4
10/03/2014 (11:26 am)
Thanks a lot for the in-depth explanation on that Joseph. That's a neat trick to force the flag like that. Unfortunately it still won't work in this case, although I am getting the console messages I put in place to tell me if the flag is being set. I tried to use if(mJumping) in 3 different locations in the void Player::pickActionAnimation() found in player.cpp to no avail. Each time I'd get the console message that indeed the flag was being set, but no jump animation. I used it like so:
// Jumping overrides the ... animation 
   if (mJumping)
   {
      // Play the jumping animation
      action = PlayerData::JumpAnim;
   }

Since this is basically just an AIPlayer, I also tried the mJumping flag in AIPlayer.cpp in the getAIMove() block. I set it up precisely where the movePtr increases mMoveSpeed, which is how the AI moves. I am getting my console message that mJumping is true at exactly the right time - when moving and jumping simultaneously. No animation :(

Something here is conflicting with the AIPlayer movement I guess. My AIPlayer will automatically change his action state if falling, landing, and even swimming. I pulled a water plane over him and he immediately goes into swimming mode. Although it is strange I can hold a movement key left or right and the swimming or falling animation still play. The only thing that will not happen is the jump animation. The AIPlayer moves with setDestination() in AIPlayer.cpp. While I agree that by all rights this flag should fix it, for some reason it doesn't. Ugh, why do I always find the corner cases? lol :P

Edit: Oh, I also added:

stream->writeFlag(mJumping); and stream->readFlag(mJumping); to my pack/unpackUpdates. Made no difference :/
#5
10/03/2014 (1:15 pm)
Jesse, exactly how are you triggering your AIPlayer to start his jump? It is at that point where you will have to set the jump flag, and I'm assuming you do have a specific jump animation set up for the player. I guess I'm not understanding your situation exactly, maybe you could make a vid? In my situation, the player drives a boat. When it hits an obstacle, I send a jump trigger to eject the player, and play an animation. Before I set this up, the player would be running in mid-air, now he does a nice head-first dive into the water. To make this animation override the default, I set my ejecting flag to true, then I schedule it to set back to false 1 second later. Since this happens in the boat, I set it up in the vehicle collision callback (in script):
function VehicleData::OnCollision(%this,%obj,%col,%vec,%speed)
{
   if(%col.getClassName() $= "AIPlayer" && %col.getState() !$= "Dead") //We hit a live gator
   {
      //echo("Hit a Gator weighing "@ %col.mass);
      echo("Speed = "@%speed);
      echo("Vector = "@%vec);

      if(%col.mass > 250 && $myPlayer.isMounted()) //We hit a Big gator
      {
         $mvTriggerCount2 ++; //Force jump from boat
         $myPlayer.isEjecting(true);
         commandToServer('Throw',"Weapon");
         schedule(64, 0, "resetJump");
         schedule(1024, 0, "endEject");
      }
   }
}

function resetJump()
{
   $mvTriggerCount2 ++;
   
}

function endEject()
{
   $myPlayer.isEjecting(false);
}

And here is the result (WIP!)

#6
10/03/2014 (1:56 pm)
Nice vid :) It reminds me of my home-state and boat rides on the bayou. Good work!

I can see the difference right away, since in your example you are triggering the jump with $mvTriggerCount2++ in script. That means you are using a Player and not an AIPlayer. The difference is the AIPlayer cannot call to $mvTriggerCount2++ in script and jump. It just won't work, since if you are using an AIPlayer the AIPlayer is not your ControlObject. The camera is.

So, what you have to do is go into the AIPlayer.cpp file and set it up manually. What happens in the AIPlayer.cpp is there are some functions there that perform the commands, telling the AIPlayer what to do and when. Luckily AIPlayer is derived from Player, so most of the stuff that Player can do an AIPlayer can do as well. The problem I am having, Joseph, lies with a conflict in that AIPlayer.cpp file and the Player.cpp file. Somehow, when the AIPlayer calls the getAIMove() function it is not taking into account the jumping at all. I've added in the mJumping flags right in there in the getAIMove() which should work.

Crack open the AIPlayer.cpp file and have a look, and also read my post #2 above to see exactly how I'm triggering the jump. That code up there is at the bottom of the AIPlayer::getAIMove() function.

It's hard to see the problem if you're not looking at AIPlayer.cpp specifically and seeing how it sets the moveSpeed and acts on that. Still, though, technically the AIPlayer should be automatically playing the jump animation based on vertical speed as defined in the Player.cpp(because AIPlayer is derived from that). Hope that all makes sense. If not, sure, I'll go ahead and grab a vid of it.

Additional Note
None of this would be necessary at all if the camera would actually just stay on the player in the first place. I've been testing 2 camera modes for this: isometric and sideview. The main problem is with the side-view. If you set the camera up to OrbitObject at -90 degrees you get a nice side-view. If you walk about 10-20 meters away, though, the control object will automatically change and the camera won't stick to where it was(looking at the player from a -90 degree angle).

If the ControlObject didn't automatically change (for some unknown reason) then I would just be using the default Player class and all would be well.
#7
10/03/2014 (8:40 pm)
Thanks Jesse! Using the AIPlayer might not be the right way to go to achieve what you want. It may be easier to figure out a way to make the side view camera work for you. Have you ever tried using freelook with the Player? Hold down the "v" key and you can rotate the camera. If you then move the player, the camera will follow him, and stay in the rotated position as long as you hold down the v. Maybe you can figure out how to turn this state into the camera you want.
#8
10/03/2014 (9:00 pm)
Yep, you're absolutely right Joseph. I tend to agree that it may be best just to get the camera to do my bidding instead. This way I won't have to deal with hacking up the AIPlayer class even more when other issues arise down the line. I've tried every camera setting I could find, but you know it never occurred to me to try altering the freelook in some way. I'll look more into this and see what I can come up with. Thanks for the lead. Surely there must be a way!

Edit: Hmm, tracked down a good bit of ControlObject code in GameConnection.cpp. May be a way to override this behavior of the ControlObject switching, but I hadn't discovered it yet.
#9
10/04/2014 (9:26 am)
Alright, I'm at a crossroads atm. On one hand, it can be better to use a Player and on the other it can be better to use the AIPlayer.

  • If I use the Player, I've got to either:
  • A) Set the $mvFreelook variable so that it's on all the time(easy) and figure out a way to adjust the yaw to a sideview and lock it there.
    B) Setup an OrbitObject camera and figure out a way to override whatever is happening in GameConnection.cpp to cause the ControlObject to switch automatically.

  • If I use the AIPlayer, I've got to:
  • A) Fix the client ghost animations, preferably with new logic in the AIPlayer.cpp based on velocity.

    I found this thread by Guy Allard that is exactly what I'm facing with the AIPlayer. Apparently, it's been a long standing ghosting issue(that posting was in 2012 and he reports it goes back prior to that). He also proposes a fix in that thread, although it is disputed by Ivan. Note that I did apply Guy's fix to test it out, and it fixes rare cases where AIPlayers glide across the ground but still doesn't update AIPlayer jumps.

    One thing that is important here is that if I do use the Player and lock down the $mvFreeLook trigger, the game will be in a state where mousemovents that would normally adjust the angle of the Player's aim are ignored (when $mvFreeLook is on, the pitch function takes over). I can disable the pitch function, but the player still wouldn't alter its aim up/down. This is important in sideview that this will happen.

    Anyone have any suggestions or comments?
    #10
    10/04/2014 (11:34 am)
    It's interesting that only AIPlayers have animation issues. I suspect Ivan's last post (picking animations based on velocity) is correct.

    In your case Jesse, it might be better to get a proper sideview camera working and use Player instead.

    I may be misunderstanding what you're doing, but how are you setting up your sideview camera? I quickly hacked up this bit (tacked onto the end of GameCore::spawnPlayer()):

    // Make the camera side-facing
    %client.setCameraObject(%client.camera);
    %client.camera.setOrbitObject(%client.player, "0 0 " @ -mDegToRad(90), 15, 25, 25, false, "0 2 1", true);
    %client.setFirstPerson(false);

    I can move around the level with a Player object and the camera never seems to lose track of it.
    #11
    10/04/2014 (11:46 am)
    Hi Chris, thanks for the reply. Your code is pretty much exactly what I have tried, only I dropped it at the end of GameCore::preparePlayer(). Did you run a long ways with that setup? The reason I ask is I just copied your code, pasted it at the end of GameCore::spawnPlayer() and the camera still drops the ControlObject after I run off a distance. You can replicate it like this:

  • Use the above code.
  • Launch the Empty Terrain mission.
  • Press W(forward) and hold shift and run off to the right over that first big dune.
  • Keep going, going...About the time you get to the bottom of that dune you should see the camera in FPS view again. Only this time, the camera is at the position of the player so the player isn't there. Should just be gliding across the ground basically.

  • Please let me know if this is the case. If not, perhaps what build of Torque are you using?
    #12
    10/04/2014 (12:01 pm)
    I've tried it a few different times. I can run the character the end of the terrain and the camera never loses track of the player. Something funky does happen when the player gets close (~5m) to the edge of the terrain. The player disappears and the console seems to indicate the control object has been changed. Presumably the player has went out of scope and the control object has been reverted to the camera. Why that happens I'm not sure.

    Edit: This is using a clean build based off the latest (well, as of 2 days ago) development branch.
    #13
    10/04/2014 (12:55 pm)
    Dude, you nailed it!! I am eternally grateful for your help in tracking this problem down!! Once you mentioned 'out of scope' a light came on. You know what it was? I had added the Improved ShapeBase Limiting resource by Vince which, by all rights, is a fantastic resource. But in this case(sideview), the shape culling is unnecessary. Man, I thank you! You saved me MUCH hair pulling!

    Of course, I hope the same out of scope issue doesn't crop up again later on down the line if I travel too far to one side...but at least this is a major victory. I couldn't understand why on Earth the ControlObject would just drop lol.
    #14
    10/04/2014 (1:00 pm)
    Awesome! :]