State-Driven Game Agent Design
状态驱动的游戏智能体设计
Mat Buckland
赖勇浩(http://blog.csdn.net/lanphaday)
Note: the text for this tutorial comprises part of the second chapter of the book Programming Game AI by Example. Its appearance online is a cunningly disguised attempt to tempt you into purchasing said book. I have no shame. Buy the book, you’ll enjoy it. J
题注:本文取自《Programming Game AI by Example》一书的第二章,它在网上刊出的原因是想让你购买这本书。尽管这看起来有点狡猾,但我并不感到羞愧,因为如果你购买了这本书,你会发现它值得你购买。
原文地址:http://www.ai-junkie.com/architecture/state_driven/tut_state1.html
Finite state machines, or FSMs as they are usually referred to, have for many years been the AI coder’s instrument of choice to imbue a game agent with the illusion of intelligence. You will find FSMs of one kind or another in just about every game to hit the shelves since the early days of video games, and despite the increasing popularity of more esoteric agent architectures, they are going to be around for a long time to come. Here are just some of the reasons why:
就像经常听到的,有限状态机(简写为FSM)早就被AI程序员用来实现游戏智能体以体现智能感。你会发现在FSM几乎是所有视频游戏的基础构架,不管正在出现和流行的越来越深奥的智能体架构,FSM在长久的未来仍然有武之地。这里是一些为什么FSM如此强劲的原因:
  • They are quick and simple to code.There are many ways of programming a finite state machine and almost all of them are reasonably simple to implement. You’ll see several alternatives described in this article together with the pros and cons of using them.
  • 可以快速简单地编写代码。实现有限状态机有多种途径,而且都可以简单实现。在本文你就能看到多种描述和赞成或者反对使用它们的理由。
  • They are easy to debug.Because a game agent’s behavior is broken down into easily manageable chunks, if an agent starts acting strangely, it can be debugged by adding tracer code to each state. In this way, the AI programmer can easily follow the sequence of events that precedes the buggy behavior and take action accordingly.
  • 易于调试。因为一个游戏智能体的行为由一个易于管理的代码段来实现,如果一个智能出现奇怪的行为,可以通过为每一个状态增加Tracer来调试。这样能够容易地跟踪事件序列,就可以针对之前的怪异行为修改代码了。
  • They have little computational overhead.Finite state machines use hardly any precious processor time because they essentially follow hard- coded rules. There is no real “thinking” involved beyond the if-this-then-that sort of thought process.
  • 需要付出一点计算代价。有限状态机几乎不使用宝贵的处理器时间,因为他们本质上跟硬编码是一样的。在那种“如果这样就那样”的思考处理中根本不存在真正的“思考”。
  • They are intuitive.It’s human nature to think about things as being in one state or another and we often refer to ourselves as being in such and such a state. How many times have you “got yourself into a state” or found yourself in “the right state of mind”? Humans don’t really work like finite state machines of course, but sometimes we find it useful to think of our behavior in this way. Similarly, it is fairly easy to break down a game agent’s behavior into a number of states and to create the rules required for manipulating them. For the same reason, finite state machines also make it easy for you to discuss the design of your AI with non-programmers (with game producers and level designers for example), providing improved communication and exchange of ideas.
  • 符合直觉。类天生就以当前处以这种或者哪种状态来思考事情,所以我们常常听到我们自己处于什么状态的说法。多少次你“让你自己进入状态”或者发现你自己处于“正确的精神状态”?尽管人类并非真的像有限状态机那样工作,但通常我们发现这样有利于我们思考我们的行为。同样地,这易于通过一系列的状态和创造操作规则来实现一个游戏智能体的行为。基于同样的原因,有限状态机能让你和非程序员(如游戏策划和关卡设计师等)更好地进行关于你的AI设计的讨论,改进交流和交换观点。
  • They are flexible.A game agent’s finite state machine can easily be adjusted and tweaked by the programmer to provide the behavior required by the game designer. It’s also a simple matter to expand the scope of an agent’s behavior by adding new states and rules. In addition, as your AI skills grow you’ll find that finite state machines provide a solid backbone with which you can combine other techniques such as fuzzy logic or neural networks.
  • 可伸缩性。游戏智能体的有限状态机易于调整,能够很容易让程序员实现游戏设计师需要的行为,也易于通过增加新的状态和规则来扩展智能体的行为。此外,随着你AI技术的增进,你将发现有限状态机提供坚实的基础,让你能够把模糊逻辑和神经网络之类的技术组合到游戏中。
What Exactly Is a Finite State Machine?
有限状态机定义
Historically, a finite state machine is a rigidly formalized device used by mathematicians to solve problems. The most famous finite state machine is probably Alan Turing’s hypothetical device: the Turing machine, which he wrote about in his 1936 paper, “On Computable Numbers.” This was a machine presaging modern-day programmable computers that could perform any logical operation by reading, writing, and erasing symbols on an infinitely long strip of tape. Fortunately, as AI programmers, we can forgo the formal mathematical definition of a finite state machine; a descriptive one will suffice:
从历史观点上来说,有限状态机是一种严格的公式化的被数学家用以解决难题的一种策略。最著名的有限状态机可能是阿兰·图灵在1936年发表的论文《On Computable Numbers》上写下的的猜想——图灵机。这是现代计算机的雏形,能够通过在无限长的磁带上进行读、写和擦除符号来实现所有逻辑操作。幸运的是,作为AI程序员,我们能够对公式化的数学定义不加理会,如下描述已经足够:
A finite state machine is a device, or a model of a device, which has a finite number of states it can be in at any given time and can operate on input to either make transitions from one state to another or to cause an output or action to take place. A finite state machine can only be in one state at any moment in time.
有限状态机是一种策略或者一种策略模型,它由有限的一系列状态构成,在任一给定时刻,可以通过输入操作作出从一种状态到另一种状态的转换或者产生输出或者发生动作。有限状态机在任一时刻都只能够处于一种状态中。
The idea behind a finite state machine, therefore, is to decompose an object’s behavior into easily manageable “chunks” or states. The light switch on your wall, for example, is a very simple finite state machine. It has two states: on and off. Transitions between states are made by the input of your finger. By flicking the switch up it makes the transition from off to on, and by flicking the switch down it makes the transition from on to off. There is no output or action associated with the off state (unless you consider the bulb being off as an action), but when it is in the on state electricity is allowed to flow through the switch and light up your room via the filament in a lightbulb. See Figure 2.1.
因此,有限状态机背后的思想就是把一个对象的行为分解为易于管理的“块”或者状态。例如墙上的电灯开关,就是一种非常简单的有限状态机。它有两个状态:开与关。两个状态通过你指头产生的输入来切换。把开关扳上,它就从关的状态转换到开的状态,把开关扳下,它就从开的状态转换到关的状态。在关的状态没有任何输出或者动作(除非你装灯泡被关掉视为一种动作),但当在开的状态下时电流通过开关并且通过灯泡里的灯丝照亮房间。如图2.1
Figure 2.1. A light switch is a finite state machine. (Note that the switches are reversed in Europe and many other parts of the world.)
2.1 开关是一种有限状态机。(注意:这种开关在欧洲和其它很多国家仍有存在。)
Of course, the behavior of a game agent is usually much more complex than a lightbulb (thank goodness!). Here are some examples of how finite state machines have been used in games.
当然,游戏智能体的行为往往比灯泡要复杂得多。下文是一些在游戏中使用有限状态机的的例子。
本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明。
译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的。任何人不得在未经同意的情况下将英文版和中文版用于商业行为,转载本文产生的法律和道德责任由转载者承担,与译者无关。
· The ghosts’ behavior in Pac-Man is implemented as a finite state machine. There is one Evade state, which is the same for all ghosts, and then each ghost has its own Chase state, the actions of which are implemented differently for each ghost. The input of the player eating one of the power pills is the condition for the transition from Chase to Evade. The input of a timer running down is the condition for the transition from Evade to Chase.
· Pac-Mac里的精灵的行为用有限状态机实现。所有的精灵都有一种Evade(逃避)状态,它们的实现都是一样的;但每一个精灵都一个Chase(追踪)状态,它的实现各不相同。
· Quake-style bots are implemented as finite state machines. They have states such as FindArmor, FindHealth, SeekCover, and RunAway. Even the weapons in Quake implement their own mini finite state machines. For example, a rocket may implement states such asMove, TouchObject, and Die.
· Quake系列的机器人以有限状态机实现。它们FindArmor(找装备)、FindHealth(找补血)、SeekCover(找掩护)和RunAway(逃跑)等多种状态。甚至Quake里实现的武器都带有小型有限状态机,例如一个火箭炮实现的状态就有Move(移动)、TouchObject(触到物体)和Die(死亡)等几种状态。
· Players in sports simulations such as the soccer game FIFA2002 are implemented as state machines. They have states such as Strike, Dribble, ChaseBall, and MarkPlayer. In addition, the teams themselves are often implemented as FSMs and can have states such as KickOff, Defend, or WalkOutOnField.
· FIFA2002之类的运动模拟游戏里的运动员是用状态机实现的,它们有Strike(踢出)、Dribble(带球)、ChaseBall(逐球)和MarkPlayer(盯人)等状态。此外,整个球队通常也是用FSM实现的,有KickOff(发球)、Defend(防守)和WalkOutOnField(不知道怎么翻译,请足球达人告知一下)。
· The NPCs (non-player characters) in RTSs (real-time strategy games) such as Warcraft make use of finite state machines. They have states such as MoveToPosition, Patrol, andFollowPath.
· RTS(实时策略游戏)(例如Warcraft)中的NPC(非玩家角色)也利用有限状态机。它们的状态有MoveToPosition(移动到某地)、Patrol(巡逻)和FollowPath(跟随)等。
Implementing a Finite State Machine
有限状态机实现
There are a number of ways of implementing finite state machines. A naive approach is to use a series of if-then statements or the slightly tidier mechanism of a switch statement. Using a switch with an enumerated type to represent the states looks something like this:
实现有限状态机有许多方式。一个直观的做法就是使用一系列的if-then语句或者稍显整洁的switch语句。使用switch的实现看起来就像这里的代码:
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
}
Although at first glance this approach seems reasonable, when applied practically to anything more complicated than the simplest of game objects, the switch/if-then solution becomes a monster lurking in the shadows waiting to pounce. As more states and conditions are added, this sort of structure ends up looking like spaghetti very quickly, making the program flow difficult to understand and creating a debugging nightmare. In addition, it’s inflexible and difficult to extend beyond the scope of its original design, should that be desirable… and as we all know, it most often is. Unless you are designing a state machine to implement very simple behavior (or you are a genius), you will almost certainly find yourself first tweaking the agent to cope with unplanned-for circumstances before honing the behavior to get the results you thought you were going to get when you first planned out the state machine!
尽管咋一看这个方案还可以,但只要将其应用到比最简单的游戏对象稍为复杂的实际情况下,这个switch/if-then方案就变成了蛰伏在阴影下的怪物——随时都可能突袭你一下。随着大量的状态和条件的增加,那种结构很快就会变得像意大利面条一样,使用程序难以理解,并成为调试梦魇。此外,它不可伸缩并且难以在最初的设计范围之外进行扩展,然而我们都知道,它极为常见。除非你用状态机实现非常简单的行为(或者你是一个天才),否则当你第一次策划状态机的时候,在你“磨合”取得的结果和你希望取得的结果之前,你几乎肯定会发现你的智能体无法应用未考虑到的环境。
Additionally, as an AI coder, you will often require that a state perform a specific action (or actions) when it’s initially entered or when the state is exited. For example, when an agent enters the state RunAway you may want it to wave its arms in the air and scream “Arghhhhhhh!” When it finally escapes and changes state to Patrol, you may want it to emit a sigh, wipe its forehead, and say “Phew!” These are actions that only occur when the RunAway state is entered or exited and not during the usual update step. Consequently, this additional functionality must ideally be built into your state machine architecture. To do this within the framework of a switch or if-then architecture would be accompanied by lots of teeth grinding and waves of nausea, and produce very ugly code indeed.
此外,作为一名AI程序员,你经常需要在某一状态实现一种特殊行为(或者一系列行为),比如在进入或者离开某一状态的时候。例如当一个智能体进行RunAway(逃跑)状态时你希望它把武器抛向空中并尖叫一声“Arghhhhhh(啊)!”当它成功逃脱并转换到Patrol(巡逻)状态,你可能想让它喘口气、擦擦额头然后说一声“Phew(呸)!”这些行为都只在进入和离开RunAway(逃跑)状态时才会发生,而不是整个普通的update(更新)阶段都会出现。因此这些额外的功能必须被完美地集成到你的状态机架构里。在switch或者if-then架构里实现这些将让人难以忍受,产生的代码将非常丑陋,不忍卒读。
本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明。
译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的。任何人不得在未经同意的情况下将英文版和中文版用于商业行为,转载本文产生的法律和道德责任由转载者承担,与译者无关。
State Transition Tables
状态转换表
A better mechanism for organizing states and affecting state transitions is a state transition table. This is just what it says it is: a table of conditions and the states those conditions lead to. Table 2.1 shows an example of the mapping for the states and conditions shown in the previous example.
一个能够更好地组织和进行状态转换的机制是状态转换表。顾名思义这就是一个包含条件和条件导致的状态的表。表2.1是前文代码的状态和条件影射表:
Table 2.1. A simple state transition table
2.1 简单状态转换表
Current State
Condition
State Transition
Runaway
Safe
Patrol
Attack
WeakerThanEnemy
RunAway
Patrol
Threatened AND StrongerThanEnemy
Attack
Patrol
Threatened AND WeakerThanEnemy
RunAway
This table can be queried by an agent at regular intervals, enabling it to make any necessary state transitions based on the stimulus it receives from the game environment. Each state can be modeled as a separate object or function existing external to the agent, providing a clean and flexible architecture. One that is much less prone to spaghettification than the if-then/switch approach discussed in the previous section.
智能体隔一定时间查询这个表格,以使得它能够基于从游戏环境接收到的消息来进行必须的状态转换。每一个状态都能够实现为彼此分离的与智能体不耦合的对象或函数,以提供清晰和可伸缩的架构。这一设计不再那么容易像前文讨论的if-then/switch架构那样容易成为意大利面条。
Someone once told me a vivid and silly visualization can help people to understand an abstract concept. Let’s see if it works…
曾有人告诉我一个明晰而无聊的可视物能帮助人们理解抽象的理论,让我们来看看当它工作时……
Imagine a robot kitten. It’s shiny yet cute, has wire for whiskers and a slot in its stomach where cartridges — analogous to its states — can be plugged in. Each of these cartridges is programmed with logic, enabling the kitten to perform a specific set of actions. Each set of actions encodes a different behavior; for example, play_with_string,eat_fish, or poo_on_carpet. Without a cartridge stuffed inside its belly the kitten is an inanimate metallic sculpture, only able to sit there and look cute… in a Metal Mickey kind of way.
想像存在一个机器猫,它闪闪发亮但是非常可爱,有着金属丝制作的胡须并且在胃部有一个插槽——可以依照状态插入可插入模组。每一个可插入模组都是一段逻辑程序,使得小猫能够实现一系列指定的动作。每一系列动作都编码为不同的行为,如play_with_stringeat_fishpoo_on_carpet。如果没有在小猫的胃部插上可插入模组,它就是一件死物——坐在那里,看起来蛮可受。
The kitten is very dexterous and has the ability to autonomously exchange its cartridge for another if instructed to do so. By providing the rules that dictate when a cartridge should be switched, it’s possible to string together sequences of cartridge insertions permitting the creation of all 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.
这个小猫非常灵巧,并且能够根据指令自动地更换可插入模组。通过给定什么时候该更换可插入模组的规则,它能利用连贯地插入一系列的可插入模组创造所有有趣而复杂的行为。这些与前文讨论的状态转换表相类似的规则被编成程序写入到一个极小的芯片,放置在小猫的头部。芯片与小猫的内部功能通信,以获得处理规则时需要的信息(如Kitty有多饿和它感觉到的好玩度是多少)。状态转换芯片可以用如下的方式编写规则:
IF Kitty_Hungry AND NOT Kitty_Playful SWITCH_CARTRIDGE eat_fish
在每一个时间片都检测表中的所有的规则,从而给Kitty发送指令以切换弹可插入模组。
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_carpet cartridge when it senses poo levels are running dangerously high.
另一可选的方法是把状态转换规则内嵌到状态本身当中。在对机器猫应用这个概念后,可以去除状态转换芯片,直接将规则内置到可插入模组里。例如play_with_string可插入模组能够监视小猫的饥饿度并适时地命令它切换到eat_fish可插入模组。同样地,eat_fish可插入模组能够监视小猫是否已经吃饱,并在感觉到迫切的排泄感时命令它切换到poo_on_carpet可插入模组。
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 external 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 addition, all state objects share a common interface: a pure virtual class named State. Here’s a version that provides a simple interface:
现在来看看在视频游戏中如何实现这一方案。像刚才讨论的小猫的可插入模组,可以封装到一个对象里,其中包含辅助状态转换的的逻辑。另外,所有的状态共享一个通用接口:一个命名为State的纯虚类。这里有个接口简单的实现:
Class State
{
public:
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 Stateclass, and a method permitting a client to change the instance the pointer is pointing to.
现在想象Troll类有一系列的数值成员变量:healthangerstamina等,当然也有相应的接口以查询和设置这些变量值。Troll通过增加一个成员指针变量(指向State类派生类的实例)来增加有限状态机的功能,还提供一个改变指针指向的实例的方法。
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 Executemethod 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 owner’s 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.
当调用TrollUpdate方法时,它以this指针为参数调用当前状态类型的Excecute方法。当前状态可能使用Troll的接口查询它的拥有者,设置拥有者的属性或者产生一个状态转换。换句话说,Troll能依赖当前状态的逻辑作出完整行为。这里有最好的例子表达这一观点,让我们来完成两个状态实现以使得Troll能够在危险时逃跑,或者在安全时睡觉。
//----------------------------------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();
}
}
};
As you can see, when updated, a troll will behave differently depending on which of the states m_pCurrentState points to. Both states are encapsulated as objects and both provide the rules effecting state transition. All very neat and tidy.
如你所见,当调用Update时,Troll的行为依赖m_pCurrentState指向的状态不同而有所不同。两者状态都封装到对象里,并且都提供了产生状态转换的规则。所有这一切都灵巧而整洁。
This architecture is known as the state design patternand provides an elegant way of implementing state-driven behavior. Although this is a departure from the mathematical formalization of an FSM, it is intuitive, simple to code, and easily extensible. It also makes it extremely easy to add enter and exit actions to each state; all you have to do is create Enter and Exit methods and adjust the agent’s ChangeState method accordingly. You’ll see the code that does exactly this very shortly.
这一架构即是有名的状态设计模式,它提供了雅致的状态驱动行为实现。尽管这有违FSM的数学形式,但它符合直觉、易于编码并且容易扩展。它同样也能够极其容易地增加进入和离开状态时的动作;你需要做只是实现 EnterExit方法并相应地改变ChangeState方法。你将可以看到完成这些所产生的代码真的非常短小。
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)
{
//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);
}
}
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方法用以改变位置值。
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";
//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());
}
}
Note here how the Miner::ChangeStatemethod is called using QuenchThirst’s or VisitBankAndDepositGold’s Instancemember, which provides a pointer to the unique instance of that class.
值得注意的是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 modify the behavior of an agent or effect a transition into another state. You may find it useful at this stage to load up the WestWorld1 project into your IDE and scan the code. In particular, check out all the states in MinerOwnedStates.cpp and examine the Minerclass to familiarize yourself with its member variables. Above all else, 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.
我希望前述的三个方法能帮助你理清头绪,现在你应该已经理解了每一个状态怎么改变智能体的行为,又如何从一个状态到另一个状态转换。你用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 one. This can be useful if you have a very complicated design that would be better organized as a collection of several separate smaller state machines. For example, the state machine for a first-person shooter (FPS) like Unreal 2 tends to be large and complex. When designing the AI for a game of this sort you may find it preferable to think in terms of several smaller state machines representing functionality like “defend the flag” or “explore map,” which can be switched in and out when appropriate. The state design pattern makes this easy to do.
正如你所见,状态设计模式为状态驱动的智能体提供了具有非常好的伸缩性的机制,当需要的时候,你可以极其容易地增加新的状态。在你有非常复杂的设计,而且能更好地组织一系列的分散的小状态机的时候,甚至可以替换智能体的整个状态架构。例如,像Unreal2这样的第一人称射击游戏(FPS)有着巨大而复杂的状态机,当设计这种游戏的AI的时候,你将发现它能完美地应用于基于团队而设计的多个小状态机(对应的功能可能是“保旗”或者“探险”),使得能够在需要的时候进行切换。正在状态设计模式使它易于实现。
Making the State Base Class Reusable
编写可重用的State基类
As the design stands, it’s necessary to create a separate Statebase class for each character type to derive its states from. Instead, let’s make it reusable by turning it into a class template.
作为立足之本,有必要构造一个独立的State基类,以供每一个角色类类型获得自身的状态。我们可以通过类模板来使得它可重用:
template <class entity_type>
class State
{
public:
virtual void Enter(entity_type*)=0;
virtual void Execute(entity_type*)=0;
virtual void Exit(entity_type*)=0;
virtual ~State(){}
};
The declaration for a concrete state — using the EnterMineAndDigForNuggetminer state as an example — now looks like this:
下面是Miner类的EnterMineAndDigForNugget状态:
class EnterMineAndDigForNugget : public State<Miner>
{
public:
/* OMITTED */
};
This, as you will see shortly, makes life easier in the long run.
如你所见,它短小精悍。
Global States and State Blips
全局状态和状态闪动(诚心求更好的译法)
More often than not, when designing finite state machines you will end up with code that is duplicated in every state. For example, in the popular game The Sims by Maxis, a Sim may feel the urge of nature come upon it and have to visit the bathroom to relieve itself. This urge may occur in any state the Sim may be in and at any time. Given the current design, to bestow the gold miner with this type of behavior, duplicate conditional logic would have to be added to every one of his states, or alternatively, placed into the Miner::Updatefunction. While the latter solution is accept- able, it’s better to create a global statethat is called every time the FSM is updated. That way, all the logic for the FSM is contained within the states and not in the agent class that owns the FSM.
通常当设计有限状态机的时候,你最后都会在所有状态中出现重复代码。例如,在Maxis开发的流行游戏《The Sims(第二人生)》中,Sim可以感受到内急等生理需要,必须去洗手间解决。无论Sim在哪里、在什么时间,内急都可能发生。根据当前的设计,给淘金者加上这样一种行为,重复的条件逻辑就可能增加到每一个状态,或者放到Miner::Update函数里。下面介绍一个可接受的解决方案,它增加了一个全局状态——供FSM更新的时候调用。这样,FSM的所有的逻辑都包含在状态内,而不在智能体类的FSM里。
To implement a global state, an additional member variable is required:
实现全局状态,需要增加一个成员变量:
//notice  how  now  that  State  is  a  class  template  we  have  to  declare  the  entity  type
State<Miner>*  m_pGlobalState;
In addition to global behavior, occasionally it will be convenient for an agent to enter a state with the condition that when the state is exited, the agent returns to its previous state. I call this behavior a state blip. For example, just as in The Sims, you may insist that your agent can visit the bathroom at any time, yet make sure it always returns to its prior state. To give an FSM this type of functionality it must keep a record of the previous state so the state blip can revert to it. This is easy to do as all that is required is another member variable and some additional logic in the Miner::ChangeStatemethod.
有时智能体从一个状态进入另一个状态,当它退出这个状态时需要回到它的前一个状态,我将之称为状态闪动。例如就像《The Sims》中你可能必须让你的智能体能够在任何时间进入洗手间,之后再回到之前的状态。要实现这样的功能,就必须记录前一个状态,以便在状态闪动时返回。这可以容易地通过增加成员变量和对Miner::ChangeState方法增加一些额外逻辑来实现。
[译注:状态闪动这一概念的确比较难以理解。所以我画了下面这一张图来帮助理解]
译注图1状态闪动示意图
By now though, to implement these additions, the Minerclass has acquired two extra member variables and one additional method. It has ended up looking something like this (extraneous detail omitted):
到现在,为了完成这些额外功能,Miner类已经增加了两个成员变量和一个方法。它最后看起来就像这样(忽略无关元素):
class Miner : public BaseGameEntity
{
private:
State<Miner>*   m_pCurrentState;
State<Miner>*   m_pPreviousState;
State<Miner>*   m_pGlobalState;
...
public:
void ChangeState(State<Miner>* pNewState);
void RevertToPreviousState();
...
};
Hmm, looks like it’s time to tidy up a little.
嗯,的确需要整理一下。
Creating a State Machine Class
创建一个状态机类
The design can be made a lot cleaner by encapsulating all the state related data and methods into a state machine class. This way an agent can own an instance of a state machine and delegate the management of current states, global states, and previous states to it.
把所有的状态有关的数据和方法封装到一个状态机类里有利于精简设计。这使智能体能够拥有一个状态机实例并委派它管理当前状态、全局状态和前一个状态。
With this in mind take a look at the following StateMachineclass template.
现在来看看StateMachine模板类。
template <class entity_type>
class StateMachine
{
private:
//a pointer to the agent that owns this instance
entity_type*          m_pOwner;
State<entity_type>*   m_pCurrentState;
//a record of the last state the agent was in
State<entity_type>*   m_pPreviousState;
//this state logic is called every time the FSM is updated
State<entity_type>*   m_pGlobalState;
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<entity_type>* s){m_pCurrentState = s;}
void SetGlobalState(State<entity_type>* s) {m_pGlobalState = s;}
void SetPreviousState(State<entity_type>* s){m_pPreviousState = s;}
//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<entity_type>* pNewState)
{
assert(pNewState &&
"<StateMachine::ChangeState>: trying to change to a null state");
//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<entity_type>*  CurrentState()  const{return m_pCurrentState;}
State<entity_type>*  GlobalState()   const{return m_pGlobalState;}
State<entity_type>*  PreviousState() const{return m_pPreviousState;}
//returns true if the current state’s type is equal to the type of the
//class passed as a parameter.
bool  isInState(const State<entity_type>& st)const;
};
Now all an agent has to do is to own an instance of a StateMachineand implement a method to update the state machine to get full FSM functionality.
现在所有的智能体都能够拥有一个StateMachine实例,需要做的就是实现一个方法来更新状态机以获得完整的FSM功能。
本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明。
译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的。任何人不得在未经同意的情况下将英文版和中文版用于商业行为,转载本文产生的法律和道德责任由转载者承担,与译者无关。
The improved Minerclass now looks like this:
新实现的Miner类看起来就是这样的:
class Miner : public BaseGameEntity
{
private:
//an instance of the state machine class
StateMachine<Miner>*  m_pStateMachine;
/* 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<Miner>(this);
m_pStateMachine->SetCurrentState(GoHomeAndSleepTilRested::Instance());
m_pStateMachine->SetGlobalState(MinerGlobalState::Instance());
}
~Miner(){delete m_pStateMachine;}
void Update()
{
++m_iThirst;
m_pStateMachine->Update();
}
StateMachine<Miner>* GetFSM()const{return m_pStateMachine;}
/* EXTRANEOUS DETAIL OMITTED */
};
Notice how the current and global states must be set explicitly when a StateMachineis instantiated.The class hierarchy is now like that shown in Figure 2.4.
注意StateMachine实例化后如何正确设计当前和全局状态。图2.4是现在的类层次结构图。

