`
varsoft
  • 浏览: 2436361 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

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

阅读更多

本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明。
译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的。任何人不得在未经同意的情况下将英文版和中文版用于商业行为,转载本文产生的法律和道德责任由转载者承担,与译者无关。
State-Driven Game Agent Design
状态驱动的游戏智能体设计(中)
Mat Buckland
(续上篇)
―――――――――――――――――――――――――――――――――――――
The West World Project
WestWorld项目
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 only be one inhabitant — a gold miner named Miner Bob — but later his wife will also make an appearance. You will have to imagine the tumbleweeds, creakin’ mine props, and desert dust blowin’ in your eyes because West World is implemented as a simple text-based console application. Any state changes or output from state actions will be sent as text to the console window. I’m using this plaintext-only approach as it demonstrates clearly the mechanism of a finite state machine without adding the code clutter of a more complex environment.
作为一个如何利用有限状态机的创造智能体的实例,我们创建名为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 on variables like thirst, fatigue, and how much gold he has found hacking away down in the gold mine.
WestWorld有四个场景:一个金矿、一个储藏库(Bob把找到的金块存放在这里)、一个酒吧(喝水吃饭)和一个家(睡觉)。确切来讲就是他去哪里、做什么和什么去,都由Bob当前的状态决定。他根据饥渴度、疲惫度和从金旷获得的金块数量来改变状态。
Before we delve into 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
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 class BaseGameEntity. This is a simple class with a private member for storing an ID number. It also specifies a pure virtual member function, Update, which must be implemented by all subclasses. Update is a function that gets called every update step and will be used by subclasses to update their state machine along with any other data that must be updated each time step.
WeskWorld游戏里的所有物体都从BaseGameEntity类派生。这是只有一个私有成员(用以保存ID)的简单类,此外就只有一个纯虚函数Update了,它必须在子类中实现。Update函数将在更新步骤里调用,用以给子类在每一个时间片依据其它数据更新他们的状态机里必须被更新的其它数据。
The BaseGameEntity class declaration looks like this:
BaseGameEntity的声明如下:
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;}
};
For reasons that will become obvious later [in the book], it’s very important for each entity in your game to have a unique identifier. Therefore, on instantiation, the ID passed to the constructor is tested in the 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 identifier. These can be found in the file EntityNames.h as ent_Miner_Bob and ent_Elsa.
为游戏里的每一个实体设置一个唯一的ID是非常重要的,在本书后面的章节将为你讲述为什么非常重要。因此,在实例化的时候把ID通过构造函数传递,并通过SetID函数来测试它是否唯一,如果不唯一,程序将会退出,产生一个断言失败错误。在本文的例子中,将把一个枚举值作为唯一的ID,在EntityNames.h文件里可以找到ent_Miner_Bobent_Elsa等枚举值。
The Miner Class
Miner
The Miner class is derived from the BaseGameEntity class 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 pointer to an instance of a State class in addition to a method for changing what State that pointer points to.
Miner类从BaseGameEntity类派生,它包括健康、疲惫程度和位置等数据成员。像前文描述过的Troll例子,Miner也有一个指向State类实例的指针,当然也少不了用以改变State指针所指向的实例的方法。
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 */
};
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:
Miner::Update方法直接明了:它在调用当前状态的Execute方法之前简单地增加m_iThirst。它的实现如下:
本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明。
译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的。任何人不得在未经同意的情况下将英文版和中文版用于商业行为,转载本文产生的法律和道德责任由转载者承担,与译者无关。
void Miner::Update()
{
m_iThirst += 1;
if (m_pCurrentState)
{
m_pCurrentState->Execute(this);
}
}
Now that you’ve seen how the Miner class 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 one of four states. Here are the names of those states followed by a description of the actions and state transitions that occur within those states:
淘金者Bob能够进入这四个状态之一。下文是这些状态的名字(结合了动作的描述),状态转换发生在状态内部。
  • 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 if while digging he finds himself thirsty, he will stop and change state to QuenchThirst.
  • EnterMinAndDigForNuggetBob不在金矿的时候,他移动到金矿。如果已经在金矿,他会持续掘金。直到他的袋子装满金矿石,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 return 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 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 on the eyes and can make it much easier to spot any errors in the logic flow.
一个这样的图示有助于我们理解,也更容易找出逻辑流中的错误。
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 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.
之前已经对这个模式作了简单介绍,但不够深入。每一个游戏智能体的状态机都作为唯一的类来实现,智能体拥有一个指向当前状态实例的指针。智能体需要实现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 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.
之前也提及过通常每一个状态都有相应的EnterExit动作,这将使得程序员能够编写仅在进入或者离开状态只执行一次的逻辑以增强FSM的可伸缩性。为了实现这一点,让我们来看看改进后的State基类。
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;
}
These additional methods are only called when a Minerchanges state. When a state transition occurs, the Miner::ChangeStatemethod first callsthe Exitmethod of the current state, then it assigns the new state to the current state, and finishes by calling the Entermethod 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 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 new state
m_pCurrentState = pNewState;
//call the entry method of the new state
m_pCurrentState->Enter(this);
}
Notice how a Minerpasses the thispointer to each state, enabling the state to use the Minerinterface to access any relevant data.
注意Minerthis指针传递到每一个状态,使得状态能够使用Miner的接口获取相关数据。
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 Minermay access are derived from the Stateclass, giving us these concrete classes: EnterMineAndDigForNugget, VisitBankAndDepositGold, GoHomeAndSleepTilRested, and QuenchThirst. The Miner::m_pCurrentStatepointer is able to point to any of these states. When the Updatemethod of Mineris called, it in turn calls the Executemethod of the currently active state with the thispointer as a parameter. These class relationships may be easier to understand if you examine the simplified UML class diagram shown in Figure 2.3. (Click here for an introduction to UML class diagrams)
Miner可能处于四个状态之一,它们都从State类派生而来,具体是:EnterMineAndDigForNuggetVisitBankAndDepositGoldGoHomeAndSleepTilRestedQuenchThirstMiner::m_pCurrrentState可能指向其中的任何一个。当MinerUpdate方法被调用,它就以this指针为参数调用当前活动状态的Execute方法。如果你能看图2.3UML图,应该很容易理解这些类之间的关系。(这里有对UML图的介绍)
本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明。
译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的。任何人不得在未经同意的情况下将英文版和中文版用于商业行为,转载本文产生的法律和道德责任由转载者承担,与译者无关。
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 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 if you have many agents sharing a complex FSM and/or you are developing for a machine with limited resources.
每一个状态都以单件对象的形式实现,这是为了确保只有一个状态的实例,所有的智能体共享这一实例(想了解什么是单件,可以阅读这个文档)。使用单件使得这一设计更加高效,因为避免了在每一次状态转换的时候申请和释放内存。这在你有很多智能体共享复杂的FSM的时候变得极其重要,特别是你在资源受限的机器上进行开发的话。
Figure 2.3. UML class 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 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.
注意:我乐于使用单件的原因在上文已经给出,但这也有一个缺陷。因为他们由客户共享,单件状态不能使用他们自有的,特定智能体的数据。例如,当某一处于某状态的智能体移动到某一位置时,他不能把这一位置存储在状态内(因为这个状态可能与其它正处于这一状态的智能体不同)。它只能把它存储在其它地方,然后由状态机通过智能体的接口来存取。如果你的状态只有一两个数据要存取,那这也不是什么大问题,但如果你在很多外部数据,那可能就值得考虑放弃单件设计,而转而写一代码来管理状态内存的申请与释放了。

Okay, let’s see how everything fits together by examining the complete code for one of the miner states.
好了,现在让我们来看看如何把所有的东西都融合在一起完成一个淘金者的状态。
The EnterMineAndDigForNuggetState
EnterMineAndDigForNugget状态
In this state the miner should change location to be at the gold mine. Once at the gold mine he should dig for gold until his pockets are full, when he should change state to VisitBankAndDepositNugget.If the miner gets thirsty while digging he should change state to QuenchThirst.
淘金者在这个状态会改变所在地,去到金矿场,到矿场后就开始掘金,直到装满口袋,这时改变状态到VisitBankanDepositNugget。如果掘金中途感到口渴,淘金者就转换到QuenchThirst状态。
Because concrete states simply implement the interface defined in the virtual base class State, their declarations are very straightforward:
因为具类只是简单地实现虚基类State定义的接口,它们的声明非常简明:
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);
};
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)
{

相关推荐

    C++实现状态驱动智能体设计——消息功能

    设计精度的游戏趋向于事件驱动。即当一个事件发生了(武器发射了子弹等),事件被广播给游戏中的相关的对象。这样它们可以恰当地做出反应。而这个消息可以是立即执行,也可以设定多久后才执行。更多详情参见本人博客...

    有限状态机(FSM)

    游戏人工智能,状态驱动智能体设计——有限状态机(FSM),编译环境:VS2010。本人博客:http://blog.csdn.net/sinat_24229853

    基于强化学习和深度 Q 学习的 AI 驱动的蛇游戏python源码+项目说明.zip

    每次智能体执行一个动作时,环境都会给智能体一个奖励,奖励可以是正面的,也可以是负面的,这取决于该特定状态下动作的好坏程度。 深度强化学习 (DRL) 将 RL 的上述思想与深度神经网络相结合。神经网络学习“Q ...

    NodeCanvas 3.1.8.rar

    ● 图形、GameObject 和全局黑板变量,以创建可重复使用和以智能体为中心的参数行为。 ● 具有实例或静态属性和字段的数据绑定变量。 ● 使用 UNET 的网络同步变量 (Network Sync Variable)。 (由于现已弃用 UNET,...

    JAVA上百实例源码以及开源项目

    在有状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天...

    JAVA上百实例源码以及开源项目源代码

    在有状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天...

    c语言编写单片机技巧

    C语言是一种结构化程序设计语言,它支持当前程序设计中广泛采用的由顶向下结构化程序设计技术。此外,C语言程序具有完善的模块程序结构,从而为软件开发中采用模块化程序设计方法提供了有力的保障。因此,使用C语言...

    vc++ 应用源码包_6

    主要在MzfHipsDlg中,程序分析进程数据、驱动数据、注册表数据从而实现主动防御。 超级下载 不过不是c++源码 Notepad++ V5.6.8 源码! OA精灵代码 c++版 一套oa系统。 ocxdlgtest dll的一个实例。 OD反汇编引擎...

    vc++ 应用源码包_5

    主要在MzfHipsDlg中,程序分析进程数据、驱动数据、注册表数据从而实现主动防御。 超级下载 不过不是c++源码 Notepad++ V5.6.8 源码! OA精灵代码 c++版 一套oa系统。 ocxdlgtest dll的一个实例。 OD反汇编引擎...

    vc++ 应用源码包_1

    主要在MzfHipsDlg中,程序分析进程数据、驱动数据、注册表数据从而实现主动防御。 超级下载 不过不是c++源码 Notepad++ V5.6.8 源码! OA精灵代码 c++版 一套oa系统。 ocxdlgtest dll的一个实例。 OD反汇编引擎...

    vc++ 应用源码包_2

    主要在MzfHipsDlg中,程序分析进程数据、驱动数据、注册表数据从而实现主动防御。 超级下载 不过不是c++源码 Notepad++ V5.6.8 源码! OA精灵代码 c++版 一套oa系统。 ocxdlgtest dll的一个实例。 OD反汇编引擎...

    vc++ 应用源码包_3

    主要在MzfHipsDlg中,程序分析进程数据、驱动数据、注册表数据从而实现主动防御。 超级下载 不过不是c++源码 Notepad++ V5.6.8 源码! OA精灵代码 c++版 一套oa系统。 ocxdlgtest dll的一个实例。 OD反汇编引擎...

    vc++ 开发实例源码包

    主要在MzfHipsDlg中,程序分析进程数据、驱动数据、注册表数据从而实现主动防御。 超级下载 不过不是c++源码 1:综合FTP下载和HTTP(网络蚂蚁)(多线程). 2:FTP下载支持多个站点同时下载一个文件(同时支持断点续传). 3...

Global site tag (gtag.js) - Google Analytics