目录

有限状态机

行为树

  • 控制节点
  • 条件节点
  • 行为节点
  • 装饰节点
  • 总结
  • 额外

游戏AI的决策部分是比较重要的部分,游戏程序的老前辈们留下了两种经过考验的用于AI决策的结构:

  • 有限状态机
  • 行为树

在以前,游戏AI的实现基本都是有限状态机,

随着游戏的进步,游戏AI的复杂性要求越来越高,传统的有限状态机实现很难维护越来越复杂的AI需求。

现代游戏AI都比较偏向采用行为树作为决策结构。

有限状态机

有限状态机的一般实现是将每个状态写成类,再用一个载体(也就是所谓的状态机)管理这些状态的切换。

关于状态机设计模式的具体介绍,可参考我的另一篇博文:https://www.cnblogs.com/KillerAery/p/9680303.html

有限状态机的缺陷:

  • 各个状态类之间互相依赖很严重,耦合度很高。
  • 结构不灵活,可扩展性不高,难以脚本化/可视化。

行为树

(一个武装小队队员的AI行为树示例)

可以看到,行为树由一个个节点组成

  • 结构:树状结构
  • 运行流程:从根节点开始自顶向下往下走,每经过一个节点就执行节点对应的功能。

我们规定,每个节点都提供自己的执行函数,返还执行失败/成功结果。

然后根据不同节点的执行结果,往下的路径随之改变,最终总会走到末尾的叶节点,执行其对应的行为。

  1. //节点类(基类)
  2. class Node{
  3. //...
  4. public:
  5. virtual bool excute() = 0;      //执行函数,返还 成功/失败
  6. //...
  7. };

复制代码

主流的行为树实现,将节点主要分为四种类型:

  • 控制节点(非叶节点)
  • 条件节点(叶节点)
  • 行为节点(叶节点)
  • 装饰节点(非叶节点)

控制节点

控制节点是用于控制如何执行子节点。

由于非叶节点的特性,其需要提供容纳子节点的容器和添加子节点的函数。

所以先写好非叶节点的类:

  1. class NonLeafNode : public Node {
  2. std::vector<Node*> children;    //子节点群
  3. public:
  4. void addChild(Node*);           //添加子节点
  5. virtual bool excute() = 0;      //执行函数,返还 成功/失败
  6. };

复制代码

下面列出一些控制节点的介绍:

选择节点(Selector)

按顺序执行多个子节点,手游账号买号平台若成功执行一个子节点,则不继续执行下一个子节点。

举例:实现要不攻击,要不防御,要不逃跑。

用一个选择节点,按顺序添加<攻击节点>和<防御节点>和<逃跑节点>作为子节点。

  1. class SelectorNode : public NonLeafNode{
  2. public:
  3. virtual bool excute()override{
  4. for(auto child : children){
  5. //如果有一个子节点执行成功,则跳出
  6. if(child->excute() == true){break;}
  7. }
  8. return true;
  9. }
  10. };

复制代码

顺序节点(Sequence)

按顺序执行多个子节点,若遇到一个子节点不能执行,则不继续执行下一个子节点。

举例:实现先开门再移动到房子里。

用一个顺序节点,按顺序添加<开门节点>和<移动节点>作为子节点。

000

并行节点(Parallel)

同时执行多个节点。

举例:一边说话和一边走路。

用一个并行节点,添加<说话节点>和<走路节点>作为子节点。

  1. class SequenceNode : public NonLeafNode{
  2. public:
  3. virtual bool excute()override{
  4. for(auto child : children){
  5. //如果有一个子节点执行失败,则跳出
  6. if(child->excute() == false){break;}
  7. }
  8. return true;
  9. }
  10. };

复制代码

常用的控制节点一般是<并行节点><选择节点><并行节点>。当然还有其他更多控制节点种类(不常用):

  • 随机选择节点(随机执行一个子节点)。例如偶尔闲逛,偶尔停下来发呆。
  • 随机顺序节点(随机顺序执行若干个子节点)
  • 次数限制节点(只允许执行若干次)
  • 权值选择节点(执行权值最高的子节点)
  • 等等..

