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

sorts of interesting and complicated behavior. These rules are programmed
onto a tiny chip situated inside the kitten’s head, which is analogous to the
state transition table we discussed earlier. The chip communicates with the
kitten’s internal functions to retrieve the information necessary to process
the rules (such as how hungry Kitty is or how playful it’s feeling).
As a result, the state transition chip can be programmed with rules like:
IF Kitty_Hungry AND NOT Kitty_Playful
SWITCH_CARTRIDGE eat_fish
All the rules in the table are tested each time step and instructions are sent
to Kitty to switch cartridges accordingly.
This type of architecture is very flexible, making it easy to expand the
kitten’s repertoire by adding new cartridges. Each time a new cartridge is
added, the owner is only required to take a screwdriver to the kitten’s head
in order to remove and reprogram the state transition rule chip. It is not
necessary to interfere with any other internal circuitry.
Embedded Rules
An alternative approach is to embed the rules for the state transitions
within the states themselves. Applying this concept to Robo-Kitty, the state
transition chip can be dispensed with and the rules moved directly into the
cartridges. For instance, the cartridge for “play with string” can monitor
the kitty’s level of hunger and instruct it to switch cartridges for the “eat
fish” cartridge when it senses hunger rising. In turn the “eat fish” cartridge
can monitor the kitten’s bowel and instruct it to switch to the “poo on car-
pet” cartridge when it senses poo levels are running dangerously high.
Although each cartridge may be aware of the existence of any of the
other cartridges, each is a self-contained unit and not reliant on any exter
-
nal logic to decide whether or not it should allow itself to be swapped for
an alternative. As a consequence, it’s a straightforward matter to add states
or even to swap the whole set of cartridges for a completely new set
(maybe ones that make little Kitty behave like a raptor). There’s no need to
take a screwdriver to the kitten’s head, only to a few of the cartridges
themselves.
Let’s take a look at how this approach is implemented within the context
of a video game. Just like Kitty’s cartridges, states are encapsulated as
objects and contain the logic required to facilitate state transitions. In addi
-
tion, all state objects share a common interface: a pure virtual class named
State. Here’s a version that provides a simple interface:
class State
{
public:
48 | Chapter 2
Implementing a Finite State Machine
virtual void Execute (Troll* troll) = 0;
};
Now imagine a Troll class that has member variables for attributes such as
health, anger, stamina, etc., and an interface allowing a client to query and
adjust those values. A
Troll can be given the functionality of a finite state
machine by adding a pointer to an instance of a derived object of the
State
class, and a method permitting a client to change the instance the pointer is
pointing to.
class Troll
{
/* ATTRIBUTES OMITTED */
State* m_pCurrentState;
public:
/* INTERFACE TO ATTRIBUTES OMITTED */
void Update()
{
m_pCurrentState->Execute(this);
}
void ChangeState(const State* pNewState)
{
delete m_pCurrentState;
m_pCurrentState = pNewState;
}
};
When the Update method of a Troll is called, it in turn calls the Execute
method of the current state type with the this pointer. The current state
may then use the
Troll interface to query its owner, to adjust its owners
attributes, or to effect a state transition. In other words, how a
Troll
behaves when updated can be made completely dependent on the logic in
its current state. This is best illustrated with an example, so let’s create a
couple of states to enable a troll to run away from enemies when it feels
threatened and to sleep when it feels safe.
//----------------------------------State_RunAway
class State_RunAway : public State
{
public:
void Execute(Troll* troll)
{
if (troll->isSafe())
{
troll->ChangeState(new State_Sleep());
}
State-Driven Agent Design
| 49
Implementing a Finite State Machine

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