智能驱动:状态驱动的游戏智能体设计( 2)



As a practical example of how to create agents that utilize finite state machines, we are going to look at a game environment where agents inhabit an Old West-style gold mining town named West World. Initially there will _disibledevent=>作为个如何利用有限状态机创造智能体例子我们创建名为WestWorld旧西部风格淘金镇游戏并研究其中智能体实现开始只存在个名为Miner Bob淘金者随后他妻子也出现你可以想像风滚草、叽叽作响淘金用具和沙漠风把沙吹进你眼睛WestWorld只是个简单基于文本控制台所有状态改变和状态动作产生输出都作为文本传送到控制台窗口我使用纯文本原因是为了清晰地示范有限状态机机制不想增加代码以免搞得太过于复杂
There are four locations in West World: a goldmine, a bankwhere Bob can deposit any nuggets he finds, a saloonin which he can quench his thirst, and home-sweet-homewhere he can sleep the fatigue of the day away. Exactly where he goes, and what he does when he gets there, is determined by Bob’s current state. He will change states depending _disibledevent=>WestWorld有 4个场景:个金矿、个储藏库(Bob把找到金块存放在这里)、个酒吧(喝水吃饭)和个家(睡觉)确切来讲就是他去哪里、做什么和什么去都由Bob当前状态决定他根据饥渴度、疲惫度和从金旷获得金块数量来改变状态
Before we delve o the source code, check out the following sample output from the WestWorld1 executable.
在我们研究代码的前我们先来看看WestWorld1可执行文件产生输出:
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





[Page]In the output from the program, each time you see Miner Bob change location he is changing state. All the other events are the actions that take place within the states. We’ll examine each of Miner Bob’s potential states in just a moment, but for now, let me explain a little about the code structure of the demo.
(You can download the accompanying project files here (24k))
输出来看Miner Bob次改变他所处场景时他都会改变状态所有其它事件都发生在状态里动作我们将会检测Miner Bob在每时刻个潜在状态但现在让我来对demo代码结构稍作解释
(你可以从 这里 下载项目代码(24k))
The BaseGameEntity Class
BaseGameEntity
All inhabitants of West World are derived from the base BaseGameEntity. This is a simple with a private member for storing an ID number. It also species a pure virtual member function, Update, which must be implemented by all subes. Update is a function that gets called every update step and will be used by subes to update their state machine along with any other data that must be updated each time step.
WeskWorld游戏里所有物体都从BaseGameEntity类派生这是只有个私有成员(用以保存ID)简单类此外就只有个纯虚Update它必须在子类中实现Update将在更新步骤里用以给子类在每个时间片依据其它数据更新他们状态机里必须被更新其它数据
The BaseGameEntity declaration looks like this:
BaseGameEntity声明如下:
BaseGameEntity
{
private:

//every entity has a unique identying number
m_ID;

//this is the next valid ID. Each time a BaseGameEntity is instantiated
//this value is updated
m_iNextValidID;

//this is called within the constructor to make sure the ID is
//correctly. It veries that the value passed to the method is greater
//or equal to the next valid ID, before ting the ID and incrementing
//the next valid ID
void SetID( val);

public:

BaseGameEntity( id)
{
SetID(id);
}

virtual ~BaseGameEntity{}

//all entities must implement an update function
virtual void Update=0;

IDconst{ m_ID;}
};





For reasons that will become obvious later [in the book], it’s very important for each entity in your game to have a unique identier. Therefore, _disibledevent=>SetID method to make sure it’s unique. If it is not, the program will exit with an assertion failure. In the example given, the entities will use an enumerated value as their unique identier. These can be found in the file EntityNames.h as ent_Miner_Bob and ent_Elsa.
[Page]为游戏里个实体设置个唯ID是非常重要在本书后面章节将为你讲述为什么非常重要因此在例子化时候把ID通过构造传递并通过SetID来测试它是否唯如果不唯将会退出产生个断言失败在本文例子中将把个枚举值作为唯IDEntityNames.h文件里可以找到ent_Miner_Bobent_Elsa等枚举值
The Miner Class
Miner
The Miner is derived from the BaseGameEntity and contains data members representing the various attributes a Miner possesses, such as its health, its level of fatigue, its position, and so forth. Like the troll example shown earlier, a Miner owns a poer to an instance of a State in addition to a method for changing what State that poer pos to.
Miner类从BaseGameEntity类派生它包括健康、疲惫程度和位置等数据成员像前文描述过Troll例子Miner也有个指向State类例子指针当然也少不了用以改变State指针所指向例子思路方法
Class Miner : public BaseGameEntity
{
private:

//a poer 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
m_iGoldCarried;

//how much money the miner has deposited in the bank
m_iMoneyInBank;

//the higher the value, the thirstier the miner
m_iThirst;

//the higher the value, the more tired the miner
m_iFatigue;

public:

Miner( ID);

//this must be implemented
void Update;

//this method changes the current state to the state
void ChangeState(State* pNewState);

/* bulk of erface omitted */
};