可能到这里,有想到还有个问题:为什么控制节点也需要提供(执行成功/执行失败)两种执行结果。

答:这样做就可以做到决策的复合——控制节点不仅可以控制行为节点,也能控制控制节点。

条件节点

前提条件

执行节点不会总是一帆风顺的,有成功也总会有失败的结果。

这就是引入前提条件的作用——满足前提条件,才能成功执行行为,返还<执行成功>结果。否则不能执行行为,返还<执行失败>结果。

但是每个节点的前提总会不同,或有些没有前提(换句话说总是能满足前提)。

一个可行的做法是:让行为节点含有bool函数对象(或函数接口)。这样对于不同的逻辑条件,就可以写成不同的bool函数,绑定给相应的行为节点。

  1. std::function<bool()> condition;    //前提条件

复制代码

但是一种更复杂也更成熟的做法则是把前提条件抽象分离成新的节点类型,称之为条件节点。

将其作为叶节点混入行为树,辅助控制节点决策。它相当模块化,更加方便适用。

这里的se节点,能够让其所有子节点依次运行,若运行到其中一个子节点失败则不继续往下运行。

这样可以实现出不满足条件则失败的效果。

只是由于逻辑条件的种类繁多,其编写各种条件节点类需要花费一定时间,不过我们可以使用别人现成写好的库,

亦或者仍然基于bool函数对象,相当于上文做法的装饰。

  1. class ConditionNode : public Node {
  2. std::function<bool()> condition;    //前提条件
  3. public:
  4. virtual bool excute()override {
  5. return condition();
  6. }
  7. };

复制代码

行为节点

行为节点是代表行为的叶节点,其执行函数一般位该节点代表的行为。

行为节点的类型是比较多的,毕竟一个智能体的行为是多种多样的,而且都得根据自己的智能体模型定制行为节点类型。

这里列举一些行为:站立,射击,移动,跟随,远离,保持距离....

行为状态

一些行为是可以瞬间执行完的(例如转身?),

而另外一些动作则是执行持续一段时间才能完成的(例如攻击从启动攻击行为到攻击结算要1秒左右的时间)

为了不让每帧重复启动执行一个持续行为,

我们给所有行为节点引入一个成员变量来标志,我们称为<行为状态>。

行为状态一般有2种:

  • ready(可执行)
  • running(正在执行)

另外可根据自己实际项目需求来定制状态(例如加入fail状态)。

行为节点示例实现

  1. //行为节点类(基类)
  2. class BehaviorNode : public Node{
  3. protected:
  4. BehaviorState state;                //行为状态
  5. public:
  6. virtual bool excute() = 0;          //执行节点
  7. };

复制代码

  1. //举例:移动行为节点
  2. class MoveTo : public BehaviorNode{
  3. public:
  4. virtual bool excute()override{
  5. //如果状态是完成,则启动行为
  6. if(state == BehaviorState::ready){
  7. state = BehaviorState::running; //先赋予正在运行的状态
  8. ...  //让智能体启动移动行为
  9. }
  10. return true;
  11. }
  12. };

复制代码

装饰节点

装饰节点,顾名思义,是用来装饰辅助的节点。

例如执行结果取反/并/或,重复执行若干次等辅助修饰节点的作用,均可做成装饰节点。

  1. //取反节点
  2. class InvertNode : public OneChildNonLeafNode{
  3. public:
  4. virtual bool excute()override{
  5. return !child->excute();
  6. }
  7. };

复制代码

  1. //重复执行次数节点
  2. class CountNode : public OneChildNonLeafNode{
  3. int count;
  4. public:
  5. virtual bool excute()override{
  6. while(--count){
  7. if(child->excute() == false)return false;
  8. }
  9. return true;
  10. }
  11. };

