A Finite State Machine (FSM) is nothing but a system to manage the various states that a particular machine (the clown in our case) can be in. You can create an FSM to describe the various stages of an entity's lifecycle. The term entity could be anything from the main character in a game where the states would be walking, running, jumping, shooting, dying, and so on. Similarly, an entity could be the game itself where the states would be: game world creation, game world update, collision detection, level completion, game over, and so on.
A common way to represent the states within an FSM is to enumerate them, which in the case of our clown's states is the following enum that you will find in the GameGlobals.h
file:
enum EClownState { E_CLOWN_NONE = 0, E_CLOWN_UP, E_CLOWN_DOWN, E_CLOWN_BOUNCE, E_CLOWN_ROCKET, E_CLOWN_BALLOON, };
For this simple game, we have just five states the clown can be in, excluding the E_CLOWN_NONE
state of course. The Clown
class will have a state_
property of the type EClownState
. We will now look at the SetState
function of the Clown
class:
void Clown::SetState(EClownState state) { // only accept a change in state if(state_ == state) return; state_ = state; // call respective state based action switch(state_) { case E_CLOWN_UP: StartGoingUp(); break; case E_CLOWN_DOWN: StartComingDown(); break; case E_CLOWN_BOUNCE: StartBounce(); break; case E_CLOWN_ROCKET: StartRocket(); break; case E_CLOWN_BALLOON: StartBalloon(); break; } }
We keep the SetState
function straightforward. We perform processing only if there is a change in state. Why is this of importance you wonder? Well, consider that you create a new CCAnimate
object to play a walking animation, as the character enters into the walking state every time the player presses the right arrow key. This way, you will be repeatedly creating a new CCAnimate
object every time the right arrow key's event is fired!
Next, we write a switch case structure that handles each state and calls the respective function. The StartGoingUp
, StartComingDown
, and StartBounce
functions change the current display frame for the clown, so we'll skip straight to the StartRocket
function:
void Clown::StartRocket() { setDisplayFrame(CCSpriteFrameCache::sharedSpriteFrameCache()-> spriteFrameByName("cjroket.png")); // unschedule any previously scheduled selectors...possibly by another rocket/balloon unschedule(schedule_selector(Clown::FinishRocketBalloon)); // stay in this state for some time scheduleOnce(schedule_selector(Clown::FinishRocketBalloon), ROCKET_DURATION); // no gravity while aboard a bottle rocket body_->SetGravityScale(0.0f); // decently high velocity while aboard a bottle rocket body_->SetLinearVelocity(b2Vec2(0.0f, 30.0f)); // create neat jet stream for the rocket rocket_trail_ = CCParticleSystemQuad::create("explosion.plist"); rocket_trail_->setDuration(-1); rocket_trail_->setPositionType(kCCPositionTypeRelative); game_world_->game_object_layer_->addChild(rocket_trail_); SOUND_ENGINE->playEffect("bottle_rocket.wav"); }
We begin by changing the current display frame. We then unschedule any previously scheduled selector and schedule a new one with the respective duration.
The next part is interesting, since we alter the physics of the clown's body. When the clown enters in to this state, we want him to shoot upwards at a constant velocity unaffected by gravity, so we do just that. We set the gravity scale to 0.0f, which means that the clown's body will not be influenced by the force of gravity and then set a decently high linear velocity pointing straight up.
That last bit of code will simply add a particle system to represent the jet stream of the rocket. I wonder how that clown holds on to such a powerful rocket without burning himself! The code for the StartBalloon
function is similar to the StartRocket
function, but the linear velocity we set is much lesser because a balloon tends to be slower than a rocket. Also, it doesn't have a jet stream so we don't add a particle system either.
However, unlike the rocket, the user can control the clown's horizontal movement via the accelerometer of course! You can find the relevant logic in the didAccelerate
function inside GameWorld.cpp
. So, let's discuss the callback that both these functions have scheduled, the FinishRocketBalloon
function:
void Clown::FinishRocketBalloon(float dt) { // after rocket/balloon, clown will be moving upwards SetState(E_CLOWN_UP); // resume normal gravity body_->SetGravityScale(1.0f); // remove any rocket jet stream if it exists if(rocket_trail_) { rocket_trail_->removeFromParentAndCleanup(true); rocket_trail_ = NULL; } }
Once both the states have finished, we set the clown's state to be moving upwards and resume normal gravity scale. We also remove any glorious particle systems that we may have added. Voila, you've implemented a simple yet elegant FSM.
18.191.168.203