The Miner::Update method is straightforward; it simply increments the m_iThirst value before calling the Execute method of the current state. It looks like this:
[Page]Miner::Update思路方法直接明了:它在当前状态Execute思路方法的前简单地增加m_iThirst实现如下:
本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday欢迎转载但必须保持全文完整也必须包含本声明
译者并示取得中文版翻译授权翻译本文只是出于研究和学习目任何人不得在未经同意情况下将英文版和中文版用于商业行为转载本文产生法律和道德责任由转载者承担和译者无关
void Miner::Update
{
m_iThirst 1;

(m_pCurrentState)
{
m_pCurrentState->Execute(this);
}
}



Now that you’ve seen how the Miner operates, let’s take a look at each of the states a miner can find itself in.
现在你知道Miner操作了让我们来看看它个状态是如何样
The Miner States
Miner状态
The gold miner will be able to enter _disibledevent=>淘金者Bob能够进入这 4个状态的下文是这些状态名字(结合了动作描述)状态转换发生在状态内部
  • EnterMineAndDigForNugget: If the miner is not located at the gold mine, he changes location. If already at the gold mine, he digs for nuggets of gold. When his pockets are full, Bob changes state to VisitBankAndDepositGold, and while digging he finds himself thirsty, he will stop and change state to QuenchThirst.
  • EnterMinAndDigForNugget:Bob不在金矿时候他移动到金矿如果已经在金矿他会持续掘金直到他袋子装满金矿石

    Bob将会转换到VisitBankAndDepositGold状态但如果在掘金时候觉得饥渴他就会停下来把状态转换到QuenchThirst
  • VisitBankAndDepositGold: In this state the miner will walk to the bank and deposit any nuggets he is carrying. If he then considers himself wealthy enough, he will change state to GoHomeAnd- SleepTilRested. Otherwise he will change state to EnterMine- AndDigForNugget.
  • VisitBankAndDepositGold:处于这个状态时淘金者会走到储藏库并把带来金矿石保存起来如果他觉得自己足够富有他就转换到GoHomeAndSleepTilRested状态否则就转换到EnterMineAndDigForNugget
  • GoHomeAndSleepTilRested: In this state the miner will to his shack and sleep until his fatigue level drops below an acceptable level. He will then change state to EnterMineAndDigForNugget.
  • GoHomeAndSleepTilRested:处于此状态淘金者会返回到他房子里睡觉直到疲惫下降到可接受情况这时转换到EnterMineAndDigForNugget
  • QuenchThirst: If at any time the miner feels thirsty (diggin’ for gold is thusty work, don’t ya know), he changes to this state and visits the saloon in order to buy a whiskey. When his thirst is quenched, he changes state to [Page]EnterMineAndDigForNugget.
  • QuenchThirst:任何时候当淘金者感到饥渴他就改变他状态去商店买威士忌解渴后转换到EnterMineAndDigForNugget
Sometimes it’s hard to follow the flow of the state logic from reading a text description like this, so it’s often helpful to pick up pen and paper and draw a state transition diagramfor your game agents. Figure 2.2 shows the state transition diagram for the gold miner. The bubbles represent the individual states and the lines between them the available transitions.
通过阅读来理解状态逻辑流是相当困难所以最后为你游戏智能体画张状态转换图2.2是淘金者状态转换图圆角矩形是独立状态它们的间连线是允许转换
A diagram like this is better _disibledevent=>个这样图示有助于我们理解也更容易找出逻辑流中
\"\"
Figure 2.2. Miner Bob’s state transition diagram
2.2 淘金者

Bob状态转换图
The State Design Pattern Revisited
重温状态设计模式
You saw a brief description of this design pattern earlier, but it won’t hurt to recap. Each of a game agent’s states is implemented as a unique and each agent holds a poer 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 . All state es are derived from an abstract base , thereby defining a common erface. So far so good. You know this much already.
的前已经对这个模式作了简单介绍但不够深入个游戏智能体状态机都作为唯类来实现智能体拥有个指向当前状态例子指针智能体需要实现ChangeState成员以实现状态切换决定状态转换逻辑包含在每State派生类内部所有状态类都从个抽象类派生以获得统接口现在你已经知道足够多有关状态设计模式知识了
Earlier it was mentioned that it’s usually favorable for each state to have associated Enterand Exitactions. This permits the programmer to write logic that is _disibledevent=>State base .
的前也提及过通常每个状态都有相应EnterExit动作这将使得员能够编写仅在进入或者离开状态只执行逻辑以增强FSM可伸缩性为了实现这让我们来看看改进后State基类
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;
}



