Finite Automata in Games
This article is a work in progress. Please check back later.
This example is from a Java game engine I'm working on. It's a platformer, 2D sidescroller. The character and map tile sizes are rigid to allow specific kinds of moves. The tiles are square, and the character is two tiles tall. The hero can interact with the environment in ways typical to platformers -- jumping, running, ducking. etc. I'm also including advanced movement types found in some games -- wall jumping, grabbing ledges, rolling (similar in effect to sliding), crawling, etc.
The character can be performing any of these actions, and which action he's performing determines which sprite to display on the screen. During the input processing on each frame, the character may change actions depending on input or environment collisions.
Typical finite automata are handled in code with a switch statement. Here, we use a combination of boolean states and enums to create an easy-to-understand set of code. Simplified platformer games would use a subset of these states.
Some actions last a certain duration and can't be interrupted by the player. Examples are rolling, musclingUp, loweringDown, and vaulting. These last a fixed number of frames (unless a wall is hit). The finished() function checks to see if we're at the last frame of this action. One hybrid state is launching. The player can control how high/far he jumps by how long he holds the jump key. While he's still launching, gravity is not applied (which has the effect of a stronger jump). The player may continue to launch until he releases the key or until he's hit the frame limit, determined by finished(launching).
Pseudocode for now. Once I get the details worked out I'll post a cleaned up version.
// process player input for this frame
switch(characterState) {
case ducking:
if (!pressing(down) && !obstacle(above)) {
characterState = standing; break;
}
if ((pressing(right) || pressing(left)) && !obstacle(ahead)) {
characterState = crawling; break;
}
if (pressing(hand) && obstacle(ledgeBelow)) {
characterState = loweringDown; break;
}
break;
case crawling:
if (!pressing(right) && !pressing(left)) {
characterState = ducking; break;
}
if (obstacle(ahead)) {
characterState = ducking; break;
}
if (!obstacle(below)) {
characterState = airborne; break;
}
break;
case rolling:
if (obstacle(low)) {
characterState = ducking; break;
}
if (finished(rolling)) {
if (obstacle(above)) {
characterState = ducking; break;
}
else {
characterState = standing; break;
}
}
if (!obstacle(below)) {
characterState = airborne; break;
}
break;
case standing:
if ((pressing(right) || pressing(left)) && !obstacle(ahead)) {
characterState = running; break;
}
if (pressing(down)) {
characterState = ducking; break;
}
if (pressing(foot) && !obstacle(above)) {
characterState = launching; break;
}
break;
case running:
if (obstacle(ahead)) {
characterState = standing; break;
}
if (!pressing(right) && !pressing(left)) {
characterState = standing; break;
}
if (pressing(down) && !obstacle(low)) {
characterState = rolling; break;
}
if (pressing(foot) && !obstacle(above)) {
characterState = launching; break;
}
if (pressing(hand) && obstacle(low) && !obstacle(high)) {
characterState = vaulting; break;
}
if (!obstacle(below)) {
characterState = airborne; break;
}
break;
case launching:
if (!pressing(foot)) {
characterState = airborne; break;
}
if (finished(launching)) {
characterState = airborne break;
}
// if rising and obstacle(above), stop upward momentum
// if movingForward and obstacle(ahead), stop forward momentum
if (obstacle(ledgeAhead) && pressing(hand)) {
characterState = hanging; break;
}
break;
case airborne:
// apply gravity
if (falling && obstacle(below)) {
characterState = standing; break;
}
// if rising and obstacle(above), stop upward momentum
// if movingForward and obstacle(ahead), stop forward momentum
if (obstacle(ledgeAhead) && pressing(hand)) {
characterState = hanging; break;
}
if (pressing(back) && pressing(jump) && obstacle(low)) {
characterState = wallJumping; break;
}
break;
case hanging:
if (pressing(up)) {
characterState = musclingUp; break;
}
if (!pressing(hand)) {
characterState = airborne; break;
}
break;
case musclingUp:
if (finished(musclingUp)) {
characterState = ducking; break;
}
break;
case loweringDown:
if (obstacle(below)) {
characterState = standing; break;
}
if (finished(loweringDown) {
characterState = hanging; break;
}
break;
case vaulting:
if (obstacle(ahead)) {
characterState = ducking; break;
}
if (finished(vaulting) {
if (!obstacle(above)) {
characterState = standing; break;
}
else {
characterState = ducking; break;
}
}
break;
case wallJumping:
if (finished(wallJumping)) {
characterState = airborne; break;
}
break;
default:
break;
}
Clint Bellanger intends to actually finish this game engine. No, really. He needs the material for articles at PFunked.
This article is released under the terms of the Creative Commons Share-Alike License. The source code listed above is released under the terms of the GNU Public License