设为首页 - 加入收藏 焦点技术网
当前位置:首页 >


导读: //原文链接:http://www.gamasutra.com/view/feature/1862/creating_all_humans_a_datadriven_.php?page=3 A Gamasutra featured article Creating All Humans: A Data-Driven AI Framework for Open Game Worlds创建人类世界:...。。。




A Gamasutra featured article


Creating All Humans: A Data-Driven AI Framework for Open Game Worlds



Populating an entire game world with characters that give an impressionof life is a challenging task, and it's certainly no simpler in an openworld, where gameplay is less restricted and players are free to roamand experience the world however they choose. The game engine has to beflexible enough to react and create interesting scenarios wherever theplayer goes. In particular, the demands on the AI are different from alinear game, requiring an approach that, while using established gameAI techniques, emphasizes a different aspect of the architecture.



This article discusses the data-driven AI architecture constructed for Pandemic Studios' open world title Destroy All Humans 2.It describes the framework that holds the data-defined behaviors thatcharacters perform, and how those behaviors are created, piecedtogether, and customized.


The premise of an open world game with sandbox gameplay is to giveplayers the freedom to do what they want, the freedom to create theirown game within the world the developers provide. Their play is notlinear, which is fantastic for a sense of immersion, but reduces theability of the game developer to control, limit, and pre-scriptscenarios that the players encounter.


The AI code needs to be built on a foundation that is flexible enoughto respond to any eventuality. It needs to handle a domain of gameplaythat is broader in scope than a linear title and react to situationsthat might not have been anticipated. In effect, the AI needs to have astrong emphasis on breadth of behavior over depth. That is, thearchitecture must promote the ability to create large numbers ofbehaviors and make applying them to characters as easy as possible.


One solution to this challenge is to make the behaviors data-driven.They should be created without requiring changes to code, piecedtogether and reused as shared components, and substituted out forspecialized versions. Ideally, the developer should be able to tweaknot only the settings of a behavior, such as how long a timer lasts orhow aggressive an enemy is, but also the very structure of the behavioritself. For example, what steps are needed to complete a given task ordefine how those steps are performed? By allowing our behaviors to fitinto multiple situations, be reusable, and be quick to create andcustomize, we can more effectively create all the actions that thecharacters will need to give the game life.



Behavior Foundation: Performing Tasks with Sub-Tasks


The basis for the behavior system in Destroy All Humans 2is a hierarchical finite state machine (HFSM), in which the currentstate of an actor is defined on multiple levels of abstraction. At eachlevel in the hierarchy, the states will potentially use sub-levelstates to break their tasks into smaller problems (for example, attackenemies is at a high level of abstraction and uses the less-abstract fireweaponbelow it to perform part of its function). This HFSM structure is acommon method used in game AI to frame a character's behaviors. It hasseveral immediate benefits over a flat FSM. (For current informationabout HFSMs, see Resources)

毁灭人类2中的行为系统的基础是树状的有限状态机hierarchical finite state machine (HFSM),该技术下角色的当前状态是通过多层不同程度的抽象完成的,该行为树状体系中的每一层 其对应属性都会使用其子节点的抽象来构建更高一层的行为状态。例如:攻击敌人行为是一种高层的行为抽象,其子节点则可能定义了开火这样的行为抽象。HFSM架构是游戏AI中架构游戏角色行为AI的一种常见手段,和平展的状态机相比有许多优点。(关于HFSM的更多信息可以参见附录)


In our implementation, each state in the HFSM is called a behaviorand makes up the basic architectural unit of the system. Everythingthat characters can do in the game is constructed by piecing togetherbehaviors in different ways that are allowed by the HFSM. A behaviorcan start more behaviors beneath it that will run as children, eachperforming a smaller (and more concrete) part of the task of theparent. Breaking each task into smaller pieces allows us to reap a lotof mileage out of the behavior unit-reusing it in other behaviors,overriding it in special cases, dynamically changing the structure, andso forth-and spend more time making the system intuitive and easy tomodify.



