Unity实现高级可配置化游戏AI系统
在1月的《Unity实现高级可配置化游戏AI系统》直播课程中,我们通过使用ScriptableObject为坦克项目创建了可配置的AI系统。
课程的主要包括四部分的内容:
可配置化AI系统介绍及选择ScriptableObject的原因
基于有限状态机(FSM)的AI系统设计
有限状态机中的状态(State),动作(Action),决策(Decision),状态转化(Transition)的具体实现
如何快速扩展新的AI行为
今天Unity技术经理成亮会对本次课程进行一个全面的梳理与回顾,帮助大家温故知新。
以下为直播视频,视频长1个半小时,请注意请在Wi-Fi状态下观看。
https://v.qq.com/txp/iframe/player.html?vid=c0537cidpg9&width=500&height=375&auto=0
可配置化AI系统及选择ScriptableObject
可配置化AI系统介绍
我们期望实现可以灵活配置的AI系统,如图01所示,不同的AI行为对应不同的芯片,坦克只需要切换一下芯片就可视实现不同的AI行为。
图 01
为了实现AI的可配置化,我们需要代码逻辑能够被保存到数据资产(Asset)中,也就是说支持序列化。很自然的,我们会想到常用的MonoBehavior类,但是Unity还提供了ScriptableObject类,也具有同样的能力。那么该选择哪一个类来实现我们的AI系统呢?下面我们就对二者进行一个比较。
为什么选择使用ScriptableObject
我们列举了ScriptableObject和MonoBehaviour的一些特点进行比较。如图02所示。二者都支持数据序列化以及可视化(可以在编辑器中暴露变量并进行编辑),这是实现可配置化系统所必须的。
图 02
我们再看三个主要的不同点:
ScriptableObject可以保存在”.asset”文件中,而MonoBehaviour只能保存在”.scene”或者”.prefab”中。由于prefab通常是作为物件模版来使用,如果又用于保存逻辑,会造成概念的混淆;
ScriptableObject在运行时不需要作为GameObject的Component,使用相对简单和直接;
不包含Transform, gameObject等数据,比MonoBehaviour更加轻量级。
因此,使用ScriptableObject来实现可配置化AI会更加的简洁和方便。
基于有限状态机(FSM)的AI系统
我们来介绍下AI系统的具体设计。在游戏开发中,常用的一些AI技术包括有限状态机,行为树,以及最近开始流行的机器学习等。这次我们在坦克项目中所采用的是有限状态机。
有限状态机介绍
我们可以用下面的句话来描述有限状态机:
一台具有有限个状态的抽象机器。
比如我们可以把坦克就比做这台机器,它包含巡逻,追逐等有限个状态。
状态机中的状态可以因为输出或者环境改变转换到另外的状态。
比如当坦克发现敌人后就从巡逻状态转换到追逐状态。
每个状态包含一些列动作以及状态转换所需要的决策。
比如当坦克发现敌人后,是转换到追逐状态,还是保持当前状态。
图 03
相较于完全用if语句去编写AI,通过有限状态机,AI逻辑会划分得更加的清晰,甚至可以开发出对应的图形化工具进行AI设计与实现。
坦克AI系统设计
通过以上介绍,坦克的AI系统采用了有限状态机。我们要实现的第一个AI行为称之为追逐者,它表现为在没有发现敌人的时候会沿着设定好的路径进行巡视,当发现敌人后就一只追逐敌人直到把目标摧毁。
因此我们为它设计了两个状态:巡视(Patrol)和追逐(Chase)。为了引用和控制这些状态的执行,我们为坦克设计了StateController的组件,可以把他想象为AI芯片的插槽。AI系统的构成如图04所示。
图 04
有限状态机的实现
介绍完整个坦克AI系统的设计,我们再继续介绍有限状态机的具体实现。当然由于篇幅所限,这里也仅仅是介绍实现中的一些原理和要点。
面向对象的设计
在我们的系统中,有的类需要采用到面向对象的方式来设计,比如状态(State)中的动作(Action)。我们既希望用子类来表现不同的实现,也希望用基类来提供同意的接口。所以不同的Action都有一个基类Action, 方便State用统一的接口来管理这些Action。另外,在基类中Act方法我们把它定义为abstract 方法,因为并不需要具体的实现。
图 05
动作的实现
在目前的AI系统中,我们主要包括3个动作:巡逻,追逐,以及开火。巡逻动作在巡逻状态中执行,追逐和开火动作在追逐状态中执行。下面我们分别来介绍相应的实现。
巡逻(PatrolAction)
巡逻的行为是绕着在地图上设定好的路径点行走,通过Unity自带的导航系统,我们可以很方便的实现这样的功能。另外通过CreateAssetMenu属性,我们可以很方便的在菜单上添加创建PatrolAction资产的命令。
图 06
实现好脚本后,在Unity中就可以创建PatrolAction的资产了。
图 07
追逐(ChaseAction)
追逐的行为是朝着攻击目标移动,实现原理和巡逻类似,也是利用了导航系统。不同的是移动的目标改变了。这个追逐目标是在决策中发现并保存到StateController中的,后面会讲到。
图 08
开火(AttackAction)
开火的行为是朝攻击目标发射炮弹。发射炮弹是坦克项目已经具备的功能,我们调用TankShooting组件的Fire函数就可以实现。
图 09
决策的实现
目前系统包括两个决策,一个是巡逻状态的观察决策,另一个是追逐状态的目标是否存活的决策。
观察决策(LookDecision)
观察决策继承于Decision这个基类,这个实现方式和Action类似。LookDecision同样使用了CreateAssetMenu属性来添加创建资产的菜单项。在目标检查的功能实现上采用了Physics.SphereCast,相对于Physics.Raycast更真实一点。最后当检测到另外的坦克后,就会把该坦克的位置 保存到controller.chaseTarget中作为追逐的目标。
图 10
目标是否存活决策 (ActiveStateDecision)
判断目标是否存活的决策较为简单,只需判断是否物体是否处于active状态即可。
图 11
当实现完相应的代码后,我们可以回到Unity创建相应的资产。
图 12
状态转换的实现
状态转换不需要单独作为资产,只是保存决策以及决策对应的两个目标状态。
图 13
状态的实现
最后,我们来看一下状态的实现。首先状态(State)继承于ScriptableObject,也使用了CreateAssetMenu属性。包含了相关的动作(Action)和状态转换(Transition)数组。然后在每一帧会调用的UpdateState函数中执行当前状态的动作,以及决策。
图 14
实现好代码后,我们就可以在Unity中创建State资产,并配置该状态了。下图就是巡逻状态的配置。
图 15
把这些State添加到StateController组件后,我们的坦克就可以执行相应的AI行为了。到此为止,我们的可配置AI系统已经实现了。我们可以组合不同的Action和Decision来实现不同的State,然后通过不同的State来实现不同的AI行为。
增加新的AI
最后,我们再来实现一个稍微不同的AI行为来检视一下我们的可配置AI系统,就是当进入追逐状态后,不再是一直追逐直到目标被摧毁。而是目标离开视线后,就原地巡视,如果再次发现目标,就进入追逐状态,如果巡视时间结束就返回巡逻状态。
AI设计
通过分析,我们为这样的AI行为实现了如下图 16 所示的状态机。
图 16
和之前的状态机稍有不同。首先我们对追逐状态做了修改,把判断目标存活的决策替换为观察决策。然后我们增加了预警状态(AlertScanner),在预警状态中,我们使用了扫描决策(ScanDecision)和观察决策(LookDecision)。 而整个状态机的构建唯一需要实现的只有扫描决策,可见我们的状态机具有很好的可配置性。
扫描决策(ScanDecision)实现
该决策的实现比较简单,主要就是选择坦克,以及判断扫描时间是否结束。
图 17
总结
通过本次课程,我们学习了如何用有限状态机来设计AI系统。通过有限状态机我们可以更加清晰的表示游戏物体的AI逻辑,便于我们设计一些较为复杂的AI行为。
除此之外,我们利用ScriptableObject来实现有限状态机中的动作,决策和状态。由于这些对象都可以保存为资产,并且可以在编辑器中进行可视化操作,使得我们可以方便的配置出我们期望的AI行为。而这也正是我们所说的可配置化AI系统。
更多Unity精彩文章, 尽在Unity官方中文论坛(Unitychina.cn)!
点击“阅读原文”访问Unity官方社区!