复制代码

OneChildNonLeafNode是指最多可拥有一个子节点的非叶节点类,这里就不做具体实现。

总结

到这里,我们可以看到行为树的本质:

  • 把所有行为(走,跑,打,站等等)分离出来作为各种<行为节点>,
  • 然后以不同的<控制节点><条件节点><装饰节点>将这些行为复合在一起,组合成一套复杂的AI。

相比较传统的有限状态机:

  • 易脚本化/可视化的决策逻辑
  • 逻辑和实现的低耦合,可复用的节点
  • 可以迅速而便捷的组织较复杂的行为决策

这里并不是说有限状态机一无所用:

  • 状态机可以搭配行为树:状态机负责智能体的身体状态,行为树则负责智能体的智能决策。这样在行为树做决策前,得考虑状态机的状态。
  • 状态机适用于简单的AI:对于区区需两三个状态的智能,状态机解决绰绰有余。
  • 状态机运行效率略高于行为树:因为状态机的运行总是在当前状态开始,而行为树的运行总在根开始,这样就额外多了一些要遍历的节点(也就多了一些运行开销)。

在《杀手:赦免》的人群系统里,人群的状态机AI只有简单的3种状态,由于人群的智能体数量较多,若采取行为树AI,则会大大影响性能。

简言之:行为树是适合解决复杂AI的解决方案。

对于Unity用户,Unity商店现在已经有一个比较完善的行为树设计(Behavior Designer)插件可供购买使用。

关于行为树运行流程,有两个实现方式,博主只简单实现过第二个实现方式,这里对于第一种方式就不做详解。

  • 一次性流程:Unity行为树插件目前的实现方式。一次性流程可能会阻塞在延时行为,但是可以利用协程等待该其行为完成而不产生阻塞。
  • 每帧重复从根节点开始自顶向下执行的流程:这种方式不会产生阻塞,因为若有延时行为在运行中则视为执行成功,继续往下走。

缺点是每帧都得从根节点重新往下走,效率可能不如一次性流程(一次性走完行为树,而不会从头走起)。

额外

  • 可让根节点记录该AI要操控的智能体引用(指针),每次进行决策,传给子节点当前要操控的智能体引用。这样就可以使AI行为树容易改变寄主。

(例如1个丧尸死了被释放内存了,寄生它的AI行为树不必释放并标记为可用。一旦产生新的丧尸,就可以给这个行为树根节点更换新的寄主,标记再改回来)

  • 得益于树状结构,重复执行次数节点(或其他类似的节点),可以让它执行完相应的次数后,解开与父节点的连接,释放自己以及自己的子节点。
  • 共享节点型行为树是可供多个智能体共用的一种行为树,是节省内存的一种设计:
  • LOD优化技术:LOD原本是3D渲染的优化技术。对于远处的物体,渲染面数可以适当减少,对于近处的物体,则需要适当增加细节渲染面数。

同样的可以用于AI上,对于远处的AI,不需要精准每帧执行,可以适当延长到每若干帧执行。

