---
AS3: Main
Category: Intermediate
---
--- AS3: State Machines ---
This is a state machine as it applies to "slides" or "states" of a project (or game) instead of a finite state machine detailed here.
I "creatively" call them "inifinite state machines" (though they are not actually called that) to give them some separation between the two different (though they look and work similarly) types of state machine.
So, first of all, what the hell is this and why do I need it?
If any of you come from using the Flash IDE to code in like I did (terrible idea by the way, just grab FlashDevelop since it's free) then you'll probably have used different frames for things like the menu, the game, different endings or cutscenes, etc etc.
This is the programming equivalent of that.
For example, say I have a game and I want to make a main menu. I would put all the buttons, code, etc for the main menu into what's called a "state". Then I would move on and create a sub menu like an options menu. All the buttons and code for that would then be in its own state. The same for the cutscenes, ending, and even the game (since the game would probably be one state anyway)
This massively helps keep code organized to the point that you know exactly where any part of anything would be put, since everything alike belongs in the same state.
So now we know the theory. How do we implement it? There's rare cases in which knowing the theory is enough to create the implementation, and this is one of them. For the most part, you can make it up and change it as you go. However, I will provide two examples of state machines (mine and Flixel's) and guide you through a simple one to get you started (or you can just copy them, whatever)
My state machine has some overhead to it since it uses a (fairly minimalistic) library that I created to accompany it, however I've included all the files required to run it in the zip located here. The one thing I didn't include is Starling - this version uses the framework to render the graphics, though that can be changed fairly easily.
Flixel's state machine can be found on Github and the documentation here
I will explain how to use state machines after we create our first machine. Don't worry, it's pretty simple.
The first thing we're going to want is two classes, one called Machine that extends nothing (it will be a static class), and the other called State that extends Sprite (seriously, who uses MovieClips any more?) the State class is what we will extend to make states, and the Machine class controls the construction and destruction of those states (using methods we call from within the state)
our State class should have the following public methods that take no parameters and return void: create, update, draw, and destroy.
create is called first, when the state is created. It's bad practice to put code in constructors since the debug stack generally stops at a constructor. Meaning if you step through your code, you will end at a constructor of some sort and it's better you get as much information as possible when you debug.
update is called every timer tick (whenever you decide that is) and is used to update things like x and y positions or make graphical changes of sorts. This is confusing since we have our draw method, but i'll explain that in a second.
draw is called every timer tick (using a different timer) and is used to actually represent the changes from our update function. In Flash, generally when you make a change to, say, the x or y position of a sprite it moved the graphic instantly. Using this, the graphics won't change at all until this function is called. Neat, eh?
destroy is called just before everything is removed, making sure to clean up all of the code before we move on. Things like event listeners are detached here.
finally, there's a public boolean forceUpdate that we'll want to add in. This will be explained later.
congratulations, we've now split framerate into "update" and "draw" sections and completely removed Flash's frame events entirely! Using an event listener attached to those will do nothing to our main code any more. Good, it was kinda shit anyway.
So, here's what our State class looks like now.
package {
import starling.display.Sprite;
/**
* ...
* @author egg82
*/
public class State extends Sprite {
//vars
public var forceUpdate:Boolean = false;
//constructor
public function State() {
}
//public
public function create():void {
}
public function update():void {
}
public function draw():void {
}
public function destroy():void {
}
//private
}
}
We're done with this class, you can close it. We'll now move on to our Machine class. Again, this is a static class so pretty much all of our variables and methods will be labeled static.
Variables:
stage:Stage
states:Vector.<State>
updateTimer:Timer
drawTimer:Timer
stage is the main stage. This is what we use to add our states to
states is a list of the current active states (I'll get to that in a second)
updateTimer is a timer that calls the update function
drawTimer is a timer that calls the draw function. Remember to keep these two timers separate
Methods:
Machine - constructor, takes initState:State and stage:Stage - no return
swapStates - takes newState:State and overlay:Boolean - returns void
numStates - getter, takes nothing - returns uint
removeState - takes index:uint - returns void
onUpdate - takes e:TimerEvent - returns void
onDraw - takes e:TimerEvent - returns void
swapStates does just what is says. It facilitates the move from one state to the next.
numStates returns the number of states currently active
removeState removes a state from the stack.
onUpdate is our update timer's event handler
onDraw is our draw timer's event handler
so Why is states a vector (an array with a datatype) instead of a single instance? In case we want to visually put one state on top of another (in the case of menus and such)
Can we do that? Hell yeah!
and now in the constructor of our Machine class, we'll add this
if (!initState) { //if the initState provided is null
throw new Error("initState cannot be null"); //throw an error
}
if (!stage) { //if the stage provided is null
throw new Error("stage cannot be null"); //throw an error
}
this.stage = stage; //our stage should be the stage provided. Using "this." insures we use the class-wide variable rather than the function's variable of the same name.
swapStates(initState, false); //add the first state
_updateTimer.addEventListener(TimerEvent.TIMER, onUpdate); //set the update timer event
_drawTimer.addEventListener(TimerEvent.TIMER, onDraw); //set the draw timer event
_updateTimer.start(); //start the update timer
_drawTimer.start(); //start the draw timer
Programming stuffs (tutorials and extras)
PM me (instead of MintPaw) if you're confuzzled.
thank Skaren for the sig :P