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