这篇文章参考了这些材料,在此特向各位原创作者表示敬意:

前言

游戏中的非玩家角色等等常常需要AI技术来作为行动的指导与支撑。

这种游戏AI与我们常说的人工只能研究中常用的AI技术,像遗传算法、神经网络算法相比, 区别较大。

一方面,AI领域里的遗传算法、神经网络算法计算起来比较慢,需要比较强大的硬件支持,而游戏运行的平台并不一定能提供这样的运算能力。

另一方面,游戏中的AI对于玩家来说,并不一定是越强越好。比如像在FPS游戏中,AI完全可以枪枪爆头,或是在卡牌游戏中,AI通过对局和很强大的神经网络算法获得了在各种situation下最优解的policy,这样对于普通玩家来说,其实是很降低体验的事情,甚至可能使玩家完全丧失游戏体验。

所以在游戏中的AI技术,早期基本都是使用有限状态机,后来在较复杂的情况下逐渐升级为行为树。一般来说,状态机是游戏AI的基本功,行为树是可选的实现。

层次化的AI架构

再了解具体的游戏AI算法之前,需要先了解一下层次话的AI架构。这种架构的耦合性很低,中间耦合的部分以一种类似于双缓冲的结构来实现。

层次化的AI结构

整体来说,决策层用于决定做什么,行为层负责怎么做。

在这样一个结构中,两层之间通过请求来连接。比如决策层决定现在要射击,那么就发出射击的请求,然后行为层开始执行这种动作。

中间的前端请求和后端请求是一种double buffer的策略,前端请求就是当前要执行的请求,后端请求就是下一次要执行的请求。

这样的话整体的AI架构就可以解耦合,决策层和行为层之间完全不用关注对方的实现细节,决策层只需要发送合适的请求,行为层需要处理好这些请求就可了。

有限状态机

有限状态机(Finite State Machine)是指一个具有状态的设备,在任何时间都可以接受输入,并产生对应的输出或者行动,并同时修改自身的状态。

有限状态机在很多情况下是游戏编程人员首选的AI实现方式,因为它具有以下的优点:

  1. 编程快速简单
  2. 易于调试
  3. 很少的计算开销
  4. 直觉性
  5. 灵活性

一般来说可以直接用switch case语句来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
void ProcessEvent(Event event)  
{
switch(state)
{
case StateA:
StateAProcess(event);
break;

case StateB:
StateBProcess(event);
break;
}
}

或者用函数指针数组实现也可, 这样写避免了switch case, 可读性和可扩展性都要更好一些:

1
2
3
4
5
6
7
8
9
10
11
EventHandler stateHandler[] =  
{
StateA_Event1, StateA_Event2,
StateB_Event1, StateB_Event2,
};

void ProcessEvent(Event event)
{
int index = state * EVENT_NUM + event;
stateHandler[index]();
}

在游戏AI中,一般是分层状态机 + 角色支持系统 + 团队角色分配 来配合形成整体的AI体系。

其中,分层状态机就是将对象的状态分类,将相同层次和类别的的状态集成为一个小的状态机来处理。

决策支持系统提供支持行为状态机以上的状态抉择,比如当前采取怎样的策略,选取怎样的道路。

团队角色分配指的是最高层的角色状态机的工作由团队AI来掌控。

分层状态机

每一层状态机都是通过为下一层状态机设定目标来实现控制(目标设定后,下层状态机将自动工作,上层不用关心动画到底播到哪了,现在到底是跑是跳),从而为上层提供更加高级拟人化的行为,所有状态机固定频率更新(如每秒10次),用于判断状态变迁和检查底层目标完成情况。

在每个人物上实现分层状态机,越低的状态机解决复杂度越低的问题,并为上层的状态机提供接口。上层的状态机通过调用下层的接口从而实现逐渐复杂的逻辑。

就是FSM当状态太多的时候,不好维护,于是将状态分类,抽离出来,将同类型的状态做为一个状态机,然后再做一个大的状态机,来维护这些子状态机。

举个决策小狗行为的例子:

