状态驱动的游戏智能体设计(中)
2008-03-11 14:51
447 查看
本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明。译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的。任何人不得在未经同意的情况下将英文版和中文版用于商业行为,转载本文产生的法律和道德责任由转载者承担,与译者无关。State-Driven Game Agent Design 状态驱动的游戏智能体设计(中)[align=left]Mat Buckland著[/align][align=left]赖勇浩(http://blog.csdn.net/lanphaday) 译[/align][align=left]原文地址:http://www.ai-junkie.com/architecture/state_driven/tut_state2.html[/align][align=left](续上篇)[/align][align=left]状态驱动的游戏智能体设计(上)http://blog.csdn.net/lanphaday/archive/2007/06/24/1664369.aspx[/align][align=left] ―――――――――――――――――――――――――――――――――――――[/align][align=left]The West World Project[/align][align=left]WestWorld项目[/align][align=left]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.[/align][align=left]作为一个如何利用有限状态机的创造智能体的实例,我们创建名为WestWorld的旧西部风格的淘金镇的游戏,并研究其中的智能体实现。一开始只存在一个名为Miner Bob的淘金者,随后他的妻子也出现。你可以想像风滚草、叽叽作响的淘金用具和沙漠的风把沙吹进你的眼睛,因为WestWorld只是一个简单的基于文本的控制台程序。所有的状态改变和状态动作产生的输出都作为文本传送到控制台窗口。我使用纯文本的原因是为了清晰地示范有限状态机的机制,不想增加代码以免搞得太过于复杂。[/align][align=left]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.[/align][align=left]WestWorld有四个场景:一个金矿、一个储藏库(Bob把找到的金块存放在这里)、一个酒吧(喝水吃饭)和一个家(睡觉)。确切来讲就是他去哪里、做什么和什么去,都由Bob当前的状态决定。他根据饥渴度、疲惫度和从金旷获得的金块数量来改变状态。[/align][align=left]Before we delve into the source code, check out the following sample output from the WestWorld1 executable.[/align][align=left]在我们研究代码之前,我们先来看看WestWorld1可执行文件产生的输出:[/align][align=center]
Miner Bob: Pickin' up a nugget[/align] [align=left]Miner Bob: Pickin' up a nugget[/align] [align=left]Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold[/align] [align=left]Miner Bob: Goin' to the bank. Yes siree[/align] [align=left]Miner Bob: Depositin’ gold. Total savings now: 3[/align] [align=left]Miner Bob: Leavin' the bank[/align] [align=left]Miner Bob: Walkin' to the gold mine[/align] [align=left]Miner Bob: Pickin' up a nugget[/align] [align=left]Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold[/align] [align=left]Miner Bob: Boy, ah sure is thusty! Walkin' to the saloon[/align] [align=left]Miner Bob: That's mighty fine sippin liquor [/align] [align=left]Miner Bob: Leavin' the saloon, feelin' good[/align] [align=left]Miner Bob: Walkin' to the gold mine[/align] [align=left]Miner Bob: Pickin' up a nugget[/align] [align=left]Miner Bob: Pickin' up a nugget[/align] [align=left]Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold[/align] [align=left]Miner Bob: Goin' to the bank. Yes siree[/align] [align=left]Miner Bob: Depositin' gold. Total savings now: 4[/align] [align=left]Miner Bob: Leavin' the bank[/align] [align=left]Miner Bob: Walkin' to the gold mine [/align] [align=left]Miner Bob: Pickin' up a nugget [/align] [align=left]Miner Bob: Pickin' up a nugget[/align] [align=left]Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold[/align] [align=left]Miner Bob: Boy, ah sure is thusty! Walkin' to the saloon[/align] [align=left]Miner Bob: That's mighty fine sippin' liquor [/align] [align=left]Miner Bob: Leavin' the saloon, feelin' good [/align] [align=left]Miner Bob: Walkin' to the gold mine[/align] [align=left]Miner Bob: Pickin' up a nugget[/align] [align=left]Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold[/align] [align=left]Miner Bob: Goin' to the bank. Yes siree[/align] [align=left]Miner Bob: Depositin' gold. Total savings now: 5[/align] [align=left]Miner Bob: Woohoo! Rich enough for now. Back home to mah li'l lady[/align] [align=left]Miner Bob: Leavin' the bank [/align] [align=left]Miner Bob: Walkin' home [/align] [align=left]Miner Bob: ZZZZ...[/align] [align=left]Miner Bob: ZZZZ... [/align] [align=left]Miner Bob: ZZZZ... [/align] [align=left]Miner Bob: ZZZZ...[/align] [align=left]Miner Bob: What a God-darn fantastic nap! Time to find more gold[/align] |
class BaseGameEntity[/align] [align=left]{[/align] [align=left]private:[/align] [align=left] [/align] [align=left] //every entity has a unique identifying number[/align] [align=left] int m_ID;[/align] [align=left] [/align] [align=left] //this is the next valid ID. Each time a BaseGameEntity is instantiated[/align] [align=left] //this value is updated[/align] [align=left] static int m_iNextValidID;[/align] [align=left] [/align] [align=left] //this is called within the constructor to make sure the ID is set[/align] [align=left] //correctly. It verifies that the value passed to the method is greater[/align] [align=left] //or equal to the next valid ID, before setting the ID and incrementing[/align] [align=left] //the next valid ID[/align] [align=left] void SetID(int val);[/align] [align=left] [/align] [align=left]public:[/align] [align=left] [/align] [align=left] BaseGameEntity(int id)[/align] [align=left] {[/align] [align=left] SetID(id);[/align] [align=left] }[/align] [align=left] [/align] [align=left] virtual ~BaseGameEntity(){}[/align] [align=left] [/align] [align=left] //all entities must implement an update function[/align] [align=left] virtual void Update()=0;[/align] [align=left] [/align] [align=left] int ID()const{return m_ID;} [/align] [align=left]};[/align] |
Class Miner : public BaseGameEntity[/align] [align=left]{[/align] [align=left]private:[/align] [align=left] [/align] [align=left] //a pointer to an instance of a State[/align] [align=left] State* m_pCurrentState;[/align] [align=left] [/align] [align=left] // the place where the miner is currently situated[/align] [align=left] location_type m_Location;[/align] [align=left] [/align] [align=left] //how many nuggets the miner has in his pockets[/align] [align=left] int m_iGoldCarried;[/align] [align=left] [/align] [align=left] //how much money the miner has deposited in the bank[/align] [align=left] int m_iMoneyInBank;[/align] [align=left] [/align] [align=left] //the higher the value, the thirstier the miner[/align] [align=left] int m_iThirst;[/align] [align=left] [/align] [align=left] //the higher the value, the more tired the miner[/align] [align=left] int m_iFatigue;[/align] [align=left] [/align] [align=left]public:[/align] [align=left] [/align] [align=left] Miner(int ID);[/align] [align=left] [/align] [align=left] //this must be implemented[/align] [align=left] void Update();[/align] [align=left] [/align] [align=left] //this method changes the current state to the new state[/align] [align=left] void ChangeState(State* pNewState);[/align] [align=left] [/align] [align=left] /* bulk of interface omitted */[/align] [align=left]};[/align] |
void Miner::Update()[/align] [align=left]{[/align] [align=left] m_iThirst += 1;[/align] [align=left] [/align] [align=left] if (m_pCurrentState)[/align] [align=left] {[/align] [align=left] m_pCurrentState->Execute(this);[/align] [align=left] }[/align] [align=left]}[/align] |
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 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。
[align=left]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.[/align][align=left]通过阅读来理解状态逻辑流是相当困难的,所以最后为你的游戏智能体画一张状态转换图。图2.2是淘金者的状态转换图,圆角矩形是独立的状态,它们之间的连线是允许的转换。[/align][align=left]A diagram like this is better on the eyes and can make it much easier to spot any errors in the logic flow. [/align][align=left]一个这样的图示有助于我们理解,也更容易找出逻辑流中的错误。[/align]
[align=center]Figure 2.2. Miner Bob’s state transition diagram[/align][align=center]图2.2 淘金者Bob的状态转换图[/align][align=left] The State Design Pattern Revisited[/align][align=left]重温状态设计模式[/align][align=left]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.[/align][align=left]之前已经对这个模式作了简单介绍,但不够深入。每一个游戏智能体的状态机都作为唯一的类来实现,智能体拥有一个指向当前状态实例的指针。智能体需要实现ChangeState成员函数以实现状态切换。决定状态转换的逻辑包含在每一个State派生类的内部。所有的状态类都从一个抽象类派生,以获得统一接口。现在,你已经知道足够多关于状态设计模式的知识了。[/align][align=left]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.[/align][align=left]之前也提及过通常每一个状态都有相应的Enter和Exit动作,这将使得程序员能够编写仅在进入或者离开状态只执行一次的逻辑以增强FSM的可伸缩性。为了实现这一点,让我们来看看改进后的State基类。[/align][align=center]
class State[/align] [align=left]{[/align] [align=left]public:[/align] [align=left] [/align] [align=left] virtual ~State(){}[/align] [align=left] [/align] [align=left] //this will execute when the state is entered[/align] [align=left] virtual void Enter(Miner*)=0;[/align] [align=left] [/align] [align=left] //this is called by the miner’s update function each update-step[/align] [align=left] virtual void Execute(Miner*)=0;[/align] [align=left] [/align] [align=left] //this will execute when the state is exited[/align] [align=left] virtual void Exit(Miner*)=0;[/align] [align=left]}[/align] |
void Miner::ChangeState(State* pNewState)[/align] [align=left]{[/align] [align=left] //make sure both states are valid before attempting to [/align] [align=left] //call their methods[/align] [align=left] assert (m_pCurrentState && pNewState);[/align] [align=left] [/align] [align=left] //call the exit method of the existing state[/align] [align=left] m_pCurrentState->Exit(this);[/align] [align=left] [/align] [align=left] //change state to the new state[/align] [align=left] m_pCurrentState = pNewState;[/align] [align=left] [/align] [align=left] //call the entry method of the new state[/align] [align=left] m_pCurrentState->Enter(this);[/align] [align=left]}[/align] |
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.[/align] [align=left]提示:状态设计模式对于游戏主流程的组织也是非常有用的,例如,你可能有菜单状态、保存状态、暂停状态、设置状态和运行状态等。[/align] |
[align=center]Figure 2.3. UML class diagram for Miner Bob’s state machine implementation[/align][align=center]图2.3 Miner Bob的状态机实现的UML类图[/align][align=left] [/align][align=center]
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.[/align] [align=left]注意:我乐于使用单件的原因在上文已经给出,但这也有一个缺陷。因为他们由客户共享,单件状态不能使用他们自有的,特定智能体的数据。例如,当某一处于某状态的智能体移动到某一位置时,他不能把这一位置存储在状态内(因为这个状态可能与其它正处于这一状态的智能体不同)。它只能把它存储在其它地方,然后由状态机通过智能体的接口来存取。如果你的状态只有一两个数据要存取,那这也不是什么大问题,但如果你在很多外部数据,那可能就值得考虑放弃单件设计,而转而写一代码来管理状态内存的申请与释放了。[/align] |
Okay, let’s see how everything fits together by examining the complete code for one of the miner states.[/align][align=left]好了,现在让我们来看看如何把所有的东西都融合在一起完成一个淘金者的状态。[/align][align=left]The EnterMineAndDigForNuggetState[/align][align=left]EnterMineAndDigForNugget状态[/align][align=left]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.[/align][align=left]淘金者在这个状态会改变所在地,去到金矿场,到矿场后就开始掘金,直到装满口袋,这时改变状态到VisitBankanDepositNugget。如果掘金中途感到口渴,淘金者就转换到QuenchThirst状态。[/align][align=left] [/align][align=left]Because concrete states simply implement the interface defined in the virtual base class State, their declarations are very straightforward:[/align][align=left]因为具类只是简单地实现虚基类State定义的接口,它们的声明非常简明:[/align][align=center]
class EnterMineAndDigForNugget : public State[/align] [align=left]{[/align] [align=left]private:[/align] [align=left] [/align] [align=left] EnterMineAndDigForNugget(){}[/align] [align=left] [/align] [align=left] /* copy ctor and assignment op omitted */[/align] [align=left] [/align] [align=left]public:[/align] [align=left] [/align] [align=left] //this is a singleton[/align] [align=left] static EnterMineAndDigForNugget* Instance();[/align] [align=left] [/align] [align=left] virtual void Enter(Miner* pMiner);[/align] [align=left] [/align] [align=left] virtual void Execute(Miner* pMiner);[/align] [align=left] [/align] [align=left] virtual void Exit(Miner* pMiner);[/align] [align=left]};[/align] |
void EnterMineAndDigForNugget::Enter(Miner* pMiner)[/align] [align=left]{[/align] [align=left] //if the miner is not already located at the goldmine, he must[/align] [align=left] //change location to the gold mine[/align] [align=left] if (pMiner->Location() != goldmine)[/align] [align=left] {[/align] [align=left] cout << "/n" << GetNameOfEntity(pMiner->ID()) << ": " [/align] [align=left] << "Walkin' to the goldmine";[/align] [align=left] [/align] [align=left] pMiner->ChangeLocation(goldmine);[/align] [align=left] }[/align] [align=left]}[/align] |
void EnterMineAndDigForNugget::Execute(Miner* pMiner)[/align] [align=left]{ [/align] [align=left] //the miner digs for gold until he is carrying in excess of MaxNuggets. [/align] [align=left] //If he gets thirsty during his digging he stops work and [/align] [align=left] //changes state to go to the saloon for a beer.[/align] [align=left] pMiner->AddToGoldCarried(1);[/align] [align=left] [/align] [align=left] //digging is hard work[/align] [align=left] pMiner->IncreaseFatigue();[/align] [align=left] [/align] [align=left] cout << "/n" << GetNameOfEntity(pMiner->ID()) << ": " [/align] [align=left] << "Pickin' up a nugget";[/align] [align=left] [/align] [align=left] //if enough gold mined, go and put it in the bank[/align] [align=left] if (pMiner->PocketsFull())[/align] [align=left] {[/align] [align=left] pMiner->ChangeState(VisitBankAndDepositGold::Instance());[/align] [align=left] }[/align] [align=left] [/align] [align=left] //if thirsty go and get a beer[/align] [align=left] if (pMiner->Thirsty())[/align] [align=left] {[/align] [align=left] pMiner->ChangeState(QuenchThirst::Instance());[/align] [align=left] }[/align] [align=left]}[/align] |
void EnterMineAndDigForNugget::Exit(Miner* pMiner)[/align] [align=left]{[/align] [align=left] cout << "/n" << GetNameOfEntity(pMiner->ID()) << ": " [/align] [align=left] << "Ah'm leavin' the goldmine with mah pockets full o' sweet gold";[/align] [align=left]}[/align] |
相关文章推荐
- 状态驱动的游戏智能体设计(下)
- 状态驱动的游戏智能体设计
- 状态驱动的游戏智能体设计(下)
- 状态驱动的游戏智能体设计(上)
- 状态驱动的游戏智能体设计(中)
- 状态驱动的游戏智能体设计(上)
- 状态驱动的游戏智能体设计(上)
- 状态驱动的游戏智能体设计
- 状态驱动的游戏智能体设计(中)
- 游戏程序开发:状态驱动的游戏智能体设计(一)
- 状态驱动的游戏智能体设计(下)
- 状态驱动的游戏智能体设计(下)
- 状态驱动的游戏智能体设计(下)
- 状态驱动的游戏智能体设计
- 游戏程序开发:状态驱动的游戏智能体设计 (二)
- 状态驱动的游戏智能体设计(中英双语)
- 状态驱动的游戏智能体设计(上)
- 游戏程序开发:状态驱动的游戏智能体设计(三)
- 状态驱动的游戏智能体设计(上)
- 状态驱动的游戏智能体设计(中)