00:00
00:00
Newgrounds Background Image Theme

jdsdfeasdfr4e32qws just joined the crew!

We need you on the team, too.

Support Newgrounds and get tons of perks for just $2.99!

Create a Free Account and then..

Become a Supporter!

AS3: State Machines

1,432 Views | 10 Replies
New Topic Respond to this Topic

---
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

BBS Signature

Response to AS3: State Machines 2015-01-02 18:09:46 (edited 2015-01-02 18:22:15)


and now our swapStates function:

var oldState:State; //for removing the old state if we need to

if (!newState) { //if the newState is null
	return; //do nothing
}

if (!overlay) { //if the new state isn't an overlay
	oldState = states.shift(); //grab the top state from the stack
	if (oldState) { //if there's something there
		oldState.destroy(); //destroy the old state
		stage.removeChild(oldState); //remove the old state
	}
}

states.unshift(newState); //add the new state to the top of the stack
stage.addChild(states[0]); //add the new state to the stage
states[0].create(); //create the new state
states[0].update(); //update the new state
states[0].draw(); //draw the new state

our numStates fucntion is just this

return states.length as uint;

and our removeState

var state:State; //the old state

if (index < states.length) { //if the index specified is within range (so no null errors)
	state = states.splice(index, 1)[0]; //grab the state from the stack
	state.destroy(); //destroy the state
	stage.removeChild(state); //remove the state from the stage
}

onUpdate is the real meat of the code

if (!updateTimer.delay) { //if the framerate is zero
	return; //do nothing. This effectively pauses the state
}

for (var i:uint = 0; i < states.length; i++) { //iterate through the state stack
	if (i == 0 || states[i].forceUpdate) { //if it's the top state or the state's forceUpdate boolean is true
		states[i].update(); //update the state
	}
}

Here's where we use our forceUpdate boolean. Basically the code only updates the top state on the stack, but if you want it to update specific states underneath you simply set the forceUpdate to true for those states.

finally, the onDraw function is quite similar to the onUpdate

if (!updateTimer.delay) { //if the framerate is zero
	return; //do nothing. This effectively pauses the state
}

for (var i:uint = 0; i < states.length; i++) { //iterate through the state stack
	if (i == 0 || states[i].forceUpdate) { //if it's the top state or the state's forceUpdate boolean is true
		states[i].draw(); //update the state
	}
}

As you can see, it's pretty much the same function except we're calling draw instead of update. The reason we keep these separate is because we don't want our update logic blocking the same thread as the draw. We keep a consistent framerate even if whatever we're updating has some trouble keeping up.

Our finished Machine class:

package {
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	import flash.display.Stage;
	
	/**
	 * ...
	 * @author egg82
	 */
	
	public class Machine {
		//vars
		private static var stage:Stage;
		private static var states:Vector.<State> = new Vector.<State>();
		private static var updateTimer:Timer = new Timer((1.0 / 60) * 1000.0); //1 / 60 * 1000 = 60 FPS, similarly 1 / 30 * 1000 = 30 FPS and 1 / 24 * 1000 = 24 FPS
		private static var drawTimer:Timer = new Timer((1.0 / 60) * 1000.0);
		
		//constructor
		public function Machine(initState:State, stage:Stage) {
			if (!initState) {
				throw new Error("initState cannot be null");
			}
			if (!stage) {
				throw new Error("stage cannot be null");
			}
			
			this.stage = stage;
			
			swapStates(initState, false);
			updateTimer.addEventListener(TimerEvent.TIMER, onUpdate);
			drawTimer.addEventListener(TimerEvent.TIMER, onDraw);
			updateTimer.start();
			drawTimer.start();
		}
		
		//public
		public static function swapStates(newState:State, overlay:Boolean):void {
			var oldState:State;
			
			if (!newState) {
				return;
			}
			
			if (!overlay) {
				oldState = states.shift();
				if (oldState) {
					oldState.destroy();
					stage.removeChild(oldState);
				}
			}
			
			states.unshift(newState);
			stage.addChild(states[0]);
			states[0].create();
			states[0].update();
			states[0].draw();
		}
		public static function get numStates():uint {
			return states.length as uint;
		}
		public static function removeState(index:uint):void {
			var state:State;
			
			if (index < states.length) {
				state = states.splice(index, 1)[0];
				state.destroy();
				stage.removeChild(state);
			}
		}
		
		//private
		private static function onUpdate(e:TimerEvent):void {
			if (!updateTimer.delay) {
				return;
			}
			
			for (var i:uint = 0; i < states.length; i++) {
				if (i == 0 || states[i].forceUpdate) {
					states[i].update();
				}
			}
		}
		private static function onDraw(e:TimerEvent):void {
			if (!drawTimer.delay) {
				return;
			}
			
			for (var i:uint = 0; i < states.length; i++) {
				if (i == 0 || states[i].forceUpdate) {
					states[i].draw();
				}
			}
		}
	}
}

now how do we use this? In the Document Class (usually Main) we'll add this:

new Machine(new InitState(), stage);