[Page]

These additional methods are _disibledevent=>Minerchanges state. When a state transition occurs, the Miner::ChangeStatemethod first callsthe Exitmethod of the current state, then it assigns the state to the current state, and finishes by calling the Entermethod of the state (which is now the current state). I think code is clearer than words in this instance, so here’s the listing for the ChangeStatemethod:
这两个思路方法仅在Miner改变状态时候当发生个状态转换Miner::ChangeState思路方法首先当前状态Exit思路方法然后它为当前状态指派个新状态最后新状态Enter思路方法我认为代码比言语更清晰所有这里列出ChangeState思路方法代码:
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 state
m_pCurrentState = pNewState;

//call the entry method of the state
m_pCurrentState->Enter(this);
}



Notice how a Minerpasses the thispoer to each state, enabling the state to use the Minererface to access any relevant data.
注意Minerthis指针传递到每个状态使得状态能够使用Miner接口获取相关数据
TIP: The state design pattern is also useful for structuring the 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 Minermay access are derived from the State, giving us these concrete es: EnterMineAndDigForNugget, VisitBankAndDepositGold, GoHomeAndSleepTilRested, and QuenchThirst. The Miner::m_pCurrentStatepoer is able to po to any of these states. When the Updatemethod of Mineris called, it in turn calls the Executemethod of the currently active state with the thispoer as a parameter. These relationships may be easier to understand you examine the simplied UML diagram shown in Figure 2.3. (Click here for an roduction to UML diagrams)
Miner可能处于 4个状态的它们都从State类派生而来具体是:EnterMineAndDigForNuggetVisitBankAndDepositGoldGoHomeAndSleepTilRestedQuenchThirstMiner::m_pCurrrentState可能指向其中任何MinerUpdate思路方法被它就以this指针为参数当前活动状态Execute思路方法如果你能看图2.3UML应该很容易理解这些类的间关系(这里有对UML介绍)
[Page]本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday欢迎转载但必须保持全文完整也必须包含本声明
译者并示取得中文版翻译授权翻译本文只是出于研究和学习目任何人不得在未经同意情况下将英文版和中文版用于商业行为转载本文产生法律和道德责任由转载者承担和译者无关
Each concrete state is implemented as a singleton object. This is to ensure that there is _disibledevent=> this). 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 you have many agents sharing a complex FSM and/or you are developing for a machine with limited resources.
个状态都以单件对象形式实现这是为了确保只有个状态例子所有智能体共享这例子(想了解什么是单件可以阅读这个文档)使用单件使得这设计更加高效避免了在每次状态转换时候申请和释放内存这在你有很多智能体共享复杂FSM时候变得极其重要特别是你在资源受限机器上进行开发


\"\"\">
Figure 2.3. UML diagram for Miner Bob’s state machine implementation
2.3 Miner Bob状态机实现UML类图

NOTE I prefer to use singletons for the states for the reasons I’ve already given, but there is _disibledevent=>注意:我乐于使用单件原因在上文已经给出但这也有个缺陷他们由客户共享单件状态不能使用他们自有特定智能体数据例如当某处于某状态智能体移动到某位置时他不能把这位置存储在状态内(这个状态可能和其它正处于这状态智能体区别)它只能把它存储在其它地方然后由状态机通过智能体接口来存取如果你状态只有两个数据要存取那这也不是什么大问题但如果你在很多外部数据那可能就值得考虑放弃单件设计而转而写代码来管理状态内存申请和释放了



[Page]
Okay, let’s see how everything fits together by examining the complete code for _disibledevent=>好了现在让我们来看看如何把所有东西都融合在起完成个淘金者状态
The EnterMineAndDigForNuggetState
EnterMineAndDigForNugget状态
In this state the miner should change location to be at the gold mine. _disibledevent=>VisitBankAndDepositNugget.If the miner gets thirsty while digging he should change state to QuenchThirst.
淘金者在这个状态会改变所在地去到金矿场到矿场后就开始掘金直到装满口袋这时改变状态到VisitBankanDepositNugget如果掘金中途感到口渴淘金者就转换到QuenchThirst状态

Because concrete states simply implement the erface d in the virtual base State, their declarations are very straightforward:
具类只是简单地实现虚基类State定义接口它们声明非常简明:


EnterMineAndDigForNugget : public State
{
private:

EnterMineAndDigForNugget{}

/* copy ctor and assignment op omitted */

public:

//this is a singleton
EnterMineAndDigForNugget* Instance;

virtual void Enter(Miner* pMiner);

virtual void Execute(Miner* pMiner);

virtual void Exit(Miner* pMiner);
};



