Wednesday, December 03, 2008

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.