Figure 2.4. The updated design
Introducing Elsa
介绍Elsa
To demonstrate these improvements, I’ve created another project: WestWorldWithWoman. In this project, West World has gained another inhabitant, Elsa, the gold miner’s wife. Elsa doesn’t do much; she’s mainly preoccupied with cleaning the shack and emptying her bladder (she drinks way too much cawfee). The state transition diagram for Elsa is shown in Figure 2.5.
为了验证这些改进,我创建了一个新的项目——WestWorldWithWoman。在这个项目里,WestWorld多了一个人物——Elsa,她是淘金者Bob的妻子。Elsa做的事不多,主要是打扫房子和上洗手间(她喝了太多咖啡)。图2.5Elsa的状态转换图。
Figure 2.5. Elsa’s state transition diagram. The global state is not shown in the figure because its logic is effectively implemented in any state and never changed.
When you boot up the project into your IDE, notice how the VisitBathroomstate is implemented as a blip state (i.e., it always reverts back to the previous state). Also note that a global state has been defined, WifesGlobalState, which contains the logic required for Elsa’s bathroom visits. This logic is contained in a global state because Elsa may feel the call of nature during any state and at any time.
当你把这个项目导入到你的IDE的时候,注意VisitBathroom状态是如何以状态闪动的形式实现的(也就是它如何返回到前一个状态),同样值得注意的是定义了一个全局状态——WifesGlobalState,它包含了Elsa上洗手间所需的逻辑。在全局状态中包含这一逻辑是因为Elsa可能在任何时候都会感到内急,这是天性,哈哈。
Here is a sample of the output from WestWorldWithWoman.
这里是WestWorldWithWoman项目的输出示例。
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...
Well, that's it folks. The complexity of the behavior you can create with finite state machines is only limited by your imagination. You don’t have to restrict your game agents to just one finite state machine either. Sometimes it may be a good idea to use two FSMs working in parallel: one to control a character’s movement and one to control the weapon selection, aiming, and firing, for example. It’s even possible to have a state itself contain a state machine. This is known as a hierarchical state machine. For instance, your game agent may have the states Explore, Combat, and Patrol. In turn, the Combat state may own a state machine that manages the states required for combat such as Dodge, ChaseEnemy, and Shoot.
夸张点说,这相当了不起啊。你能够用有限状态机创造非常复杂的行为,到底有多复杂仅受限于你的相像力。你无需限制你的游戏智能体只能有一个有限状态机,有时候你可以使用两个FSM来并行工作:一个控制角色的移动,而另一个控制武器选择、瞄准和开火。你甚至可以创造包含状态机的状态机,即分级状态机。例如你的游戏智能体可能有Explore(探测)、Combat(战斗)和Patrol(逻辑)等状态,而Combat(战斗)状态又可以拥有一个状态机来管理Dodge(躲避)、ChaseEnemy(追逃)和Shoot(射击)等战斗时需要的状态。
本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明。
译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的。任何人不得在未经同意的情况下将英文版和中文版用于商业行为,转载本文产生的法律和道德责任由转载者承担,与译者无关。

