I moved to a new URL! Check it out!

AS3 State Machine

AS3 State Machine
State Machines are the greatest thing I've ever discovered when it comes to programming games. More and more I'm beginning to realize that a lot of the structure of the tools I've used is in fact a state machine!

Finite State Machines


To find out exactly what a state machine is, check out this wikipedia article which describes them as "...an abstract machine that can be in one of a finite number of states. The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition; this is called a transition. A particular FSM is defined by a list of its states, and the triggering condition for each transition."

An example of this in action in my games are the guards in Super Ninja Slash. They have a couple of different states. Idle, Alarmed, Shooting, and Dead. That covers all their bases for their behavior.

Without using a state machine, writing code for all of that behavior can quickly become a giant mess of if statements and booleans. With a state machine, I can completely separate all the code for the guard's Idle state, the Alarmed state, Shooting, and Dead.

Source Codez


It would probably help to see my code for my state machine, so here's that:
package com.kpulv.bits 
{
import com.kpulv.Bit;
public class StateMachine extends Bit
{
/*
State Adding Template:
.addState(
function():void {

},
function():void {

},
function():void {

}
)
*/
private var states:Array = new Array();
private var state:int = -1;

private var stack:Array = new Array();
private var timers:Array = new Array();

private var STATE_BEGIN:String = "begin";
private var STATE_UPDATE:String = "update";
private var STATE_END:String = "end";

/** Trace warnings to the debug console. */
public var warnings:Boolean = false;

public function StateMachine()
{
super();
name = "StateMachine";

}

override public function update():void {
super.update();

var curState:int = -1;
/** If there is a stack, set the top element to the current state */
if (stack.length > 0) {
curState = stack[stack.length - 1];
timers[stack.length - 1] += 1;
}
else {
curState = state;
}

if (curState == -1) {
if (warnings) trace("[State Machine: No State Set]");
return;
}

if (states.length == 0) { //no states!
if (warnings) trace("[State Machine: No States]");
return;
}

if (states[curState] == null) {
if (warnings) trace("[State Machine: Invalid State]");
}

var updateFunction:Function = states[curState][STATE_UPDATE];
if (updateFunction != null) {
updateFunction.call();
}

}

public function addState(begin:Function = null, update:Function = null, end:Function = null):uint {
var obj:Object = new Object();
obj[STATE_BEGIN] = begin;
obj[STATE_UPDATE] = update;
obj[STATE_END] = end;

return states.push(obj)-1;
}

public function pushState(state:uint):void {
if (states[state]) {
stack.push(state);
timers.push(0);

if (states[state]) {
if (states[state][STATE_BEGIN]) {
var beginFunction:Function = states[state][STATE_BEGIN];
beginFunction.call();
}
}
}
else {
if (warnings) trace("[State Machine: Invalid state to push.]");
}
}

public function popState():int {
if (stack.length == 0) {
if (warnings) trace("[State Machine: Stack has nothing to pop.]");
return -1;
}

var state:uint = stack[stack.length - 1];
if (states[state]) {
if (states[state][STATE_END]) {
var endFunction:Function = states[state][STATE_END];
endFunction.call();
}
}
timers.pop();
return stack.pop();
}

public function changeState(state:uint):void {
if (stack.length > 0) {
if (warnings) trace("[State Machine: Cannot change state while in stack mode.]");
return;
}

if (this.state == state) {
return;
}

var previousState:uint = this.state;
this.state = state;

if (!states[state]) {
if (warnings) trace("[State Machine: Invalid State]");
return;
}

timer = 0;

//call end on current state
if (states[previousState]) {
var endFunction:Function = states[previousState][STATE_END];
if (endFunction != null) {
//trace("[State Machine: Calling End]");
endFunction.call();
}
}

//call begin on new state
var beginFunction:Function = states[state][STATE_BEGIN];
if (beginFunction != null) {
//trace("[State Machine: Calling Begin]");
beginFunction.call();
}
}

public function resetState():void {
timer = 0;

var beginFunction:Function = states[state][STATE_BEGIN];
if (beginFunction != null) {
beginFunction.call();
}
}

public function get currentState():uint {
return state;
}

public function get stateTimer():uint {
if (stack.length > 0) {
return timers[stack.length - 1];
}
return timer;
}

}

}
I should note that it extends a class called "Bit" which is just a component base class that I use for my Entities. I can add Bits to Entities and they are automatically updated when the Entity updates. Here's Bit:
package com.kpulv {
public class Bit {

public var parent:Actor;

public var name:String;

public var timer:uint = 0;

public var active:Boolean = true;

public function Bit() {
name = String(KP.getClass(this));
}

public function update():void {
if (!active) return;

timer++;
}

public function render():void {

}

public function renderDebug():void {

}

public function added():void {

}

public function removed():void {

}

public function toString():String {
return "[Bit " + name + "]";
}
}
}