游戏AI之决策结构—行为树相关推荐

  1. 游戏AI之初步介绍(0)

    目录 游戏AI是什么? 游戏AI和理论AI 智能的假象 (更新)游戏AI和机器学习 介绍一些游戏AI 4X游戏AI <求生之路>系列 角色扮演/沙盒游戏中的NPC 游戏AI 需要学些什么? ...

  2. 游戏AI探索之旅:从AlphaGo到MOBA游戏

    背景:7月28日,腾讯云在北京举办云+社区沙龙,邀请来自腾讯与四川云检科技的五位AI技术专家,分享他们在专业领域的AI开发经验,帮助开发者在具体行业场景中实践AI技术.本文根据王亮在[7.28日腾讯云 ...

  3. 手机游戏AI探索之旅:从AlphaGo到MOBA游戏

    讲师介绍 王亮,腾讯AI高级研究员.2013年加入腾讯,从事大数据预测以及游戏AI研发工作.目前主要从事MOBA类游戏AI相关的研发工作. 本次分享大纲 今天分享的课题是游戏AI探索之旅.本次分享分为 ...

  4. 游戏ai 行为树_游戏AI –行为树简介

    游戏ai 行为树 游戏AI是一个非常广泛的主题,尽管有很多资料,但我找不到能以较慢且更易理解的速度缓慢介绍这些概念的东西. 本文将尝试解释如何基于行为树的概念来设计一个非常简单但可扩展的AI系统. 什 ...

  5. 游戏AI –行为树简介

    游戏AI是一个非常广泛的主题,尽管有很多资料,但我找不到能以较慢,更容易理解的速度缓慢介绍这些概念的东西. 本文将尝试解释如何基于行为树的概念来设计一个非常简单但可扩展的AI系统. 什么是AI? 人工 ...

  6. 使用行为树(Behavior Tree)实现游戏AI

    注意:本文版权归Csdn AKara所有,此处纯粹转载,如有再转,请严格按如下方式显示标明原创作者及出处,以示尊重!! 关注公众号 风色年代(itfantasycc) 200G Unity资料合集送上 ...

  7. 谈一谈游戏AI - 行为树

    不要用过去的成绩看未来,而是要用未来的眼睛看现在. 郑重说明:本文适合对游戏开发感兴趣的小白初学者,本人力图将事物用简单的语言表达清楚,但水平有限,能力一般,文章如有错漏之处,还望批评指正. 在本系列 ...

  8. 人工智能_游戏AI –行为树简介

    人工智能 游戏AI是一个非常广泛的主题,尽管有很多材料,但我找不到能以较慢,更易理解的速度缓慢引入这些概念的东西. 本文将尝试解释如何基于行为树的概念来设计一个非常简单但可扩展的AI系统. 什么是AI ...

  9. 【游戏精粹】AI个性化决策系统

    目录 创建影响力地图 策略评估技术 有关态度的一切:为意见.声望和NPC个性构建单元 个人观点 一.创建影响力地图 前言             本节和下一节将介绍这样的一些技术:让AI主体能够从战术 ...

最新文章

  1. 设计模式之Pimpl模式
  2. 360金融沈赟:只有适配实践的技术才能实现价值掘金
  3. AngularJS高级程序设计学习笔记(一)
  4. 温州大学《机器学习》课程课件和视频(四)朴素贝叶斯
  5. 前端学习(2047)vue之电商管理系统电商系统之使用cdn优化打包
  6. SQL取最大值编码(自动编码)
  7. 【C++基础】时间类型详解(转)
  8. verilog换行太长代码_Verilog 之 File I/O task and function
  9. 好用的Typecho系统后台模板
  10. 用工具激活visio2013
  11. hive-metastore
  12. nginx安装nginx-vts实现监控并打包镜像
  13. Java 分页工具类
  14. cadence从原理图导出器件库_一种cadence中原理图替换元器件库的方法与流程
  15. Matlab求解线性方程组Ax=b
  16. 再添荣誉!青软集团获评「山东省科技小巨人企业」称号
  17. 经典r-k法 matlab,解微分方程欧拉法,R-K法及其MATLAB实例
  18. 51系列单片机寻址方式
  19. QT之调用百度地图离线API
  20. 微信小店和微信小商店的区别?如何建立怎么开通?

热门文章

  1. python列表和元组的应用_python学习笔记之列表(list)与元组(tuple)详解
  2. java quartz timer_Java定时器Quartz和Timer
  3. 深度学总结:weight_initialization
  4. Dijkstra's algorithm (C++)
  5. Linux基础知识part3
  6. 视窗宽高offset、client、scroll
  7. 谷歌搜索和谷歌站内搜索
  8. jquery 视觉特效(幻灯片效果)
  9. C#实现HTTP协议:多线程文件传输
  10. android-----JNI学习 helloworld