我们对小狗定义了有很多行为,比如跑,吃饭,睡觉,咆哮,撒娇,摇尾巴等等,如果每个行为都是一个状态,用常规状态机的话,我们就需要在这些状态间定义跳转,比如在“跑”的状态下,如果累了,那就跳转到“睡觉”状态,再如,在“撒娇”的状态下,如果感到有威胁,那就跳转到“咆哮”的状态等等,我们会考量每一个状态间的关系,定义所有的跳转链接,建立这样一个状态机。如果用层次化的状态机的话,我们就先会把这些行为“分类”,把几个小状态归并到一个状态里,然后再定义高层状态和高层状态中内部小状态的跳转链接。

一般来说游戏中的角色可以实现三层状态机:基础状态机、行为状态机、角色状态机。以球类游戏举例如下:

基础状态机

直接控制角色动画、提供基础动作实现,为上层的行为状态机提供支持。

行为状态机

分解动作,将复杂的动作分解为基础状态机能够完成的动作,例如篮球游戏中的躲避跑、直线移动、原地站立、要球、传球、射球、追球、打人、跳。

角色状态机

实现更复杂的逻辑,比如投篮、篮板等都是由N次直线运动+跳跃完成。

各层状态机

这些状态机组合起来可以被认为是上面层次话AI架构中的行为层,用于实现对确定任务的动作。

而后面的决策支持系统和团队角色分配则相当于决策层,用于决定角色的行动。

决策支持系统

决策支持系统相当于角色的感知器官。角色的通过决策支持系统决定当前进行什么样的动作。比较常用的办法有势力图。

综合各种map计算的分数,可以计算出每个角色的合法区域和得分比较高的区域。

团队角色分配

团队角色分配即可以理解为团队状态机,用于指挥下一层(角色的最高一层),进行战术指导。

团队状态机就是根据当前的游戏的首要目标和势力图的信息,来确定一个具体的策略,然今后为每个AI角色安排一个目标,让角色再调用下面的状态机层来完成这个目标,从而整体完成这个策略。

整体过程

行为树

行为树(behaviour tree) 也是用来确定角色所做的动作。其整体来看可以用下图表示:

行为树

以一个士兵的例子来介绍一下行为树的概念。

行为树

行为树就是一个树形结构的图,有根节点,有分支,可以有任意数量的子节点个数。
行为树就是当决策当前这个士兵要做怎么样的行为的时候,就自顶向下地通过条件来搜索这棵树,最终可以决定要做的行为(叶节点),然后执行它。

行为节点

行为节点

在行为树上行为节点是可以复用的,比如移动,在巡逻的分支上,需要用到,在逃跑分支上,也会用到,这种情况下,我们就可以复用这个节点。行为节点一般分为两种运行状态:

  1. 运行中(Executing):该行为还在处理中
  2. 完成(Completed):该行为处理完成,成功或者失败
    行为节点是游戏相关的,对每一个游戏,都要写对应的行为节点对应的代码。

控制节点

控制节点

控制节点其实是行为树的精髓所在,我们要搜索一个行为就是通过这些控制节点来定义的,从控制节点上,我们就可以看出整个行为树的逻辑走向,所以,行为树的特点之一就是其逻辑的可见性。

我们可以为行为树定义各种各样的控制节点(这也是行为树有意思的地方之一),一般来说,常用的控制节点有以下三种

  1. 选择(Selector):选择其子节点的某一个执行
  2. 序列(Sequence):将其所有子节点依次执行,也就是说当前一个返回“完成”状态后,再运行先一个子节点
  3. 并行(Parallel):将其所有子节点都运行一遍

逻辑节点是游戏无关的,整体没有任何游戏特定的代码,只提供逻辑,所以不同游戏的逻辑节点的树可以是一样的,甚至可以写成行为树的库。

更深层的AI

在游戏世界,还可以用机器学习的方法来形成AI。一般用于游戏的方法是增强学习(reinforcement leaning)。

这类算法我刚好在暑期实习在解组合优化问题的时候有所涉猎,比较常用的有简单的Q-learning算法,将神经网络接入其中来避免巨大的Q-Table的算法有DQN,网络的参数更新是基于Policy Gradient算法,然后有Actor-Critc这样的框架来提供更好的学历能力和加速收敛。

但是这样做的效果并不一定就比传统的行为树要好,而且运算效率也不高,甚至不一定保证收敛……所以用在游戏中应该很少。

传闻《使命召唤》系列的游戏中AI加入了机器学习和神经网络,但不知效果如何。

期望未来的游戏中能够出现更多有趣的AI!