Implementation


The basic set up of my State Machine is that sets of functions are added to it as the States. Each State just has three functions: begin, update, and exit. When a state is added to the machine, it returns an int which is the reference to that state. So usually the code for adding a state can look like this:
//setting up a state
var STATE_IDLE:uint;
sm:StateMachine = new StateMachine();
STATE_IDLE = sm.addState(enterFunction, updateFunction, exitFunction);
sm.changeState(STATE_IDLE);

// later, in the update function
sm.update();

// later in the class
protected function updateFunction():void {
//called every update of state machine
}
protected function enterFunction():void {
//called when the state machine enters this state
}
protected function exitFunction():void {
//called when the state machine is leaving this state
}

Super Ninja Guard


This style of coding made creating the guard in Super Ninja Slash really easy. I can think about the guard acting in four different distinct ways. When he is idle, he just marches left and right and turns around when he gets to a wall, or when he reaches a ledge. He can also spot the player, or hear the player in this state.

Image


If the guard hears the player, he will change to his STATE_ALARMED state. In this state, the guard will change his walk speed and move really fast in the direction he heard the sound. When he reaches a wall or an edge, he will stop moving and after a certain amount of time of being stopped, he'll return to his STATE_IDLE state. The guard can also be re-alarmed during this state, so if the player keeps making sounds, the guard will continuously be alarmed.

Image


While in the idle state, or the alarmed state, if the guard spots the player with his flashlight, he will enter the STATE_SHOOT state. This state is pretty simple and just has the guard shoot four times at the player (or sometimes another guard's flying dead body.) After the four shots have been fired, the guard will return to the STATE_IDLE state.

Image


The last state is STATE_RAGDOLL or the dead state. This is after the player has slashed the guard with the sword. The guard can never leave this state, and all it does really is turn off all the movement of the guard, switch the animations, and change the guard's physics a little bit to make him a little bit more bouncy when he flies around from gunfire or slashes.

Image


Here's a quick attempt to map out the basic logic behind this system:

Image


The guard is pretty stupid, and if I wanted to or had more time (this was a 48 hour game jam game) I could make him way more complicated and add many more states. Maybe after he's done shooting he would enter a state that would cause him to investigate the area the player disappeared into more, or he would enter a state that would cause him to call for help and surrounding guards would enter a state that would lead them to the guard that is calling for help. When I have a state machine that can organize all the code for these different behaviors it becomes way easier to ramp up the complexity without adding too much messy code.

Some More Thoughts


With a state machine all of this code is completely separated, and it's much easier on my brain to think about the different phases of an enemy's behavior. The applications of the finite state machine go way beyond just enemies though. My entire game manager class in Offspring Fling was a huge state machine of different states like intro, paused, gameplay, cleared level, restart level, go to next level, go back to title screen, etc. Also when I was working on menus for my Global Game Jam game I made them all into state machines to handle when they are arriving on the screen, being used, and being dismissed.

Another quick thing to note is that the state machine I posted also supports a stack of states. I can push and pop states and the state machine will remember the whole stack, but only update the state on top. This was inspired by this this article about RPG design. I haven't really fully tested this yet but so far it works okay. Feel free to take my code and make it way better for your needs!

Comments

fedyfausto
fedyfausto
It's so diffiuclt to understund :0 i use switch case for check status with string :3
Posted May 14th 2013 2:41 PM
Kyle
Kyle
A switch is a very simple way to do this, but then it's more difficult to manage the onEnter, and onEnd functions, as well as use transition functions.
Posted May 14th 2013 3:21 PM
Oliver
Oliver
That's a really elegant implementation in AS3. Thanks for sharing!
Posted December 24th 2013 2:36 AM
new comment!

Post your comment!

Name
Email
Comment