Gaming Your Way

May contain nuts.

States machines in actionscript, nice and easy

December already, it really does fly.

I was chatting with our mate Jeff @ 8bitRocket the other week about function pointers / state machines and I thought it may be useful to quickly write something up about it here, as so much of my code relies on them and they do make life easier.

private var mainFunc:Function;

That's pretty much it.

I'm guessing all your routines have some sort of mainloop. Either you're old school and that's running on an enterFrame on each mc you're moving about or it's a public method in an object which you loop through and call every frame.

Let's take a space invaders mainloop as an example ( Psuedo-code ahead )

function mainloop():void{
  if(thisInvaderIsDeadFlag==true){
    return;
  }
  if(areWeDyingFlag==true){
    if(sprite.currentframe==endOfExplosionsFrame){
      thisInvaderIsDeadFlag=true;
    }
    return;
  }

  moveInvader();
  testForShootingAtPlayer();
  testForBeingHitByPlayersBullet();
}


That should hopefully be straight forward enough. In real life we wouldn't want to be testing for thisInvaderIsDeadFlag every time, we'd just remove this invader from our array of active ones, but this is just an example.

Now check this out,

mainFunc=mainloop_normal;

function mainloop():void{
    mainFunc();
}

function mainloop_normal():void{
    moveInvader();
    testForShootingAtPlayer();
    testForBeingHitByPlayersBullet();
}

Here we've set up our function pointer to point to mainloop_normal, and then we just call that one method in our mainloop() method. Cool so far ?

Now we're not checking to see if the invader is dead, or if it's exploding. That's good, because by default the vast majority of the time neither of those are going to be true, so it's a waste to check ( It's like waking up every morning and seeing if it's your birthday ).

At the moment it's more of a function pointer than a state machine as such, so next up is why doing it this way is so sexy...

function testForBeingHitByPlayersBullet():void{
    if(playerBullet.hitTest(thisInvaderSprite)){
       thisInvaderSprite.gotoAndPlay("firstExplodingFrame");
       mainFunc=mainloop_waitingToDie;
    }
}

Here's our testForBeingHitByPlayerBullet method from the main loop. Imagine that's just doing a hitTest, the invader to the player bullet. Bang, it's a collision, but instead of having to set our areWeDyingFlag from the original example, we just change the state machine.

function mainloop_waitingToDie():void{
  if(sprite.currentframe==endOfExplosionsFrame){
//Kill this invader totally, ie remove the mc
      mainFunc=null;
  }
}

In effect, no extra checks are needed every frame, we're only running what we need. We've got the advantage of a slight performance boost, but far more useful than that is the flexibility this gives us.

Say for example you want your invader to teleport in now, at the start instead of:

mainFunc=mainloop_normal;

we can now alter it to,

mainFunc=mainloop_waitingForInvaderToTeleportIn;

And run the code there waiting for your cool teleport effect to finish, and then just alter the mainFunc to carry on with our flow.

Running your code this way means you can chop and change states without having to have lots of extra conditionals in your code ( If the player is teleporting in, but that teleport tween isn't at frame 10 yet, then don't test for collisions with the player bullet etc. etc. ).

Any questions just hit that comment button,

Squize.

Comments (8) -

  • ickydime

    12/3/2008 2:38:06 PM |

    Very slick and easy to understand.  Thanks for the post.

  • Squize

    12/3/2008 6:59:32 PM |

    Thanks for the feedback mate, hope you found it some use.

    Things like state machines always seem to have this added layer of complication to them, as if they're this big clever thing, and they're really not ( I just can't stand pretentiousness with regard code. Writing code is tricky enough as it is, without filling it with jargon just to look clever  ).

  • Jeff Fulton

    12/3/2008 10:48:11 PM |

    Squize, I have your version of this working in my new Flex game control and it is silky smooth! I love it, nice idea.

  • tonypa

    12/10/2008 8:45:06 AM |

    Is the current loop always run through even when you change the state in the middle? I mean suppose you have:

    function mainloop_normal():void{
        a();
        b();
        c();
    }

    and in the a() you change

    mainFunc = mainloop_waitingForSomething;

    are the b() and c() still being run once? And is there a way to avoid that?

  • Squize

    12/10/2008 10:55:34 AM |

    Hey T.

    Yes in your example the b() and c() will still run. There are ways around that though, say for example you really don't want b() and c() to run if a() has generated a certain rule, the simple way would be

    b();
    c();
    a();

    I know that may sound flippant, but the ordering is quite important. In my baddie routines for example I always do the collision check last, so if the baddie has been killed by the player then nothing else runs after that in the same frame.

    You could also use a():Boolean eg
    if(a()==true){
    return;
    }
    b();
    c();

    Or even I guess
    a();
    if(mainFunc!=mainloop_normal){
    return;
    }
    b();
    c();

    So there are lots of ways around it. You could even get a() to call both b() & c() itself.
    Unless b() or c() can alter the mainFunc var you should be pretty much ok running anything for just 1 frame, although it does all depend what you're doing.

  • Squize

    12/15/2008 8:18:29 PM |

    Hi mate,

    I think the only advantage of the way I've posted, is that it's much more "low-level" for want of a better term.
    From the quick read of that link you posted, it looks a really all-encompassing way to do this, with requests for new states etc. and self managing.

    If that's your thing, and you're doing an app / air widget ( Or maybe even a point and click ? ), then yeah it looks a million times better / more structured than the concept I've outlined here.

    If on the other hand you're doing a straight forward game where speed is everything, then I think the approach above is quick and dirty and does what you need.

    Thanks for the link though, it's an interesting read, and I'll def. go back to it when I have more time.

  • Colm

    1/19/2009 2:51:24 PM |

    Oh this is nice; just as simple as it needs to be!

    I'll be using the concept for our game loop - thanks :)

Comments are closed