Which tells our machine the first state should be one called InitState (we'll create that in a second) and our stage is Flash's stage.

now make a new class and name it InitState, then have it extend our State class. It should look like this:

package {
	
	/**
	 * ...
	 * @author egg82
	 */
	
	public class InitState extends State {
		//vars
		
		//constructor
		public function InitState() {
			
		}
		
		//public
		
		
		//private
		
	}
}

Now if we override (make use of) our functions we can start doing cool stuff!

package {
	
	/**
	 * ...
	 * @author egg82
	 */
	
	public class InitState extends State {
		//vars
		private var step:uint = 100;
		
		//constructor
		public function InitState() {
			
		}
		
		//public
		override public function create():void {
			trace("created");
		}
		override public function update():void {
			trace("updated");
			step--;
			if (step == 0) {
				Machine.swapStates(new MenuState(), false);
			}
		}
		override public function draw():void {
			trace("drawn");
		}
		override public function destroy():void {
			trace("destroyed");
		}
		
		
		//private
		
	}
}

There we go, you now have your very own state machine and you (hopefully) know how states work! Keep your code organized, it's your best tool.


Programming stuffs (tutorials and extras)

PM me (instead of MintPaw) if you're confuzzled.

thank Skaren for the sig :P

BBS Signature

Response to AS3: State Machines 2015-01-02 18:35:01 (edited 2015-01-02 18:35:34)


I can vouch 100% that state machines make code much more organized. Coding is almost too easy now. I made a fsm based on a stack, which is what your overlay param does. My interface methods are init, destroy, enter, exit, suspend (for stack fsm), revive (stack fsm), and update (render not needed).

Also, @liljim @ tagging looks like it happens within code tags, at the very least it linkified @author in his code.
Edit: the linking is a new feature, much nicer.

Response to AS3: State Machines 2015-01-03 03:49:52


Not a bad tutorial, but it doesn't make sense for your Machine class to have an instance constructor since all of its members are static, so the created instance will be useless. It would be more fitting to have a static function do the same thing (and call it "prepare" or "init" or something like that).

At 1/2/15 06:09 PM, egg82 wrote: The first thing we're going to want is two classes, one called Machine that extends nothing

This is a bit pedantic, but for the sake of clarity and not spreading misinformation: you can't extend nothing in AS3; all classes have Object as their base class (excluding Object, of course) and it will be extended even if you don't explicitly tell your class to extend anything, so your Machine class is extending Object.

Pedantic, like I said, but, hey, knowledge is power.

Response to AS3: State Machines 2015-01-03 04:44:33


At 1/2/15 06:35 PM, MSGhero wrote: Also, @liljim @ tagging looks like it happens within code tags

Sorry about that.

Response to AS3: State Machines 2015-01-03 10:36:24


At 1/3/15 03:49 AM, Diki wrote: Not a bad tutorial, but it doesn't make sense for your Machine class to have an instance constructor since all of its members are static, so the created instance will be useless. It would be more fitting to have a static function do the same thing (and call it "prepare" or "init" or something like that).

I agree, kinda. I like having the constructors because the created object is destroyed immediately and you don't use a function initializing it. It doesn't really hurt anything and can possibly be more confusing if you don't stick with that pattern on a consistent basis, but I like it.

At 1/2/15 06:09 PM, egg82 wrote:
Pedantic, like I said, but, hey, knowledge is power.

I thought about including that information, but then it would just be confusing to say "extend Object" and have my example extend nothing. Looking back, I probably should have put a footnote in or something.


Programming stuffs (tutorials and extras)

PM me (instead of MintPaw) if you're confuzzled.

thank Skaren for the sig :P

BBS Signature

Response to AS3: State Machines 2015-01-03 10:53:33


At 1/3/15 10:36 AM, egg82 wrote: I like having the constructors because the created object is destroyed immediately and you don't use a function initializing it.

But you are using a function: the constructor. No matter which way you do it you are going to be calling a function.

At 1/3/15 10:36 AM, egg82 wrote: It doesn't really hurt anything and can possibly be more confusing if you don't stick with that pattern on a consistent basis, but I like it.

It doesn't hurt anything, no, but why initialise an object if you're not going to use it? It just doesn't make any sense and it's less clear what the class's intended use-case is.

Response to AS3: State Machines 2015-01-03 11:26:30


At 1/3/15 10:53 AM, Diki wrote: But you are using a function: the constructor. No matter which way you do it you are going to be calling a function.

Meaning not creating a new function for the initialization and instead using one that's already there automatically.

At 1/3/15 10:36 AM, egg82 wrote:
It doesn't hurt anything, no, but why initialise an object if you're not going to use it? It just doesn't make any sense and it's less clear what the class's intended use-case is.

it would be to set the variables and call the required functions, like you would an init function. You can either create a new init function or use the constructor or just use a singleton, it's all valid. I just like the constructor way because it feels cleaner to me.


Programming stuffs (tutorials and extras)

PM me (instead of MintPaw) if you're confuzzled.

thank Skaren for the sig :P

BBS Signature

Response to AS3: State Machines 2015-01-04 18:28:47


Correct me if I'm wrong, but sisn't this just a framework for basic game components? I know this guide is for people who are transitioning from AS2 but in order to code any kind of game (without the aid of flash ide) you need to use some variation of this machine and state abstraction? Since I have no formal training in game development, this has been the general idea I've been using to code games but I haven't been able to find any better way (yet).


I'm a noob.

Response to AS3: State Machines 2015-01-05 00:18:28


i can also vouch that state machines help A LOT. i do something pretty similar, except i use 2 classes:
the first one is an interface named IState (with the same layout as your State class);
the second is a manager class with state-change and transitioning functions.
it's made things a whoooole lot easier for me :>

very nice tutorial btw!

Response to AS3: State Machines 2015-01-05 01:02:20


At 1/4/15 06:28 PM, CzeryWassierSwizier wrote: Correct me if I'm wrong, but sisn't this just a framework for basic game components?

It's pretty basic, yeah. I, however, never learned a lot of design patterns early on because I simply never knew they existed and they all looked terrifying to me when I finally did learn. This guide is to help get the information out there (the fact that these exist) and hopefully make it seem less scary than it is.

I haven't been able to find any better way (yet).

There's different design patterns, but I like the state machine the best so far. I dunno, maybe I'll learn something I like better one day. Maybe you will, too.


Programming stuffs (tutorials and extras)

PM me (instead of MintPaw) if you're confuzzled.

thank Skaren for the sig :P

BBS Signature