状态驱动的游戏智能体设计相关推荐

  1. 状态驱动的游戏智能体设计(下)

    本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明. 译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的.任何 ...

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

    本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明. 译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的.任何 ...

  3. 状态驱动的游戏智能体设计(上)

    本文由恋花蝶最初发表于http://blog.csdn.net/lanphaday,欢迎转载,但必须保持全文完整,也必须包含本声明. 译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的.任何 ...

  4. 游戏程序开发:状态驱动的游戏智能体设计 (二)

    转自: 状态驱动的游戏智能体设计(一) http://edu.gamfe.com/tutor/d/30601.html 状态驱动的游戏智能体设计(二) http://edu.gamfe.com/tut ...

  5. 通过深度Q网络DQN构建游戏智能体

    目录 什么是深度Q网络(DQN) DQN的基本结构 DQN的关键技术 用Python和Gym实现DQN 算法优化 1. 网络结构优化 2. 训练策略优化 3. 超参数优化 欢迎来到我的博客,今天我们将 ...

  6. 软件学院20周年院庆系列讲座 | 可微仿真与机器人智能体设计

    可微仿真与机器人智能体设计 来源:清软小研

  7. 2022年第十七届研电赛报名|安谋科技(Arm China)命题:基于特定开发平台的理性智能体设计

    "兆易创新杯"第十七届中国研究生电子设计竞赛(以下简称"研电赛")现已正式开赛,企业命题已在官网发布,现官网已启动报名啦~ 安谋科技作为研电赛多年的合作单位及& ...

  8. 传奇黑客成『吹哨人』,推特麻烦了;谷歌20+技术学习路线;Python数据科学电子书;游戏智能体开发平台;前沿论文 | ShowMeAI资讯日报

    ShowMeAI日报系列全新升级!覆盖AI人工智能 工具&框架 | 项目&代码 | 博文&分享 | 数据&资源 | 研究&论文 等方向.点击查看 历史文章列表, ...

  9. 多智能体强化学习及其在游戏AI上的应用与展望

    近年来,人工智能技术在很多领域都取得了亮眼成就,并逐步从感知智能向决策智能迈进.强化学习是实现决策智能的重要路径,而现实世界中往往存在着多智能体的交互,也催生了多智能体强化学习的发展.这篇文章主要对多 ...

