I'm implementing the State pattern in the context of videogame development. For instance, a player has various states: Idle
, Run
, Attack
. Each state is implemented in its own class.
Importantly, each state is responsible for the transition to another state (e.g. the Idle
state can transition to Attack
, however the Jump
state cannot transition to Idle
until player touches the ground). See below a high-level idea of how I implemented it:
class Player
{
private State _state = null;
public Player()
{
this.TransitionTo(new IdleState(this));
}
public void TransitionTo(State state)
{
this._state = state;
this._state.SetPlayer(this);
}
public void Jump()
{
this._state.jump();
}
}
abstract class State
{
protected Player _player;
public void SetPlayer(Player player)
{
this._player = player;
}
public abstract void Jump();
}
class JumpState : State
{
public override void Jump()
{
// double jump is not allowed
}
}
class IdleState : State
{
public override void Jump()
{
this._player.TransitionTo(new JumpState());
}
}
This works well but I can see two big limitations with this approach:
override
of the Jump
state could do, but with my implementation a state is responsible to transition to another state and it does so by referencing a concrete implementation of the other state. E.g. in the code snippet above, note how the IdleState
transitions to a specific JumpState
. If a JumpNoDamageState
existed, I would also have to create an IdleNoDamageState
that uses that.Escape
if health < 10% after taking damage. To do this, I need to recreate all Idle
, Attack
and Jump
states to account for this, as each individual state needs to be aware of this new state and the possibility to transition to it.The State pattern is an object-oriented recipe for implementing a finite-state machine (FSM). If you want to introduce new states, you'd be changing the definition of the FSM. If you want to distinguish between different FSMs, you may have to implement different State-based APIs.
The other concern is probably a result of the State
objects keeping state. Perhaps surprisingly,
"State objects are often Singletons"
You'd usually design a State implementation by passing the so-called Context object to each State object. In this particular case, you should consider changing the API so that it looks like this:
class Player
{
private State _state;
public Player()
{
_state = new IdleState(this));
}
internal void TransitionTo(State state)
{
_state = state;
}
public void Jump()
{
_state.jump(this);
}
}
abstract class State
{
public abstract void Jump(Player player);
}
class JumpState : State
{
// Singleton
public static readonly State Instance = new JumpState();
private JumpState() {}
public override void Jump(Player player)
{
// double jump is not allowed
}
}
class IdleState : State
{
// Singleton
public static readonly State Instance = new IdleState();
private IdleState() {}
public override void Jump(Player player)
{
player.TransitionTo(JumpState.Instance);
}
}
You'll often need to maintain state for separate objects, so that different objects transition through the FSM in each their own pathways. You keep state information on the Context object (here, Player
), so that different Player
objects can have different states, and pass through the same FSM in different ways.
You can make the Context object polymorphic as well.
For instance, let's say an enemy behaves exactly the same as another
This sounds as though the Context might, indeed, need to be something more general than Player
- perhaps Character
?
except that this enemy can also Escape if health < 10% after taking damage
You could reuse the same FSM but instead have it check if the Context (Character
?) may attempt to escape. This could be as simple as having a Boolean value on the Context object.