As you can see, it’s just a formality. Let’s take a look at each of the methods in turn.
如你所见这只是个模式让我们来看看其它思路方法
EnterMineAndDigForNugget::Enter
EnterMineAndDigForNugget::Enter
The code for the Entermethod of EnterMineAndDigForNuggetis as follows:
下面是EnterMineAndDigForNuggetEnter思路方法:
void EnterMineAndDigForNugget::Enter(Miner* pMiner)
{
// the miner is not already located at the goldmine, he must
//change location to the gold mine
(pMiner->Location != goldmine)
{
cout << \"\\n\" << GetNameOfEntity(pMiner->ID) << \": \"
<< \"Walkin\' to the goldmine\";

pMiner->ChangeLocation(goldmine);
}
}





This method is called when a miner first enters the EnterMineAndDigForNuggetstate. It ensures that the gold miner is located at the gold mine.An agent stores its location as an enumerated type and the ChangeLocationmethod changes this value to switch locations.
当淘金者第次进入EnterMineAndDigForNugget状态时这个思路方法这确保淘金者位于金矿场智能体以枚举量形式保存当前位置ChangeLocation思路方法用以改变位置值
[Page]EnterMineAndDigForNugget::Execute
EnterMineAndDigForNugget::Execute
The Executemethod is a little more complicated and contains logic that can change a miner’s state. (Don’t forget that Executeis the method called each update step from Miner::Update.)
Execute有点复杂它包含了改变淘金者状态逻辑(不要忘记Miner::Update在每个更新帧都会Execute思路方法)
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\";

// enough gold mined, go and put it in the bank
(pMiner->PocketsFull)
{
pMiner->ChangeState(VisitBankAndDepositGold::Instance);
}

// thirsty go and get a beer
(pMiner->Thirsty)
{
pMiner->ChangeState(QuenchThirst::Instance);
}
}





Note here how the Miner::ChangeStatemethod is called using QuenchThirst’s or VisitBankAndDepositGold’s Instancemember, which provides a poer to the unique instance of that .
值得注意Miner::ChangeState思路方法QuenchThirstVisitBankAndDepositGoldInstance成员以获得指向该类唯例子指针
EnterMineAndDigForNugget::Exit
EnterMineAndDigForNugget::Exit
The Exitmethod of EnterMineAndDigForNuggetoutputs a message telling us that the gold miner is leaving the mine.
EnterMineAndDigForNuggetExit思路方法只是简单地输出条消息告诉我们淘金者离开了金矿
void EnterMineAndDigForNugget::Exit(Miner* pMiner)
{
cout << \"\\n\" << GetNameOfEntity(pMiner->ID) << \": \"
<< \"Ah\'m leavin\' the goldmine with mah pockets full o\' sweet gold\";
}



I hope an examination of the preceding three methods helps clear up any confusion you may have been experiencing and that you can now see how each state is able to mody the behavior of an agent or effect a transition o another state. You may find it useful at this stage to load up the WestWorld1 project o your IDE and scan the code. In particular, check out all the states in MinerOwnedStates.cpp and examine the Miner to familiarize yourself with its member variables. Above all , make sure you understand how the state design pattern works before you read any further. If you are a little unsure, please take the time to go over the previous text until you feel comfortable with the concept.
[Page]我希望前述 3个思路方法能帮助你理清头绪现在你应该已经理解了每个状态如何改变智能体行为又如何从个状态到另个状态转换你用IDE打开WestWorld1项目并浏览遍代码应该有助于理解可以抽取出MinerOwnedStates.cpp所有状态并检阅Miner类实现让你熟悉它成员变量最重要确定你理解了状态设计模式是如何工作然后再作进步阅读如果你有些不了解请重温上文直到你觉得已经完全理解了相关理论


You have seen how the use of the state design pattern provides a very flexible mechanism for state-driven agents. It’s extremely easy to add additional states as and when required. Indeed, should you so wish, you can switch an agent’s entire state architecture for an alternative _disibledevent=>正如你所见状态设计模式为状态驱动智能体提供了具有非常好伸缩性机制当需要时候你可以极其容易地增加新状态在你有非常复杂设计而且能更好地组织系列分散小状态机时候甚至可以替换智能体整个状态架构例如Unreal2这样人称射击游戏(FPS)有着巨大而复杂状态机当设计这种游戏AI时候你将发现它能完美地应用于基于团队(Team)而设计多个小状态机(对应功能可能是“保旗”或者“探险”)使得能够在需要时候进行切换正在状态设计模式使它易于实现
Tags:  智能驱动

延伸阅读

最新评论

发表评论