前言

本文根据原神组在GDCVault上进行的分享进行AI的细节拆解与分析,希望能对AI策划有一些系统性的帮助。

GDCVault原PPT链接: 'Genshin Impact': Building a Scalable AI System

Pipeline

Fig.1. AI Pipeline
  1. Reasoning: subsystem各自管理各自的参数,以不同的Tick频率,符合ECS架构
    1. 感知(对WorldState进行采样)
    2. 选择目标(基于策划设计的一系列逻辑决定仇恨、技能目标等)
    3. 响应外部事件(受击、听觉等,原神内特化的Feature还有元素状态Buff)
    4. 基于关卡的固定行为脚本(演出需要或特定Gameplay需求)
    5. 集群(与其他AI互动的需求)
    6. 战术位置(出于战术需要被高层AI要求前往某个位置,通常和集群相关)
  2. Decision Tree:决策树
    1. 这里说轻量级,估计是十个节点左右,无状态,通过bool状态选择分支的简单决策
  3. Actions:执行具体的行为逻辑
    1. Skills:释放技能,对标GameplayAbility
    2. MoveTasks:寻路异步逻辑,对标MoveTo
  4. Animation:AI系统怎么管理角色动画
    1. Params:通过参数来间接控制动画表现,而非通过LookAt、RotateTo等方式来直接控制。以Unity引擎的Animator为例,通过SetFloat、SetTrigger等形式来间接控制动画机内参数。原神这种体量的游戏多半自己重写了动画机,额外支持Vector之类的数据结构来方便IK引用。而换到Unreal中的话,则是动画蓝图通过读取MeshOwner身上的参数来修正动画。

Key State Manager

Fig.2. Key State Manager

PPT接着讲到KeyStateManager,用于管理AI逻辑的状态变化。例如怪物二阶段逻辑变化以及进入特殊状态后的表现改变。 这个KeyStateManager大概是作为subsystem实现的,凌驾于DecisionTree之上,甚至高于或管理着其他Subsystem。

这个范式我在之前的项目中也有采用过 Componential Tree, 一种行为树模块化的组织框架 ,上层利用状态机可以很好地分割复杂度(flatten the decision tree),下层利用树形决策来管理具体行为。区别在于原神是以状态机为决策主体,而我是以行为树为决策主体。

  • 一方面和引擎有关,状态机是Unity的原生工具,所以在立项的前期可能就采用了状态机甚至移了崩3的代码来做。而决策树可能只是让策划写lua脚本。
  • 另一方面,动作RPG游戏AI与射击类游戏AI的一大不同点就是射击类游戏往往会在同一时刻并行多个维度的Action,例如会在下身移动决策的同时进行上身射击/使用物品决策。因此不太适合用状态机描述,所以我的项目中会更倚重行为树一点。

这里我们注意到截图中,每个状态除了名字之外还有一个编号后缀,这意味着每个状态不仅是放在一个状态机内单独运作的,而是以模块化的形式被状态机引用。那么有两种可能性,一是这是一个分层状态机,每个状态ID对应一个子状态机引用至一个状态机资源文件;二是这是一个简单有限状态机,只有一层,每个状态ID对应一个决策树(之前Pipeline中提到的第二层)。根据原神AI复杂度与个人经验来判断,我认为第二种可能性比较高。

其他

剩下的都是一些程序做优化的经验,策划可以不用看,就不在这里细讲,只列出几个我觉得和策划有关系的Feature,大伙如果提类似需求的时候程序跟你说做不了,就把这个PPT贴出去。(笑

  • 服务端寻路结合客户端动态障碍信息。这个功能为“创建AI可识别的动态障碍”提供了可能性,但又不像Dedicated Server一样什么都演算一遍浪费服务端性能,刚好适合原神这种大部分时候单机偶尔联机的类型。另外我还观察到为了适配大世界,原神为寻路做了诸如地图分块、寻路中继点等等措施,就不在这里聊了,有兴趣的朋友可以自己观察一下。

  • AI LOD与逻辑动态运行频率。这个倒不是什么特别的东西,大一点的项目都有。策划需要关注的是这个LOD距离和具体什么逻辑被降频或是暂停了,不要让优化破坏掉设计意图。原神这里有一个正面例子是身后的LOD1范围内的角色AI逻辑还在跑只是隐藏mesh,这意味着如果你转身再回头看,NPC将处于你预期的位置,而不是从原位置开始移动,保证了AI逻辑的连续性。