最新文章

  1. python读写文件绝对路径_[Spark][Python]对HDFS 上的文件,采用绝对路径,来读取获得 RDD...
  2. 部署时服务端Excel的COM设置
  3. 数据库面试题【七、InnoDB索引和MyISAM索引的区别】
  4. Spring+Hibernate+Atomikos集成构建JTA的分布式事务--解决多数据源跨库事务
  5. 8月29日见!卢伟冰:Redmi首款互联网电视将采用70英寸巨屏
  6. JBox - 模态窗口,工具提示和消息 jQuery 插件
  7. java string debug_java 中 string 对象
  8. Ubuntu配置静态IP以及interfaces配置不生效问题解决
  9. 读《南怀瑾讲人生哲理》
  10. QT3D场景的快速绘制
  11. h5页面如何预览excel文件_如何使用JavaScript实现前端导入和导出excel文件?(H5编辑器实战复盘)...
  12. 参考线--深入了解字体
  13. Dijkstra算法指定任意两点距离(邻接矩阵法)
  14. ESP8266—01模块的3种工作模式
  15. CORDIC算法原理详解及其Verilog实现
  16. 分享国产32位单片机灵动微MM32F主流型和超值型
  17. IPFS Java实现
  18. B2B行业网站销售方式及销售工作内容
  19. axure的html按钮设置背景,Axure格式和样式功能详解
  20. (十)机房收费系统操作手册

热门文章

  1. [WARNING] The POM for com.tenyears:base-common:jar:1.0 is invalid, transitive dependen
  2. 【C语言--字符数据的输入输出】
  3. 通过命令行启动Unity并激活ZSpace和OpenGL(从新账户转)
  4. 2021年中国稀土永磁材料行业现状及政策分析,高性能钕铁硼未来应用前景广阔「图」
  5. 台式网卡计算机,台式机网卡,台式机网卡在哪里
  6. 宽平稳和严平稳的区别
  7. 4路/8路模拟CVBS摄像头TW6865/TW6869 i.MX6四核工业级Android、Linux、STM32、NXP
  8. 斜拉桥主梁的预应力混凝土横梁计算midas
  9. GoTo Group上市首日市值达315亿美元;星巴克推出“明日的拿铁”创意饮食;华住出台“专项融资扶持计划” | 美通企业日报...
  10. Vue学习第三天路由Vue Route(9月6号)