Starting children. There are many ways to break a taskinto smaller pieces, and the correct choice ultimately depends on thetype of task. Does the task require maintaining certain requirements,performing consecutive steps, randomly performing an action from alist, or something else? In our implementation, we allow severalmethods of breaking down a behavior into smaller pieces by allowingdifferent ways to start children behaviors.

启动子行为. 有很多种策略将行为分解成更小的子行为,正确的方法取决于任务的类型,例如任务需要满足特殊的需要么?连续的执行各个子步骤还是随机的执行子步骤,或者其他?我们的实现中,我们通过允许以不同的方式启动一项任务这样将一项任务分解为各项子任务


Prioritized children. The first and most common way tostart children is as a list of prioritized behaviors. Behaviors thatare started as prioritized will all be constructed at once (memory isallocated for them and they are added as children of the parent) andset into a special pending mode. (See Figure 1.)

优先的子行为. 最常见开始子行为的方式是通过列出优先排序后的行为完成的,优先启动的行为马上就会被创建(内存分配后被添加为父行为的子状态节点)然后被设置为一种特殊的悬挂模式(见图1)

FIGURE 1 A combat behavior starts prioritized children,which in turn starts more prioritized children. Pending behaviors areorange and active ones are blue.
图1 一个战斗行为启动了一个优先子状态,该子状态启动了更多的优先子状态

When a behavior is in pending mode, it is not updated; instead, itwaits until the behavior itself decides it can activate, based on itsown settings. When activated, the behavior will in turn start anychildren it has. Only active behaviors can have children, so a pendingbehavior will wait before starting its children.

As a rule, only one active behavior can run beneath a given parent,which creates a problem: what to do when multiple behaviors are able torun. We need to set a priority to determine which sub-task is moreimportant. When starting children as prioritized, we define theirpriority implicitly based on the order the behaviors are added to theparent. The earlier a behavior is listed, the higher its priority (seeIsla in Resources).


This solution avoids the problem that would have resulted had wedetermined priority strictly by number, such as priority-creep, inwhich priorities become larger and larger, trying to trump the rest.Here we localize the priority definition, so it's only relative to thesmall subset of behaviors that are started as siblings.


In the example above, we see a hierarchy of behaviors and the children available under each, with fire active as a child of attack, which in turn is active as a child of combat. If the currently pending behavior (dodge) were to determine that it needs to start (when the NPC detects it is being fired upon), it will interrupt its active sibling (attack) and revert it to pending, which in turn will delete all children of attack. Once no other active sibling behaviors are running, dodge may begin.

在上面的例子中,我们看见了行为体系树和它可用的状态,FIRE是ATTACK行为的子行为并被激活,而ATTACK又是COMBAT的子行为,如果当前的挂起状态闪避决定它要被激活(例如NPC检测到了攻击行为),那么它就会打断其兄弟行为 攻击的执行,将其转换到挂起状态,这一操作会导致攻击状态的所有子状态全部被删除,一旦没有其他的兄弟行为正在运行那么 闪避状态就可以转入运行了


