Advertisement
  1. Code
  2. Coding Fundamentals
  3. Game Development

Finite-State Machines: Theory and Implementation

Scroll to top

A finite-state machine is a model used to represent and control execution flow. It is perfect for implementing AI in games, producing great results without a complex code. This tutorial describes the theory, implementation and use of simple and stack-based finite-state machines.

All icons made by Lorc, and available on http://game-icons.net.

Note: Although this tutorial is written using AS3 and Flash, you should be able to use the same techniques and concepts in almost any game development environment.


What Is a Finite-State Machine?

A finite-state machine, or FSM for short, is a model of computation based on a hypothetical machine made of one or more states. Only a single state can be active at the same time, so the machine must transition from one state to another in order to perform different actions.

FSMs are commonly used to organize and represent an execution flow, which is useful to implement AI in games. The "brain" of an enemy, for instance, can be implemented using a FSM: every state represents an action, such as attack or evade:

FSM representing the brain of an enemy.

An FSM can be represented by a graph, where the nodes are the states and the edges are the transitions. Each edge has a label informing when the transition should happen, like the player is near label in the figure above, which indicates that the machine will transition from wander to attack if the player is near.


Planning States and Their Transitions

The implementation of a FSM begins with the states and transitions it will have. Imagine the following FSM, representing the brain of an ant carrying leaves home:

FSM representing the brain of an ant.

The starting point is the find leaf state, which will remain active until the ant finds the leaf. When that happens, the current state is transitioned to go home, which remains active until the ant gets home. When the ant finally arrives home, the active state becomes find leaf again, so the ant repeats its journey.

If the active state is find leaf and the mouse cursor approaches the ant, there is a transition to the run away state. While that state is active, the ant will run away from the mouse cursor. When the cursor is not a threat anymore, there is a transition back to the find leaf state.

Since there are transitions connecting find leaf and run away, the ant will always run away from the mouse cursor when it approaches as long as the ant is finding the leaf. That will not happen if the active state is go home (check out the figure below). In that case the ant will walk home fearlessly, only transitioning to the find leaf state when it arrives home.

FSM representing the brain of an ant. Notice the lack of a transition between run away and go home.

Implementing a FSM

An FSM can be implemented and encapsulated in a single class, named FSM for instance. The idea is to implement every state as a function or method, using a property called activeState in the class to determine which state is active:

1
public class FSM {
2
	private var activeState :Function; // points to the currently active state function

3
4
	public function FSM() {
5
	}
6
7
	public function setState(state :Function) :void {
8
		activeState = state;
9
	}
10
11
	public function update() :void {
12
		if (activeState != null) {
13
			activeState();
14
		}
15
	}
16
}

Since every state is a function, while an specific state is active the function representing that state will be invoked every game update. The activeState property is a pointer to a function, so it will point to the active state's function.

The update() method of the FSM class must be invoked every game frame, so that it can call the function pointed by the activeState property. That call will update the actions of the currently active state.

The setState() method will transition the FSM to a new state by pointing the activeState property to a new state function. The state function doesn't have to be a member of FSM; it can belong to another class, which makes the FSM class more generic and reusable.


Using a FSM

Using the FSM class already described, it's time to implement the "brain" of a character. The previously explained ant will be used and controlled by an FSM. The following is a representation of states and transitions, focusing on the code:

FSM of ant brain with focus on the code.

The ant is represented by the Ant class, which has a property named brain and a method for each state. The brain property is an instance of the FSM class:

1
public class Ant
2
{
3
	public var position   :Vector3D;
4
	public var velocity   :Vector3D;
5
	public var brain      :FSM;
6
7
	public function Ant(posX :Number, posY :Number) {
8
		position 	= new Vector3D(posX, posY);
9
		velocity 	= new Vector3D( -1, -1);
10
		brain		= new FSM();
11
12
		// Tell the brain to start looking for the leaf.

13
		brain.setState(findLeaf);
14
	}
15
16
	/**

17
	 * The "findLeaf" state.

18
	 * It makes the ant move towards the leaf.

19
	 */
20
	public function findLeaf() :void {
21
	}
22
23
	/**

24
	 * The "goHome" state.

25
	 * It makes the ant move towards its home.

26
	 */
27
	public function goHome() :void {
28
	}
29
30
	/**

31
	 * The "runAway" state.

32
	 * It makes the ant run away from the mouse cursor.

33
	 */
34
	public function runAway() :void {
35
	}
36
37
	public function update():void {
38
		// Update the FSM controlling the "brain". It will invoke the currently

39
		// active state function: findLeaf(), goHome() or runAway().

40
		brain.update();
41
42
		// Apply the velocity vector to the position, making the ant move.

43
		moveBasedOnVelocity();
44
	}
45
46
	(...)
47
}

The Ant class also has a velocity and a position property, both used to calculate the movement using  Euler integration. The update() method is called every game frame, so it will update the FSM.

To keep things simple, the code used to move the ant, such as moveBasedOnVelocity(), will be omitted. More info on that can be found in the Understanding Steering Behaviors series.

Below is the implementation of each state, starting with findLeaf(), the state responsible for guiding the ant to the leaf position:

1
public function findLeaf() :void {
2
	// Move the ant towards the leaf.

3
	velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y);
4
5
	if (distance(Game.instance.leaf, this) <= 10) {
6
		// The ant is extremelly close to the leaf, it's time

7
		// to go home.

8
		brain.setState(goHome);
9
	}
10
11
	if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
12
		// Mouse cursor is threatening us. Let's run away!

13
		// It will make the brain start calling runAway() from

14
		// now on.

15
		brain.setState(runAway);
16
	}
17
}

The goHome() state, used to guide the ant home:

1
public function goHome() :void {
2
	// Move the ant towards home

3
	velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y);
4
5
	if (distance(Game.instance.home, this) <= 10) {
6
		// The ant is home, let's find the leaf again.

7
		brain.setState(findLeaf);
8
	}
9
}

Finally, the runAway() state, used to make the ant flee the mouse cursor:

1
public function runAway() :void {
2
	// Move the ant away from the mouse cursor

3
	velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y);
4
5
	// Is the mouse cursor still close?

6
	if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) {
7
		// No, the mouse cursor has gone away. Let's go back looking for the leaf.

8
		brain.setState(findLeaf);
9
	}
10
}

The result is an ant controlled by a FSM "brain":

Ant controlled by a FSM. Move the mouse cursor to threaten the ant.

Improving the Flow: Stack-Based FSM

Imagine that the ant also needs to run away from the mouse cursor when it is going home. The FSM can be updated to the following:

Ant FSM updated with new transitions.

It seems a trivial modification, the addition of a new transition, but it creates a problem: if the current state is run away and the mouse cursor is not near anymore, what state should the ant transition to: go home or find leaf?

The solution for that problem is a stack-based FSM. Unlike our existing FSM, a stack-based FSM uses a stack to control states. The top of the stack contains the active state; transitions are handled by pushing or popping states from the stack:

Stack-based FSM

The currently active state can decide what to do during a transition:

Transitions in a stack-based FSM: pop itself + push new; pop itself; push new.

It can pop itself from the stack and push another state, which means a full transition (just like the simple FSM was doing). It can pop itself from the stack, which means the current state is complete and the next state in the stack should become active. Finally, it can just push a new state, which means the currently active state will change for a while, but when it pops itself from the stack, the previously active state will take over again.


Implementing a Stack-Based FSM

A stack-based FSM can be implemented using the same approach as before, but this time using an array of function pointers to control the stack. The activeState property is no longer needed, since the top of the stack already points to the currently active state:

1
public class StackFSM {
2
	private var stack :Array;
3
4
	public function StackFSM() {
5
		this.stack = new Array();
6
	}
7
8
	public function update() :void {
9
		var currentStateFunction :Function = getCurrentState();
10
11
		if (currentStateFunction != null) {
12
			currentStateFunction();
13
		}
14
	}
15
16
	public function popState() :Function {
17
		return stack.pop();
18
	}
19
20
	public function pushState(state :Function) :void {
21
		if (getCurrentState() != state) {
22
			stack.push(state);
23
		}
24
	}
25
26
	public function getCurrentState() :Function {
27
		return stack.length > 0 ? stack[stack.length - 1] : null;
28
	}
29
}

The setState() method was replaced with two new methods: pushState() and popState()pushState() adds a new state to the top of the stack, while popState() removes the state at the top of the stack. Both methods automatically transition the machine to a new state, since they change the top of the stack.


Using a Stack-Based FSM

When using a stack-based FSM, it's important to note that each state is responsible for popping itself from the stack. Usually a state removes itself from the stack when it is no longer needed, like if attack() is active but the target just died.

Using the ant example, just a few changes are required to adapt the code to use a stack-based FSM. The problem of not knowing the state to transition to is now seamlessly solved thanks to the very nature of stack-based FSM:

1
public class Ant {
2
	(...)
3
	public var brain :StackFSM;
4
5
	public function Ant(posX :Number, posY :Number) {
6
		(...)
7
		brain = new StackFSM();
8
9
		// Tell the brain to start looking for the leaf.

10
		brain.pushState(findLeaf);
11
12
		(...)
13
	}
14
15
	/**

16
	 * The "findLeaf" state.

17
	 * It makes the ant move towards the leaf.

18
	 */
19
	public function findLeaf() :void {
20
		// Move the ant towards the leaf.

21
		velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y);
22
23
		if (distance(Game.instance.leaf, this) <= 10) {
24
			// The ant is extremelly close to the leaf, it's time

25
			// to go home.

26
			brain.popState(); // removes "findLeaf" from the stack.

27
			brain.pushState(goHome); // push "goHome" state, making it the active state.

28
		}
29
30
		if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
31
			// Mouse cursor is threatening us. Let's run away!

32
			// The "runAway" state is pushed on top of "findLeaf", which means

33
			// the "findLeaf" state will be active again when "runAway" ends.

34
			brain.pushState(runAway);
35
		}
36
	}
37
38
	/**

39
	 * The "goHome" state.

40
	 * It makes the ant move towards its home.

41
	 */
42
	public function goHome() :void {
43
		// Move the ant towards home

44
		velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y);
45
		
46
		if (distance(Game.instance.home, this) <= 10) {
47
			// The ant is home, let's find the leaf again.

48
			brain.popState(); // removes "goHome" from the stack.

49
			brain.pushState(findLeaf); // push "findLeaf" state, making it the active state

50
		}
51
		
52
		if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
53
			// Mouse cursor is threatening us. Let's run away!

54
			// The "runAway" state is pushed on top of "goHome", which means

55
			// the "goHome" state will be active again when "runAway" ends.

56
			brain.pushState(runAway);
57
		}
58
	}
59
	
60
	/**

61
	 * The "runAway" state.

62
	 * It makes the ant run away from the mouse cursor.

63
	 */
64
	public function runAway() :void {
65
		// Move the ant away from the mouse cursor

66
		velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y);
67
		
68
		// Is the mouse cursor still close?

69
		if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) {
70
			// No, the mouse cursor has gone away. Let's go back to the previously

71
			// active state.

72
			brain.popState();
73
		}
74
	}
75
	(...)
76
}

The result is an ant able to run away from the mouse cursor, transitioning back to the previously active state before the threat:

Ant controlled by a stack-based FSM. Move the mouse cursor to threaten the ant.

Conclusion

Finite-state machines are useful to implement AI logic in games. They can be easily represented using a graph, which allows a developer to see the big picture, tweaking and optimizing the final result.

The implementation of a FSM using functions or methods to represent states is simple, but powerful. Even more complex results can be achieved using a stack-based FSM, which ensures a manageable and concise execution flow without negatively impacting the code. It's time to make all your game enemies smarter using a FSM!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.