在设计一款游戏的时候,如果我们是玩家,是希望自己能够操作角色畅玩游戏的。在一款MMORPG游戏中,大部分的实际游戏角色,是需要玩家来操作的,通过在游戏大世界相互完成游戏中的任务等等来体验游戏。在大世界交互场景中,不可避免的会有怪物的存在,也会有NPC,某些策划布置的场景角色怪物等等。同时,在常见的MMORPG游戏中,自动战斗是不可避免的一个功能。这些表面的角色或者功能的背后,其实就是游戏的AI机制。
说到游戏的AI,和现在比较流行的人工智能有一定的区别。现在的人工智能是全面的AI,包含数据收集,数据分析,行为支配等等操作。而游戏中的AI,属于比较特定的一类,主要是为战斗系统提供的一种机制。现在主流的机制主要分为两类:状态机和行为树。这两类机制,各有其优势,在实际的应用中需要结合实际的需求来采用。我分别在两款游戏中采用了这两种机制,下面也算一个个人的总结吧。
一、状态机
状态机这种机制,主要是针对战斗状态不太复杂的应用场景。如果我们的角色,在整个游戏过程中,只有少数几个可以列举的状态,比如:站立,寻敌,攻击,行走,受击这样可以枚举出来的几个状态,那么,我们可以考虑用状态机的方式实现这几种状态的相互切换。具体的关于状态机的实现,我这儿就不再赘述了,网上有很多详细的讲解,也有比较多开源的代码,大家可以参考github上的代码类似的实现自己的状态机。我就写几点个人的一些总结吧。
1、从基础搭建开始。
网上比较多的状态机,都是结合其实际的应用场景,包含一些特定的实现设计在里面,我们在搭建自己的状态机的时候,可以从一个细小的状态开始搭建,先搭建一个节点,然后依据同样的设计,搭建类似的多个节点;
2、保持节点的简洁性和去耦性。
通常,在最初的节点搭建完后,是不可能一劳永逸的,后续反复的修改,都会对这些节点进行特定的修改和某些特定规则的设计。
举个列子,射击这样一个状态节点,A英雄的设计是单发,B英雄是连续10发,那么如何保证对这两个英雄的同一个射击状态的兼容?一种设计方式,是将当前的英雄作为一个参数进行传递,那么在执行的时候,就读取当前传入参数的英雄的具体配置,进行相关的射击动作。把射击相关的逻辑封装在该英雄对应的攻击执行器中,那么其具体执行的射击单发还是射击10发,就可以作为一个循环执行,单发循环一次,10发循环10次。
以前我的设计实现思路,是针对单一的英雄,特定重载实现其对应的射击节点,这样也算一种解决方法,只是这样的设计有一个弊端,就是随着英雄类型的增多,其对应的特定实现会不断的扩大,相应的重载实现的版本会增多。如果设计思路和文本清晰,那还可以维护,如果设计思路不清晰,那么就会带来维护的消耗。
3、做好节点的剥离,避免节点耦合太多。
通常状态机是用在比较简易的游戏类型中,角色本身的状态类型不会太多,那么对应的状态节点应该避免相互之间的耦合和功能交叉,状态的切换可以走相同的属性设置来实现交互。如果随着设计的变化,状态实现越来越多,可以考虑用行为树来实现,避免相互之间的耦合太多。
二、行为树
在RPG游戏中,行为树是用的比较多的一种AI机制。就其本质而言,行为树是状态机的一种更高封装的实现。我个人的理解,行为树就是将特定的行为节点进行封装,做成叶子节点,这样可以实现任意节点的拼接。想象一下,如果我们把每个特定的状态机进一步的封装,做成一片片叶子,这样我们在搭建树的时候,就可以收集特定的叶子,来搭建特定的树,最后得到特定功能的行为树,这就是我对行为树的一个简易理解:D
行为树的节点的执行结果,可以用枚举的方式列出:
RunningStatus =
{
INVALID = 0,
SUCCESS = 1, --执行成功
FAILURE = 2, --执行失败
RUNNING = 3, --执行中
}
一棵树的搭建,不能只有叶子,还需要有枝干,这就需要行为树中的一些特定的节点来搭建这棵树,行为树主要有以下几种主干节点,举例说一下:
4种Composite节点
1、Sequence节点: 顺序执行主干节点,其执行的基本逻辑是:对其下面的所有叶子节点,均顺序执行,直到遇到返回为FAILURE的节点。(我对这种节点的理解,就是串联电路的思维,电流顺序走过每个电阻(叶子),如果遇到第一个无法流过的电阻,则返回,否则会一直流过去,直到所有电阻都流过)
2、Selector节点:选择执行主干节点,其执行的基本逻辑是:对其下面的所有叶子节点,顺序执行,直到遇到第一个返回为SUCCESS/RUNNING执行结果的节点。观察上面的Sequence节点,和Selector节点的区别仅仅在于选中的节点的返回结果的不同处理。
3、Parallel节点:并行执行主干节点,其执行的基本逻辑是:对其下面的所有叶子节点,各自执行一次,如果返回结果不为RUNNING,则分别统计SUCCESS和FAILURE的结果。当前Parallel节点的返回条件可以分为 全SUCCESS或者任意一个SUCCESS,全FAILURE或者任意一个FAILURE。在执行完所有叶子节点后,可以对比其执行的SUCCESS和FAILURE节点的个数,和其设置的返回结果对比,如果满足则返回SUCCESS(SUCCESS条件)或者FAIULURE(FAILURE条件),或者返回RUNNING。
4、Detector节点:检测执行主干节点,其执行的基本逻辑是:对其下面的所有叶子节点,逐个执行,如果返回的不为RUNNING,则检测,如果为SUCCESS,则接着执行,为FAILURE,则执行结束。即遇到第一个返回为FAILURE的节点,就结束执行。
6种Decorator节点
1、Invert节点:反转装饰节点,其执行的基本逻辑是:只有一个叶子节点,如果返回为SUCCESS,则返回FAILURE;如果返回为FAILURE,则返回SUCCESS;否则返回RUNNING
2、Sucees节点:Success装饰节点,其执行的基本逻辑是:只有一个叶子节点,执行后,直接返回SUCCESS
3、SuccessToRunning节点:根据名字就可以知道,其叶子节点在执行完后,如果返回为SUCCESS,则修改其结果为RUNNING,其他的状态不改变,返回最终执行状态给上一层。
4、FailureTORunning节点:类似于上一个节点,其叶子节点在执行完后,如果返回为FAILURE,则修改其结果为RUNNING,其他的状态不改变,返回最终执行状态给上一层。
5、SuppressSuccess节点:suppress的意思是抑制,所以这个节点的功能,就是不准返回SUCCESS的执行结果,如果叶子节点返回为RUNNING则返回RUNNING,其他都返回为FAILURE给上一层
6、SuppressFailure节点:类似于上一个节点,该节点只会返回RUNNING或者SUCCESS这两种执行结果
除了常见的Compositor节点和Decorator节点,还有Condition节点,依据前面两种类型节点,不难推出后面的Condition节点的功能,就是在某些条件下才触发某些特定返回结果的一些节点。
简而言之,行为树就是在三个大类的行为节点:Compositor节点、Decorator节点、Condition节点的搭建下,结合各个叶子节点,拼接出一个基本的AI执行机制,举个例子:
<Root> <Selector> <Attack> <Homing> </Selector> </Root>
这是一个简单的站在原地攻击的行为树设计,首先执行Selector节点,然后顺序执行其下面的所有叶子节点,首先会执行攻击的叶子节点,如果返回为Success,则继续执行Homing节点。所以其基本的设计思想就是:触发一次攻击检测,如果有攻击对象,则执行攻击,返回SUCCESS,否则返回FAILUER。如果返回FAILURE,则结束这次AI节点的tick,如果返回SUCCESS,则继续执行Homming节点。
总结:游戏中两种常见的状态机和行为树都做了一个简单的讲解,当然现在网上比较多相关的资料,如果想深入的学习,可以搜集相关的资料研究,有较多的开源代码也可以参考研究一下。当然我说的都是较为浅显的设计,具体的状态机和行为树节点的设计,其中具体逻辑的编写,是需要结合实际的游戏设计来实现的,这就需要程序和策划具体的商量和实现了。好了,今天AI的简介就说到这儿,下一篇再说说一些优化的总结吧:D