O'Reilly logo

Programming Game AI by Example by Mat Buckland

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

The State Design Pattern Revisited
You saw a brief description of this design pattern a few pages back, but it
won’t hurt to recap. Each of a game agent’s states is implemented as a
unique class and each agent holds a pointer to an instance of its current
state. An agent also implements a
ChangeState member function that can be
called to facilitate the switching of states whenever a state transition is
required. The logic for determining any state transitions is contained within
each
State class. All state classes are derived from an abstract base class,
thereby defining a common interface. So far so good. You know this much
already.
Earlier in the chapter it was mentioned that it’s usually favorable for
each state to have associated enter and exit actions. This permits the pro
-
grammer to write logic that is only executed once at state entry or exit and
increases the flexibility of an FSM a great deal. With these features in
mind, let’s take a look at an enhanced
State base class.
class State
{
public:
virtual ~State(){}
//this will execute when the state is entered
virtual void Enter(Miner*)=0;
//this is called by the miner’s update function each update step
virtual void Execute(Miner*)=0;
//this will execute when the state is exited
virtual void Exit(Miner*)=0;
}
State-Driven Agent Design
| 55
The West World Project
Figure 2.2. Miner Bob’s state transition diagram
These additional methods are only called when a Miner changes state.
When a state transition occurs, the
Miner::ChangeState method first calls
the
Exit method of the current state, then it assigns the new state to the cur
-
rent state, and finishes by calling the
Enter method of the new state (which
is now the current state). I think code is clearer than words in this instance,
so here’s the listing for the
ChangeState method:
void Miner::ChangeState(State* pNewState)
{
//make sure both states are valid before attempting to
//call their methods
assert (m_pCurrentState && pNewState);
//call the exit method of the existing state
m_pCurrentState->Exit(this);
//change state to the new state
m_pCurrentState = pNewState;
//call the entry method of the new state
m_pCurrentState->Enter(this);
}
Notice how a Miner passes the this pointer to each state, enabling the state
to use the
Miner interface to access any relevant data.
z
TIP The state design pattern is also useful for structuring the main components
of your game flow. For example, you could have a menu state, a save state, a
paused state, an options state, a run state, etc.
Each of the four possible states a Miner may access are derived from the
State class, giving us these concrete classes: EnterMineAndDigForNugget,
VisitBankAndDepositGold, GoHomeAndSleepTilRested, and QuenchThirst.
The
Miner::m_pCurrentState pointer is able to point to any of these states.
When the
Update method of Miner is called, it in turn calls the Execute
method of the currently active state with the this pointer as a parameter.
These class relationships may be easier to understand if you examine the
simplified UML class diagram shown in Figure 2.3.
Each concrete state is implemented as a singleton object. This is to
ensure that there is only one instance of each state, which agents share
(those of you unsure of what a singleton is, please read the sidebar on page
58). Using singletons makes the design more efficient because they remove
the need to allocate and deallocate memory every time a state change is
made. This is particularly important if you have many agents sharing a
complex FSM and/or you are developing for a machine with limited
resources.
56 | Chapter 2
The West World Project
Ü
NOTE I prefer to use singletons for the states for the reasons I’ve already
given, but there is one drawback. Because they are shared between clients, sin
-
gleton states are unable to make use of their own local, agent-specific data. For
instance, if an agent uses a state that when entered should move it to an arbi
-
trary position, the position cannot be stored in the state itself (because the
position may be different for each agent that is using the state). Instead, it
would have to be stored somewhere externally and be accessed by the state via
the agent’s interface. This is not really a problem if your states are accessing
only one or two pieces of data, but if you find that the states you have designed
are repeatedly accessing lots of external data, it’s probably worth considering
disposing of the singleton design and writing a few lines of code to manage the
allocation and deallocation of state memory.
State-Driven Agent Design
| 57
The West World Project
Figure 2.3. UML class diagram for Miner Bob’s state machine implementation

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required