The state pattern allows an object to alter its behavior when its internal state changes.
Let’s make a game where the heroine jumps to some input.
void Heroine::handleInput(Input input) {
if (input == PRESS_B) {
yVelocity_ = JUMP_VELOCITY;
(IMAGE_JUMP);
setGraphics}
}
Spot the bug?
You can keep jumping infinitely while you’re in the air.
Add an isJumping_
boolean to address that.
void Heroine::handleInput(Input input) {
if (input == PRESS_B) {
if (!isJumping_) {
isJumping_ = true;
yVelocity_ = JUMP_VELOCITY;
(IMAGE_JUMP);
setGraphics}
}
}
Next, we want her to be able to duck.
void Heroine::handleInput(Input input) {
if (input == PRESS_B) {
// Jump if not jumping...
}
else if (input == PRESS_DOWN) {
if (!isJumping_) {
(IMAGE_DUCK);
setGraphics}
}
else if (input == RELEASE_DOWN) {
(IMAGE_STAND);
setGraphics}
}
void Heroine::handleInput(Input input) {
if (input == PRESS_B) {
if (!isJumping_ && !isDucking_) {
// Jump...
}
}
else if (input == PRESS_DOWN) {
if (!isJumping_) {
isDucking_ = true;
(IMAGE_DUCK);
setGraphics}
}
else if (input == RELEASE_DOWN) {
if (isDucking_) {
isDucking_ = false;
(IMAGE_STAND);
setGraphics}
}
}
The code gets buggier and more error prone as we add to it.
Instead, you draw a state machine, where each state can transition to another in a fixed set of ways.
Let’s model the states through enums:
enum class State {
,
STANDING,
JUMPING,
DUCKING,
DIVING};
Now, we can handle input by switching on state.
void Heroine::handleInput(Input input) {
switch (state_) {
case State::STANDING:
if (input == PRESS_B) {
state_ = State::JUMPING;
yVelocity_ = JUMP_VELOCITY;
(IMAGE_JUMP);
setGraphics}
else if (input == PRESS_DOWN) {
state_ = State::DUCKING;
(IMAGE_DUCK);
setGraphics}
break;
case State::JUMPING:
if (input == PRESS_DOWN) {
state_ = State::DIVING;
(IMAGE_DIVE);
setGraphics}
break;
case State::DUCKING:
if (input == RELEASE_DOWN) {
state_ = State::STANDING;
(IMAGE_STAND);
setGraphics}
break;
}
}
What happens if we want to implement a chargeTime_
field
to Heroine?
void Heroine::update() {
if (state_ == State::Ducking) {
chargeTime_++;
if (chargeTime_ > MAX_CHARGE) {
();
superBomb}
}
}
Ducking resets the timer, so we have to change that on the ducking state.
void Heroine::handleInput(Input input) {
switch (state_) {
case State::STANDING:
if (input == PRESS_DOWN) {
state_ = State::DUCKING;
chargeTime_ = 0;
(IMAGE_DUCK);
setGraphics}
// Handle other inputs...
break;
// Other states...
}
}
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
We define an interface for the state. Let’s make an abstract base class:
class HeroineState {
public:
virtual ~HeroineState() {}
virtual void handleInput(Heroine& heroine, Input input) {}
virtual void update(Heroine& heroine) {}
};
For each state, define a class that implements the interface.
class DuckingState : public HeroineState {
public:
() : chargeTime_(0) {}
DuckingState
virtual void handleInput(Heroine& heroine, Input input) {
if (input == RELEASE_DOWN) {
// Change to standing state...
.setGraphics(IMAGE_STAND);
heroine}
}
virtual void update(Heroine& heroine) {
chargeTime_++;
if (chargeTime_ > MAX_CHARGE) {
.superBomb();
heroine}
}
private:
int chargeTime_;
};
Now, we give the heroine a pointer to her current state, and delegate to the state.
class Heroine {
public:
virtual void handleInput(Input input) {
state_->handleInput(*this, input);
}
virtual void update() {
state_->update(*this);
}
// Other methods...
private:
* state_;
HeroineState};
How do we store each state?
We can make it static on the base state class:
class HeroineState {
public:
static StandingState standing;
static DuckingState ducking;
static JumpingState jumping;
static DivingState diving;
// Other code...
};
To make the heroine jump, the standing state would look like this:
if (input == PRESS_B) {
.state_ = &HeroineState::jumping;
heroine.setGraphics(IMAGE_JUMP);
heroine}
We might make it so the update method can set the state after the action.
void Heroine::handleInput(Input input) {
* state = state_->handleInput(*this, input);
HeroineStateif (state != NULL) {
delete state_;
state_ = state;
}
}
We really want a hook to allow the state machine to do something when entering and exiting a state.
When the heroine changes state, we also switch her sprite.
Let’s handle that by giving the state an entry action:
class StandingState : public HeroineState {
public:
virtual void enter(Heroine& heroine) {
.setGraphics(IMAGE_STAND);
heroine}
// Other code...
};
Then, on the handleInput, we let it call the enter action on the new state.
void Heroine::handleInput(Input input) {
* state = state_->handleInput(*this, input);
HeroineStateif (state != NULL) {
delete state_;
state_ = state;
// Call the enter action on the new state.
state_->enter(*this);
}
}
The ducking code looks like this now:
* DuckingState::handleInput(Heroine& heroine,
HeroineState) {
Input inputif (input == RELEASE_DOWN) {
return new StandingState();
}
// Other code...
}
We cant use FSMs for anything more complicated than a set of states. Game AI, for example, is very difficult to code this way.
What happens if we want to add a gun to our heroine? Well, instead of packing all the state into one machine, where there are (n * m) states, we can make two different state machines, for (n + m) states.
We give her another state machine for the equipment she’s using.
class Heroine {
private:
* state;
HeroineState* equipment_;
HeroineState};
When the heroine delegates inputs to the states, she hands it to both of them:
void Heroine::handleInput(Input input) {
state_->handleInput(*this, input);
equipment_->handleInput(*this, input);
}
If the two states interact with each other, you’ll need to sprinkle
in some ifs
here and there, but it works.
What happens if we have a state that duplicates most of a parent state? We can use inheritance to handle these inputs.
class OnGroundState : public HeroineState {
public:
virtual void handleInput(Heroine& heroine, Input input) {
if (input == PRESS_B) {
// Jump...
}
else if (input == PRESS_DOWN) {
// Duck...
}
}
};
Each substate inherits it:
class DuckingState : public OnGroundState {
public:
virtual void handleInput(Heroine& heroine, Input input) {
if (input == RELEASE_DOWN) {
// Stand up...
}
else {
// Didn't handle input, so walk up hierarchy.
::handleInput(heroine, input);
OnGroundState}
}
};
If each child state can’t handle the state, it delegates up the chain.
What happens if we want to transition to a new state with this approach? Well, using a vanilla state machine, we forget what state we were in after we transition. In order to get around that limitation, we can keep a stack of states. The top of the stack is the last state you were in.
Finite state machines are useful when:
Prev: singleton Next: double-buffer