enum StateType{state_RunAway, state_Patrol, state_Attack};
void Agent::UpdateState(StateType CurrentState)
{
switch(CurrentState)
{
case state_RunAway:
EvadeEnemy();
if (Safe())
{
ChangeState(state_Patrol);
}
break;
case state_Patrol:
FollowPatrolPath();
if (Threatened())
{
if (StrongerThanEnemy())
{
ChangeState(state_Attack);
}
else
{
ChangeState(state_RunAway);
}
}
break;
case state_Attack:
if (WeakerThanEnemy())
{
ChangeState(state_RunAway);
}
else
{
BashEnemyOverHead();
}
break;
}//end switch
}
|
Current
State
|
Condition
|
State Transition
|
Runaway
|
Safe
|
Patrol
|
Attack
|
WeakerThanEnemy
|
RunAway
|
Patrol
|
Threatened AND StrongerThanEnemy
|
Attack
|
Patrol
|
Threatened AND WeakerThanEnemy
|
RunAway
|
Class
State
{
public:
virtual void Execute (Troll* troll) = 0;
};
|
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;
}
};
|
//----------------------------------State_Runaway
class State_RunAway : public State
{
public:
void Execute(Troll* troll)
{
if (troll->isSafe())
{
troll->ChangeState(new State_Sleep());
}
else
{
troll->MoveAwayFromEnemy();
}
}
};
//----------------------------------State_Sleep
class State_Sleep : public State
{
public:
void Execute(Troll* troll)
{
if (troll->isThreatened())
{
troll->ChangeState(new State_RunAway())
}
else
{
troll->Snore();
}
}
};
|
Miner Bob: Pickin' up a nugget
Miner Bob: Pickin' up a nugget
Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold
Miner Bob: Goin' to the bank. Yes siree
Miner Bob: Depositin’ gold. Total savings now: 3
Miner Bob: Leavin' the bank
Miner Bob: Walkin' to the gold mine
Miner Bob: Pickin' up a nugget
Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold
Miner Bob: Boy, ah sure is thusty! Walkin' to the saloon
Miner Bob: That's mighty fine sippin liquor
Miner Bob: Leavin' the saloon, feelin' good
Miner Bob: Walkin' to the gold mine
Miner Bob: Pickin' up a nugget
Miner Bob: Pickin' up a nugget
Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold
Miner Bob: Goin' to the bank. Yes siree
Miner Bob: Depositin' gold. Total savings now: 4
Miner Bob: Leavin' the bank
Miner Bob: Walkin' to the gold mine
Miner Bob: Pickin' up a nugget
Miner Bob: Pickin' up a nugget
Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold
Miner Bob: Boy, ah sure is thusty! Walkin' to the saloon
Miner Bob: That's mighty fine sippin' liquor
Miner Bob: Leavin' the saloon, feelin' good
Miner Bob: Walkin' to the gold mine
Miner Bob: Pickin' up a nugget
Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold
Miner Bob: Goin' to the bank. Yes siree
Miner Bob: Depositin' gold. Total savings now: 5
Miner Bob: Woohoo! Rich enough for now. Back home to mah li'l lady
Miner Bob: Leavin' the bank
Miner Bob: Walkin' home
Miner Bob: ZZZZ...
Miner Bob: ZZZZ...
Miner Bob: ZZZZ...
Miner Bob: ZZZZ...
Miner Bob: What a God-darn fantastic nap! Time to find more gold
|
class BaseGameEntity
{
private:
//every entity has a unique identifying number
int m_ID;
//this is the next valid ID. Each time a BaseGameEntity is instantiated
//this value is updated
static int m_iNextValidID;
//this is called within the constructor to make sure the ID is set
//correctly. It verifies that the value passed to the method is greater
//or equal to the next valid ID, before setting the ID and incrementing
//the next valid ID
void SetID(int val);
public:
BaseGameEntity(int id)
{
SetID(id);
}
virtual ~BaseGameEntity(){}
//all entities must implement an update function
virtual void Update()=0;
int ID()const{return m_ID;}
};
|
Class Miner : public BaseGameEntity
{
private:
//a pointer to an instance of a State
State* m_pCurrentState;
// the place where the miner is currently situated
location_type m_Location;
//how many nuggets the miner has in his pockets
int m_iGoldCarried;
//how much money the miner has deposited in the bank
int m_iMoneyInBank;
//the higher the value, the thirstier the miner
int m_iThirst;
//the higher the value, the more tired the miner
int m_iFatigue;
public:
Miner(int ID);
//this must be implemented
void Update();
//this method changes the current state to the new state
void ChangeState(State* pNewState);
/* bulk of interface omitted */
};
|
void Miner::Update()
{
m_iThirst += 1;
if (m_pCurrentState)
{
m_pCurrentState->Execute(this);
}
}
|
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;
}
|
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);
}
|
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.
提示:状态设计模式对于游戏主流程的组织也是非常有用的,例如,你可能有菜单状态、保存状态、暂停状态、设置状态和运行状态等。
|
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, singleton 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 arbitrary 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.
注意:我乐于使用单件的原因在上文已经给出,但这也有一个缺陷。因为他们由客户共享,单件状态不能使用他们自有的,特定智能体的数据。例如,当某一处于某状态的智能体移动到某一位置时,他不能把这一位置存储在状态内(因为这个状态可能与其它正处于这一状态的智能体不同)。它只能把它存储在其它地方,然后由状态机通过智能体的接口来存取。如果你的状态只有一两个数据要存取,那这也不是什么大问题,但如果你在很多外部数据,那可能就值得考虑放弃单件设计,而转而写一代码来管理状态内存的申请与释放了。
|
class EnterMineAndDigForNugget : public State
{
private:
EnterMineAndDigForNugget(){}
/* copy ctor and assignment op omitted */
public:
//this is a singleton
static EnterMineAndDigForNugget* Instance();
virtual void Enter(Miner* pMiner);
virtual void Execute(Miner* pMiner);
virtual void Exit(Miner* pMiner);
};
|
void EnterMineAndDigForNugget::Enter(Miner* pMiner)
{
//if the miner is not already located at the goldmine, he must
//change location to the gold mine
if (pMiner->Location() != goldmine)
{
cout << "/n" << GetNameOfEntity(pMiner->ID()) << ": "
<< "Walkin' to the goldmine";
pMiner->ChangeLocation(goldmine);
}
}
|
void EnterMineAndDigForNugget::Execute(Miner* pMiner)
{
//the miner digs for gold until he is carrying in excess of MaxNuggets.
//If he gets thirsty during his digging he stops work and
//changes state to go to the saloon for a beer.
pMiner->AddToGoldCarried(1);
//digging is hard work
pMiner->IncreaseFatigue();
cout << "/n" << GetNameOfEntity(pMiner->ID()) << ": "
<< "Pickin' up a nugget";
//if enough gold mined, go and put it in the bank
if (pMiner->PocketsFull())
{
pMiner->ChangeState(VisitBankAndDepositGold::Instance());
}
//if thirsty go and get a beer
if (pMiner->Thirsty())
{
pMiner->ChangeState(QuenchThirst::Instance());
}
}
|
void EnterMineAndDigForNugget::Exit(Miner* pMiner)
{
cout << "/n" << GetNameOfEntity(pMiner->ID()) << ": "
<< "Ah'm leavin' the goldmine with mah pockets full o' sweet gold";
}
|
template
class State
{
public:
virtual void Enter(entity_type*)=0;
virtual void Execute(entity_type*)=0;
virtual void Exit(entity_type*)=0;
virtual ~State(){}
};
|
class EnterMineAndDigForNugget : public State
{
public:
/* OMITTED */
};
|
//notice how now that State is a class template we have to declare the entity type
State |
class Miner : public BaseGameEntity
{
private:
State
State
State
...
public:
void ChangeState(State
void RevertToPreviousState();
...
};
|
template
class StateMachine
{
private:
//a pointer to the agent that owns this instance
entity_type* m_pOwner;
State
//a record of the last state the agent was in
State
//this state logic is called every time the FSM is updated
State
public:
StateMachine(entity_type* owner):m_pOwner(owner),
m_pCurrentState(NULL),
m_pPreviousState(NULL),
m_pGlobalState(NULL)
{}
//use these methods to initialize the FSM
void SetCurrentState(State
void SetGlobalState(State
void SetPreviousState(State
//call this to update the FSM
void Update()const
{
//if a global state exists, call its execute method
if (m_pGlobalState) m_pGlobalState->Execute(m_pOwner);
//same for the current state
if (m_pCurrentState) m_pCurrentState->Execute(m_pOwner);
}
//change to a new state
void ChangeState(State
{
assert(pNewState &&
"
//keep a record of the previous state
m_pPreviousState = m_pCurrentState;
//call the exit method of the existing state
m_pCurrentState->Exit(m_pOwner);
//change state to the new state
m_pCurrentState = pNewState;
//call the entry method of the new state
m_pCurrentState->Enter(m_pOwner);
}
//change state back to the previous state
void RevertToPreviousState()
{
ChangeState(m_pPreviousState);
}
//accessors
State
State
State
//returns true if the current state’s type is equal to the type of the
//class passed as a parameter.
bool isInState(const State
};
|
class Miner : public BaseGameEntity
{
private:
//an instance of the state machine class
StateMachine
/* EXTRANEOUS DETAIL OMITTED */
public:
Miner(int id):m_Location(shack),
m_iGoldCarried(0),
m_iMoneyInBank(0),
m_iThirst(0),
m_iFatigue(0),
BaseGameEntity(id)
{
//set up state machine
m_pStateMachine = new StateMachine
m_pStateMachine->SetCurrentState(GoHomeAndSleepTilRested::Instance());
m_pStateMachine->SetGlobalState(MinerGlobalState::Instance());
}
~Miner(){delete m_pStateMachine;}
void Update()
{
++m_iThirst;
m_pStateMachine->Update();
}
StateMachine
/* EXTRANEOUS DETAIL OMITTED */
};
|
Miner
Bob:
Pickin'
up
a
nugget
Miner
Bob:
Ah'm
leavin'
the
gold
mine
with
mah
pockets
full
o'
sweet
gold
Miner
Bob:
Goin'
to
the
bank.
Yes
siree
Elsa:
Walkin'
to
the
can.
Need
to
powda
mah
pretty
li'l
nose
Elsa:
Ahhhhhh!
Sweet
relief!
Elsa:
Leavin'
the
john
Miner
Bob:
Depositin'
gold.
Total
savings
now:
4
Miner
Bob:
Leavin'
the
bank
Miner
Bob:
Walkin'
to
the
gold
mine
Elsa:
Walkin'
to
the
can.
Need
to
powda
mah
pretty
li'l
nose
Elsa:
Ahhhhhh!
Sweet
relief!
Elsa:
Leavin'
the
john
Miner
Bob:
Pickin'
up
a
nugget
Elsa:
Moppin'
the
floor
Miner
Bob:
Pickin'
up
a
nugget
Miner
Bob:
Ah'm
leavin'
the
gold
mine
with
mah
pockets
full
o'
sweet
gold
Miner
Bob:
Boy,
ah
sure
is
thusty!
Walkin'
to
the
saloon
Elsa:
Moppin'
the
floor
Miner
Bob:
That's
mighty
fine
sippin'
liquor
Miner
Bob:
Leavin'
the
saloon,
feelin'
good
Miner
Bob:
Walkin'
to
the
gold
mine
Elsa:
Makin'
the
bed
Miner
Bob:
Pickin'
up
a
nugget
Miner
Bob:
Ah'm
leavin'
the
gold
mine
with
mah
pockets
full
o'
sweet
gold
Miner
Bob:
Goin'
to
the
bank.
Yes
siree
Elsa:
Walkin'
to
the
can.
Need
to
powda
mah
pretty
li'l
nose
Elsa:
Ahhhhhh!
Sweet
relief!
Elsa:
Leavin'
the
john
Miner
Bob:
Depositin'
gold.
Total
savings
now:
5
Miner
Bob:
Woohoo!
Rich
enough
for
now.
Back
home
to
mah
li'l
lady
Miner
Bob:
Leavin'
the
bank
Miner
Bob:
Walkin'
home
Elsa:
Walkin'
to
the
can.
Need
to
powda
mah
pretty
li'l
nose
Elsa:
Ahhhhhh!
Sweet
relief!
Elsa:
Leavin'
the
john
Miner
Bob:
ZZZZ...
|