Today, I will walk through how I implemented Character Animation Logic in Bullet Time Ninja.
The code included with this article comes from an earlier build of Bullet Time Ninja, when I was still using the red ninja.
I chose to share this code instead of a recent build because the code is a lot more readable.
Understanding the Problem
There are two things we immediately know about our ninja:
- The ninja has multiple animations
- The ninja performs exactly one animation at a time
Of course, having a bunch of states without any way to transition between them is pretty useless. Let's add that detail to our chart:
The key concept this chart illustrates is this: starting from my current state, does the game require that I transition to any other state? Lets take a look at the implementation details.
Implementation in Code
First we have the state variable, and all of its possible states:
private var state:String; public static const STANDING:String = "standing"; public static const RUNNING:String = "running"; public static const JUMPING:String = "jumping"; public static const WALL_KICK_LEFT:String = "wall kick left"; public static const WALL_KICK_RIGHT:String = "wall kick right"; public static const AIR_KICK_UP:String = "air kick up"; public static const AIR_KICK_DOWN:String = "air kick down"; public static const AIR_KICK_LEFT:String = "air kick left"; public static const AIR_KICK_RIGHT:String = "air kick right"; public static const DYING:String = "dying";
This code is Flixel-specific. Since each state is a String, I can use the state as a String key that maps to a particular set of animation frames. (see FlxSprite.addAnimation())
// load the player graphic and add animations loadGraphic(ImgSprite, true, true, 16, 16, false); addAnimation(STANDING, [0], 15, true); addAnimation(RUNNING, [1, 2, 3, 4, 5, 6], 15, true); addAnimation(JUMPING, [8], 15, true); addAnimation(WALL_KICK_LEFT, [10], 15, true); addAnimation(WALL_KICK_RIGHT, [11], 15, true); addAnimation(AIR_KICK_UP, [9], 15, true); addAnimation(AIR_KICK_DOWN, [9], 15, true); addAnimation(AIR_KICK_LEFT, [12], 15, true); addAnimation(AIR_KICK_RIGHT, [12], 15, true);
Then we have the update logic:
// in my update() loop
// from my current state, see if I transition to any other state
switch(state)
{
case STANDING:
if(!shouldStand())
state = RUNNING;
if(!onFloor)
state = JUMPING;
break;
case RUNNING:
if(shouldStand())
state = STANDING;
if(!onFloor)
state = JUMPING;
break;
case AIR_KICK_UP:
case AIR_KICK_DOWN:
case AIR_KICK_LEFT:
case AIR_KICK_RIGHT:
case JUMPING:
if(onFloor)
{
if(shouldStand())
state = STANDING;
else
state = RUNNING;
}
else if(onWall)
{
if(facing == FlxSprite.LEFT)
state = WALL_KICK_LEFT;
else
state = WALL_KICK_RIGHT;
}
break;
case WALL_KICK_LEFT:
case WALL_KICK_RIGHT:
if(onFloor)
{
if(shouldStand())
state = STANDING;
else
state = RUNNING;
}
else if(!onWall)
{
state = JUMPING;
}
break;
case DYING:
break;
}
// play animation based on state
play(state);
A couple things to note here:
- This code is written to be scalable. There are simpler ways to write state transition code if your character only has only 2 or 3 states (basically drop the switch statement). However, once your character has more than 3 states, your code quickly becomes unreadable, and by extension risks subtle logic errors.
- The switch() statement occurs once per update(). If there happens to be a funny edge case where the character transitions between two different states repeatedly, you would see the character blinking rapidly when playing the game.
Conclusions
Lastly, some coding suggestions:
- Keep your state transition logic completely separate from any other logic in your game. The code is already complicated enough as is.
- Read any variable values however you like
- The only variable you should set is your "state" variable
- If the conditions inside your if() statements are complicated, put those conditions in a function that returns a Boolean result. Your future self will thank you for the clarity.
Your partner in science,
Greg



Oh, that was helpful
ReplyDelete