楼主:
NDark (溺于黑暗)
2025-11-07 21:53:10实作一个由使用者预先定义并且容易拆解及重复利用的游戏AI引擎
网页好读有图版 https://tinyurl.com/ya9cxjrt
缘由
刚好在重构异世界物流士的 AI 部分,所以回想起15年前曾经实作过一个 AI 引擎。就把
一些旧文件拿来看,蛮多概念有点过时了,请多多包涵。
这个引擎主要的目的是:实作一个由使用者预先定义并且容易拆解及重复利用的
游戏AI引擎。(不包含实作路径搜寻或是评估模组)
简单来讲这是一个通用版的AI引擎。
主要特点在决策及行为的分解及组合使用。来避免常见初写游戏AI若使用决策树的撰写风
格下一发不可收拾不好除错的问题。
参考资料
当时的实作参考自这些资料给各位参考
适合的游戏类型
而当时在评估这个引擎的时候,特别比较了什么样的游戏类型适合使用。
很明显得战术小队的类型是主要的应用客户。
大架构
架构图分为
上方是游戏
左方是知识
中间及中间偏右侧是思考的判断区域
各种部件的说明
Plugin: AI引擎常见的插件
为了防止误会,关于AI常用的工具我还是要列在这里。但是这不是这个系统实作的内容,
所以我才用plugin来称呼。
Effic. Engine : 评估计算,用来计算哪个目标有较高的性价比。
Path Finding : 寻路。
Collision : 碰撞运算。
Editor : 编辑器(即时观察器)
Condition Bank
条件式的零组件中心
让使用者可以把一些好用的判断式写成零组件,实际使用的时候再重组。其中包含几类:
客制化条件式:距离某单位是否距离够近,某单位是否在眼前,这种常用的判断式。
逻辑闸:AND OR 等组合用。
常用元件:距离及时间或数值判断的常用元件。
一旦有了以上这些写好的判断式零组件。使用者在写AI单位需要判断的时候件就可以拿来
组合,不需要重新写。
以上图为例 自己(AI)是否被玩家看到可以用这些零组件组合而成。
Blackboard/Notebook/Memory
AI 需要去了解世界,所以要有一个类似 dictionary 的东西让游戏去填值,同时让 AI
可以方便地取出那些数值。没有这样一个标准化的接口的话,AI就必须去了解(参照)游
戏的类别,而每个游戏的数值设计可能都不一样,这样就会造成AI无法在专案间切换(强
耦合)。
这边分为三个名称的原因是想要做成。
全域,小队,以及个人的三层式资料。
同时更新的节奏也可以因为这三层来做区分。
如果不想这么麻烦的话也可以所有的AI都直接用全域去存取。
Event
相对于了解世界,AI也需要去操控影响世界,所以需要一个事件系统让AI能发送命令给游
戏端。一样是为了解耦合的目的。不过幸好,事件系统在很多引擎中已经是常见的工具。
ThinkingState
这边的状态是让AI能够将现况做切割。State的意思:是在什么"情况"下,应该要采取的
不同后续逻辑。
通常的做法是
准备一个默认逻辑(Default),没有满足任何条件下的思考情况。
准备若干个应急逻辑(Reaction),例如说主堡被攻击,需要即时切换的思考状态。
然后依此去设计满足条件下可以转换的状态:满足了某些条件,就切换过去的思考情况。
所以这边使用了类似状态机的概念,同时用上了上述的条件式零组件,让AI可以在游戏进
行的情况下做适当的切换。
特别提到切换也是一个状态,所以当状态 A 要切换到状态 B 的时候应该也要有一个
A->B 的状态来做过度。(不过切换的状态可以做比较少的事情,甚至不做事情)
Tactic Action Animation
这三个在引擎中是分为三层分别是战术,行动,及动作。
其中动作就是对应游戏内的Animation(由美术做好的一次性动作)
行动是抽象的一个动作,一对一的对应到动作。
战术则是多个抽象动作组合而成的逻辑。
每一个状态下有可能有多个可以预先设计好的战术。
例如,前述的搜索敌人状态下,AI目前选定的战术是“(过河)前往某个地点”,其中会
需要走到河边,游泳过河,抵达对岸,再行动到目的地。
走到河边用的是走路动作
游泳过河用的是游泳动作
抵达对岸可能加一个上岸的动作
行动到目的地又回到走路动作。
总结
这个AI系统让使用者可以事先写好一些零组件,在最终应用中组合来重复使用。最初会觉
得都在硬码,但是当零组件工具变多了,后面就会变成比较像在重复组合一些已经设计过
的部件。
而且因为已经部件化了,除错或从旧的物件复制一份改成新的物件也变得简单多。从实际
使用者的回馈也差不多是如此。使用者像是把需要修改的行为(不论是判断或是行动)=
零件拿出来改一下再组回去。
补充 One-State FSM for AI :
准确的名词我忘了。当时可能是在Game Programming Gems看到的。
其实这才是我想要找的资料,后续在物流士这个案子实作。
简单解释
因为传统的FSM有大量的状态机切换,导致AI逻辑不好整理。
所以当初有人提出来的简化法。
把Game AI化为两个状态机,思考及行动。思考的时候就决定要做什么。然后思考完毕切
到执行,就等待执行完毕。然后在适当时机再回过头来思考。
这样的优点是架构比较简单。
缺点就是回到思考的时机会造成这个AI的反应表现。打个比方,如果用一定的时间码表做
思考,那么在还没倒数完成的时候场景的参数变化,AI就不会来得及作出反应。就会看起
来笨笨的。
但是如果这个时间码表周期设定的太短,那AI又会占用太多的资源。如果场上的AI数量变
多,思考的资源就会不可忽视。(尤其是AI常常伴随着路径搜寻或是全局比较哪个怪物比
较应该攻击,那又是特别浪费资源的运算)
在异世界物流士这个案子中思考的虚拟码大概是这样的
我们先定义这个角色可以身处的几种情况(程式码中是用FSM,但是因为避免与上面
One-State混淆,我们把这个称之为情况。
每个画格中会先依照现行情况执行需要持续执行的动作(移动或动画)
A假如正在动画流程(One-State),就不思考。
B假如思考码表还没到,就不思考。
C其他,依照目前设计者指定的情况来做该情况的思考并执行动画,并接着切换情况.
如果进入执行动画的旗标是打开状态,会被A卡住.
如果执行动画的旗标不是打开状态,那么至少在B码表周期之内不会重新思考.
这边情况只是让判断的写法简单一点(等于是简化的决策树),跟上面提到的
One-State 的状态其实不是同一件事..
当然只要将B的思考码表设设定不要太短的周期(不要每个画格),那么我们从外界依照某
种规则来切换情况,AI就不会陷入每个画格在那边跳情况的问题。跳情况就是切换的规则
没写好。譬如说情况甲满足某个条件切到情况乙,情况乙又满足某个条件切到情况甲。
如果要舍弃情况的问题,那也不难就是在思考的流程把整个决策树跑一遍.就要注意不要
在一个画格内浪费太多运算力。(以Unity来说就是用Coroutine/Async去思
考直到有结果为止)