This method of applying priority implicitly works well in mostcases, but sometimes the importance of the tasks cannot be describedwith a simple linear ordering. To handle cases in which a behavior isdoing something important and should not be interrupted by non-criticaltasks (even if they're higher priority), we can implement a featurecalled "can interrupt." Essentially, an active behavior may receive aboost in priority, preventing interruption during specific parts of itsexecution.



With this boost, priorities can be specified in ways more complex thansimple linear ordering. For example, while melee is listed at a higherpriority than dodge in Figure 1, it should still be allowed to finish its animation even if dodge decides to start-by giving it a boost in priority while running, we prevent dodge from cutting it off mid-animation.



Sequential and random children. Other ways to startchild behaviors are known as sequential and random. Behaviors that arestarted sequentially are run in the order that they are listed. If thefirst can run, it will do so until it completes on its own, followed bythe second, and so on. When the last behavior in the sequence finishes,the parent finishes as well. For a group of child behaviors startedrandomly, only one will be chosen to run, and the parent will completeonce its child finishes.

顺序和乱序的子状态. 其他的启动子行为的方法被称为顺序和乱序. 顺序启动的行为并按照其列出的顺序执行完,这样的行为执行成为顺序执行,如果第一个行为可以执行,那么它启动并且完成它的任务,然后第二个执行以此类推,当序列中的最后一个行为执行完毕那么该序列行为也执行完毕。如果一个群组中的子行为任意的开始,只有一个会被选中执行,当该选中任务执行完毕后,改乱序执行也就执行完毕


Non-blocking children. Behaviors can also be startedas non-blocking, in which case they may activate even if there arealready other active behaviors running beneath the parent. They existoutside the prioritized list. These behaviors are useful for performingtasks that work simultaneously with others, such as firing whilemoving, or playing a voice over on a specific condition, or activatingand deactivating effects. Generally, anything performed by anon-blocking behavior must not interfere with any other siblingbehaviors that might be running, since a non-blocking behavior willonly be interrupted when its parent is deactivated (and never by asibling behavior).

非阻塞的子行为 行为同样可以有非阻塞的行为,即使已经有其他激活的行为在其父行为下运行,该非阻塞行为也可以运行,非阻塞行为存在于优先队列之外。这样的行为对于执行那些并发的行为是非常有用的,例如行进的时候开火,例如在特定的情况下发出声音,又如激活和阻塞效果。总之,任何非阻塞行为进行任何动作不能妨碍当前正在运行的行为的执行,只有其父行为被阻塞,非阻塞行为才会被阻塞;非阻塞行为永远不会被其兄弟行为阻塞


By using various combinations of these methods up and down the tree,we can form decisions and task handling over multiple levels that wouldbe difficult to define in a single behavior.



Building New Puzzle Pieces构建新的解谜部件

From this framework, we have defined the basic unit that will be usedto construct HFSMs: the behavior. Now, by making behaviors sufficientlydata-driven, we can expose not only their values and settings formodification, but the structure of the actions they perform as well.(See Figure 2.)


FIGURE 2 When either of this behaviour unit's preconditions aresatisfied, it will activate, passing the stimulus source parameter toits two children. Since the children are prioritized, thehigher-priority flee child will run whenever it can, allowing cower to activate only when it fails.
图2 当任意两个该单元被激活的前置条件满足的时候,该行为就会被激活,同时将刺激源参数传递给其子状态,由于其子状态是优先排序的,因此当条件满足的时候只有‘逃跑’状态会被激活,当逃跑失效的时候,‘退缩’状态才可能启动

Each behavior is self-contained. Everything about it is determinedwithin the behavior itself: when it can activate, when it can no longerrun, what interrupts it, and what it does on activation, deactivation,and update. Most significantly, though, it defines what children itstarts. Each of these features of a behavior are set within a .behaviorfile, one per behavior, which is parsed and read in with the rest ofthe game data.



Once a behavior is created in a pending state, its duty from then onis to decide when it can activate, since it contains its own activationrequirements. These requirements are defined by attaching a list ofprecondition objects to the behavior. A precondition is a set of rulesthat may be evaluated to be true or false after checking conditionsfrom the world. Game events, the health level of the character, andcommands issued from a squad leader are all conditions that cancontribute to activating a behavior, and a precondition can beconfigured to query any of them.



The settings that define a precondition are stored in a separate fileand can be reused by other behaviors as well. This allows us toconstruct a library of preconditions that are easily selected for a newbehavior simply by listing them in the behaviors configuration file.



Once a behavior is activated, it can perform its actual purpose,which in most straightforward cases is data-defined as well. Anybehavior can start an asset on the character (play an animation, sound,or effect) or start more children without requiring any code sidechanges, which gives us tremendous flexibility in being able to quicklyflesh out the structure of new behaviors. For more complicated actions,we use code-supported behaviors.



A code-supported behavior has all the configuration settings availableto a base-level behavior, but with some extras available that plug intoa corresponding module created in code. Fireweapon, pathfollow, and meleeare examples of behaviors that have a code side associated with them,performing actions that would be too specific to generalize and makeavailable for all behaviors. In fireweapon we have, forexample, settings for "delay between shots" and "shots to fire in aburst," that would be useless on most other behaviors. Thesecode-supported behaviors fit into the tree like any behavior and aretypically found as the leaves of an HFSM, used at the bottom level aschildren of more abstract behaviors. Generally, the role of thehigher-level behaviors is to separate out objects and tasks that needperforming on those objects, and then start children to make themhappen.

代码端支持的特殊性为具有基本级别行为的所有设定,不同之处在于它额外的需要代码端创建的一个相应的特殊的插入模块.武器开火,行进路径,逃跑都是具有代码端支持的特殊行为,这些行为执行时候具有一般行为所不具有的特殊性。例如在‘武器开火’行为中我们具有 开火延迟 突然开火这样对立的突发状态,这些状态对于其他大多数的行为来说是没有意义的。这些特殊的行为也可以整合进原来的行为状态树而且通常作为叶子节点出现,也即作为最具体的行为出现。总而言之更高级别抽象行为的任务就是要分理处这样的行为并决定作用于哪些对象,并且在指定的时候启动这些子行为。


However, because the objects we're dealing with aren't defined untilthe game is actually running (such as the current target of a characterduring a battle or the last person to damage the player), we need a wayto reference and make decisions about those objects within ourbehaviors. We need a parameter system.


Connecting Objects to Behaviors


Giving behaviors a list of settings in their configuration file addsa lot of generalization to the system, but it does not allow objects tobe dynamically manipulated at run time, responding to arbitrarysituations. For example, the target of fireweaponcannot be known when that behavior is being written, as it will vary.Each behavior must be able to understand and make decisions aboutarbitrary game objects in the world, decided at run time.

给予行为配置文件一系列设定的做法是的系统的兼容性大大提高,但是它并没有使得行为的作用对象在运行时得到处理,例如 ‘开火’行为的作用对象无法获知,因为该对象在运行的时候是不断变化的。每一个行为必须能够理解并决定在游戏运行时候的任意的游戏对象。


The solution we used in Destroy All Humans 2 was to allowparameters to be passed from a parent to its children when the childrenwere created, letting those behaviors query and manipulate the objectpassed in as a parameter or pass it along to their own children.

我们在游戏 毁灭人类2 中用到的解决策略是当子行为创建的时候允许参数能够从父行为传递到子行为。允许这些被创建的子行为查询或者使用这些游戏对象或者它们将其传递给它们自己的子行为


Any behavior that needs to accept a parameter defines a slot for it,which must be filled by whatever parent behavior activates it as achild. From there, the variable in that slot can be used in a number ofways: it can be passed on to its own children, sent an event ormessage, or, for code-supported behaviors, made available to the codeside. Pathfollow will seek to the object passed as its first parameter, for example, while melee will swing in the direction of its first parameter.

任意需要接受参数的行为就定义了一个插槽,这些插槽需要其父行为在创建他们的时候填充。从那时起这些插槽就可以派上不同的用处了:可以传递给自己的子行为,发送一个事件或者行为,或者对于代码支持的行为,这些对象就可以为代码插件所用。 例如寻找路径行为将寻找传送给它的第一个对象,而逃跑则会扔掉传递给他的第一个参数


With this addition, the states in our HFSM can essentially send objectsalong their transitions (in our case, between parent and child),plugging them into other behaviors that expect them and use them as atarget of their functionality. It's a way to blend some script-likefunctionality into the more rigid structure of an HFSM, giving extraflexibility without sacrificing organization.



Between concrete and abstract are partially-implemented behaviors.The ability to bypass parameters opens up a lot more ways to usebehaviors, and many uses of parameters became quite common in ourimplementation. To help us with some of the standard ways in whichparameters could be used, a few partially-implemented behaviors werecreated that would handle some standard tasks on the code side, eventhough they themselves were not full code-supported behaviors. That isto say they didn't belong as leaves of the HFSM; they were just helperbehaviors that were still abstract until they were given the data thatconfigured their actions.



The most commonly used partially-implemented behavior was rangetest,which would accept a target parameter that it would track and storethroughout its existence, using it to make a number of decisions.

Because parameters are passed as soon as a behavior is created, evenbefore it is activated, we can use them to determine whether we shouldactivate it. In the case of rangetest, extra settings were available totest the range of the passed target (the first parameter) anddeactivate it if it left a second range.



This functionality proved generally useful. Because the behavior canserve as the parent for both data-defined behaviors and code-supportedbehaviors, like pathfollow or melee,it is able to break tasks into responses based on properties of objectsin the world and provide failcases implicitly when targets are too faraway. By nesting rangetest behaviors and applying them to differentobjects at different times, we can define complex decisions aboutdynamic objects with just a few simple pieces.



Protect Behavior保护行为

The behavior protect illustrates this point: when it is activated, it'scued to protect as its first parameter. It then starts a list ofprioritized behaviors, the first of which is to stay at all timeswithin a reasonable distance of the character being protected,represented by approach. Approach is implemented as a rangetest behavior that activates when the target is too far away, calling into the movement system to run closer. (See Figure 3.)

所谓的行为保护阐述了这样一个特性:当它被激活的时候,它将保护其第一个参数,然后它会启动一系列优先排序的行为,首先是要全时间驻留在一定范围内使得角色得到保护,这样的行为也被称为接近,‘接近’行为被实现为‘范围测试’行为的繁华,这样当一个目标距离本体太远的时候,它将会唤醒移动系统接近目标 (见图3)


Second, a character in protect should engage any nearby enemies, whichin our implementation are detected by a query to a target selectormodule running on the character (modules like these run separately fromthe behavior system). Combatis started in order to maintain this requirement (another rangetestbehavior), and if an enemy is too close, it will activate and startchildren to engage the target.

If neither approach nor combat are able to start (because their preconditions are not satisfied), wander will activate by default, since it is the lowest priority behavior and has no preconditions.

The character will then patrol randomly around the actor being protected.


By creating behaviors that accept parameters at runtime, we are able todefine a structure entirely in data to perform two very differenttasks, while acting on multiple entities involved in those tasks-theperson who should be protected and the enemies that pose a threat.


Sharing and Reusing Behaviors


A distinct advantage of defining a character's actionshierarchically is the ability to reuse, replace, and remove behaviorsfrom the hierarchy easily and intuitively.



Every behavior in this system (except for the simplest) has itssettings defined in its .behavior settings file. However, when abehavior spawns its children, it does not start them using the filenamedirectly; it uses an alias. Any character that starts behaviors willdefine a list of aliases, mapping each to a .behavior file. Byabstracting behavior referencing by one layer, we are able to customizeand reuse the behavior components by changing how that mapping isdefined.



One of the down sides of using a flat FSM instead of an HFSM todrive your characters is the inability to quickly modify an existingbehavior into a new one by changing only one aspect of it. To solvethis in a FSM, you would need to recreate the entire state machineagain, save the one difference.



We avoided this problem with our implementation by adding the abilityto swap out behaviors at any layer in the hierarchy. By changing themapping of a behaviors alias for a given character, you can swap outthe actual behavior that's created when it references that alias,regardless of where the behavior sits in the hierarchy. This featurewas commonly applied to give characters customizations on the behaviorsthat were widely shared, such as giving ninjas a special variant of melee or fireweapon. (See Figure 4.)

通过增加这样的一种功能:即换出行为树体系中的任意一层的能力,我们避免了这个问题.通过改变对应角色的映射行为别名,你可以换出当前实际的行为而不管该行为位于体系中的某一层,这个功能使得定制角色的行为的时候能够大量的共享行为,例如给予忍者一种特殊的‘逃跑’和‘交战’行为 (见图4)

Common combat behaviors map通用战斗行为映射
MapAlias("Combat", "pedestrian_combat.behavior")
MapAlias("Melee", "pedestrian_melee.behavior")
MapAlias("PathFollow", "pedestrian_pathfollow.behavior")
MapAlias("Patrol", "pedestrian_patrol.behavior")
MapAlias("Protect", "pedestrian_protect.behavior")
MapAlias("Approach", "pedestrian_approach.behavior")...

Ninja behaviors map忍者行为映射
INCLUDE("Common Combat Behaviors Map")
OverrideAlias("Melee", "ninja_melee_claw")
OverrideAlias("FireWeapon", "ninja_throw_shuriken")
OverrideAlias("Flee", NONE)
FIGURE 4 A common behaviour map, and a specialized one for ninjas,who have different melee and firing methods, and never flee, are listed.
图4 通用的行为映射,以及针对忍者的行为的泛化,它们有不同的逃跑和开火方法,因此没有列出

Aliases are typically defined in the file associated directly with theactor, but we gain some extra flexibility by allowing aliases to beredefined anywhere in the behavior tree as well. For example, considerthe protect behavior described earlier. There are actually two variantsof the approach behavior that are used as children within the HFSM, oneto follow the character they are meant to protect and another forapproaching enemies to engage in combat.



Conceptually, these behaviors do the same thing (move the character toa target), but the way they do it is different. For example, the combatvariant can strafe around and move in a more aggressive way, while theprotect variant simply runs to the target's location of the characterit is sent to protect.

We can allow this customization in separate branches with theoverride alias setting that any behavior can contain. When this settingis present in a behavior, it triggers any child behaviors that it orits descendants activate to instead use the new mapping. For example,we can override approach in the combatbranch to use a more aggressive version, while the opposite branchseparately overrides it to use a less aggressive version. Now, wheneverany of the descendants activate that alias, it will filter up througheach parent in the HFSM, checking in turn for a new alias mapping untilit finds the ones we assigned.



Another simple method that was very successful in customizingbehaviors was adding the ability to not just customize, but removeentire behaviors from beneath a parent. This was accomplished bymapping a behavior alias to a special "none" keyword, which whenencountered would simply not start the behavior. This was very usefulin producing variants of enemies that didn't throw grenades or didn'tdodge, for example.


AI for the Masses

In a game that features sandbox-style play, the AI needs to provideenough different and interesting characters to interact with in theworld, and the size of the world doesn't have to get very big before itbecomes unfeasible to hard code them all. Sometimes, even exposingbehavior settings isn't enough-the structure of the tasks and subtasksmust be exposed as well, in a way that's powerful but also simple touse.



In Destroy All Humans 2, the choices we made regarding AIarchitecture were intended to promote those traits. It's an adaptable,puzzle piece-like system in which functions are exposed in a genericway. It attempts to skirt the line between behaviors that are entirelyhard coded and ones that are entirely script-defined, left to thetechnical designers to manage.



Instead, a system like Pandemic Australia's packages that complexityand exposes it as individual pieces to be fit together at variouslevels of abstraction. As a result, we generate extra flexibility inthe way those pieces can be reused and expanded, multiplying theirusefulness and bringing us a step closer to populating a virtual worldwith virtual life.

相反的,想PANDEMIC AUSTRALIA的系统封装了复杂性并且暴露了各个层次的行为的抽象体系,结果我们采用了一种新的体系使得这些行为个体能够更好的被复用和繁华,这样就大大提高了这些行为的有用性,使得我们距离真实的虚拟世界又迈出了重要一步。

[EDITOR'S NOTE: This article was independently published by Gamasutra's editors, since it was deemed of value tothe community. Its publishing has been made possible by Intel, as a platform and vendor-agnostic part of Intel's Visual Computing microsite.]


Alt, G. "The Suffering: A Game AI Case Study," in The Proceedings ofthe Nineteenth National Conference on Artificial Intelligence, July2004.

Atkin, M. et al. "Hierarchical agent control: a framework for definingagent behavior," in The Proceedings of the Fifth InternationalConference on Autonomous Agents, May 2001. pp. 425-432.

Freed, M. "Managing Multiple Tasks in Complex, Dynamic Environments,"in The Proceedings of the Fifteenth National Conferences on ArtificialIntelligence, July 1998.

Fu, D. and Houlette-Stottler, R. "The Ultimate Guide to FSMs in Games," in AI Game Programming Wisdom 2. Hingham, Mass.: Charles River Media, 2003.

Isla, D. "Handling Complexity in the Halo 2 AI," in The Proceedings ofthe 2005 Game Developers Conference, San Francisco, March 2005.

Yiskis, E. "Finite-State Machine Scripting Language for Designers," in AI Game Programming Wisdom 2. Hingham, Mass.: Charles River Media, 2003.

(编辑: zinking3)