很久没写博客了,最近在项目刚部署到测试服,需要进行压测,老大相当专业的用了行为树来组织压测机器人的代码,这段时间陆陆续续在网上看了不少关于行为树的文章,其中有一篇我觉得写得非常到位,它原文是英文,链接将在下面给出,在这之前看了很多国内的文章都是写的糊里糊涂,后来看过这篇文章后,总算是对行为树的概念入了门。在这里我会把它的大致意思给翻译出来(注:由于原文有不少啰嗦的地方,所以没有逐句的翻译),以它的行为结构为标准,并会在译文中穿插一些自己的理解来谈谈我眼中的行为树,如果有什么不对的地方,欢迎大家指点。

原文链接

一、介绍


尽管网上已经有大量关于行为树的教程,但是在开发游戏中的 AI 时还是遇到了不少关于行为树的问题。很多教程不关注于代码的实现,而是给出一些非常笼统的节点图,就像这样:

这些节点看起来非常的抽象,这对于我了解行为树的运作机制没有什么帮助,因为这样完全建立不起一个完整行为树的概念。自然也就更不知道如何在代码中进行行为树的实现。

行为树的概念见名知意,它是一棵具有层级结构的树,主要用来控制 AI 的决策,树的末端(注:叶子节点)就是 AI 实际要去做的事情。连接树枝的是各种类型的节点(注:这些节点将在后面讲解),这些节点决定了如何一个 AI 如何从根节点走到某一个叶子节点,并执行相应的命令操作。

二、树的遍历


行为树的一个特点就是它会一层一层的对节点依次进行检查,而每一层都要花费一个 Tick 的时间,所以一棵树要花费很多 Tick 的时间才能完成遍历,这和一般代码的实现有很大的区别。这是一个很没有效率的方式,尤其是当树变得很深的时候。我认为行为树的实现最好要在一次 Tick 里面完成整个行为树的逻辑判断,也就是说最好能在一次 Tick 时间里完成整棵树的遍历。

三、工作流


行为树由多种不同类型的节点组成,这些节点都会返回三种状态中的一种作为节点的运行结果。三种状态分别是:

  • 成功 - Success
  • 失败 - Failure
  • 运行中 - Running

前两个,正如它们的名字一样,是用来向它们的父节点通知运行的成功或失败。第三种是指还在运行中,结果还未确定,会在下一个 Tick 的时候再去检查这个节点的运行结果。这个功能非常重要,它可以让一个节点持续运行一段时间来维持某些行为。比如一个 Walk 节点会在计算寻路和让角色保持行走的过程中持续返回 Running 来让 AI 保持这一状态。如果寻路因为某些原因失败,或是除了某些状况让行走的行为不得不中止,那么这个节点会返回 Failure 来告诉它的父节点;如果这个角色走到了指定的目的地,那么节点返回 Success 来表示这个行走的指令已经成功完成。这些状态可以用来决定行为树的走向,确保 AI 可以按照我们预期的方式来以某些顺序去执行行为树里的行为。

上面是节点的 3 种状态。说完了节点的 3 种状态,下面来说一说行为树中节点的类型。行为树节点类型分为下面 3 种:

  • 组合节点 - Composite
  • 修饰节点 - Decorator
  • 叶子结点 - Leaf

这些节点类型可以说是行为树最精华的内容,下面将分别对这 3 种类型的节点做说明。

1、组合节点

我们先来看看行为树中最常见的组合节点。组合节点通常可以拥有一个或更多的子节点。这些子节点会按照一定的次序或是随机地执行,并会根据执行的结果向父节点返回 Success 、Failure,或是在未执行完毕时返回 Running 这样的结果值。

通过使用组合节点,我们就可以构造出复杂的行为树。其中组合节点又包括下面几种节点:
  • 次序节点
  • 选择节点
  • 并行节点

(1)次序节点

次序节点会按从左往右的顺序依次执行子节点,每个子节点成功之后便轮到下一个执行,直到最后执行完该节点下的全部子节点。如果所有子节点都返回 Success,则向次序节点的父节点返回 Success;其间任何一个子节点返回 Failure,就会立即向次序节点的父节点返回 Failure 的结果。这种性质和与门(AND gate) 是一致的。次序节点有很多的用处,其中最常用的做法就是执行一连串有前后依存关系的行为,其中一个的失败必然导致后续的动作没有进行的意义,比如下面这个例子:
这个次序节点让 AI 角色实现了从走向门、进门、关门等一系列连续动作。整个过程可以描述成这样:次序节点 ->Walk to Door (Success) ->次序节点(Running) ->Open Door (Success)  ->次序节点(Running) ->Walk through Door (Success) ->次序节点(Running) ->Close Door (Success) ->次序节点(Running) -> 向次序节点的父节点返回 Success。如果 AI 因为某些原因未能成功走到门前,比如路被挡住了之类的,那么试图开门这些动作都没有意义了。即当 Walk to Door 这个动作失败后,次序节点就会向其父节点返回 Failure。注意这里不是向次序节点返回 Success,可以将次序节点理解成就是一个桥梁的作用,真正想得到这一连串执行结果的是该次序节点的父节点。
 
次序节点除了非常自然地用于进行一系列前后依存的动作之外,还可以用来做一些其他的事情,比如:
在上面这个例图中,次序节点的子节点不是一系列动作而是一系列的检查。这些子节点会检查 AI 角色是不是饿了,有没有食物,是不是在安全的地点,只有在它们都返回 Success 时,角色才会吃东西。这样使用次序节点可以实现类似于代码中 IF 判断和与门(AND gate)的效果。这些用于判断的子节点可以是其他的组合节点或是修饰节点等等来实现更丰富的效果。比如下面这个使用了逆变节点(注:逆变节点是一种装饰节点,后文阐述)的例子:
尽管功能和前面的例子完全一样,但是通过逆变节点我们在这里创建了一个非门(NOT gate),只有在“Enemies Around(敌人在周围)”这个条件返回 Failure 时,这一步才会返回 Success,从而让角色继续进行吃东西的动作。这意味着这些节点的组合可以减少很多不必要的开发量。

(2)选择节点

选择节点就是次序节点的反面。作为与门(AND gate)的次序节点要求子节点都返回 Success 来让自己返回 Success,选择节点则会在任何一个子节点返回 Success 时就返回 Success 并且不再继续运行后续的子节点。相应的,当所有子节点都 Failure 时,选择节点才会返回 Failure。选择节点其实可以被理解为一个或门(OR gate)。
 
它的主要作用在于它可以用来表示一个行为的多种方式,从最高优先级到最低,任何一个方式的成功都会让这个动作 Success,比如 Attack(攻击) 节点下的有 Hack、Cut,Chop三个子节点,每当这个 AI 尝试攻击时,都会从这三个中选择一个来执行,只要任何一个子节点执行成功,该 Attack 节点就返回成功。

(3)并行节点

我们每个节点都会有一个运行状态,来表示当前行为是否结束。对于组合节点来说,它的运行状态就是其子节点的运行状态,选择节点和次序节点比较好处理,因为对于这两种控制节点来说,每时刻,只会有一个子节点在运行,只要返回在运行的这个子节点的状态即可。但对于并行节点来说,它同时刻会有多个子节点运行。

2、修饰节点

修饰节点也可以拥有子节点,但是不同于组合节点,它只能拥有一个子节点。取决于修饰节点的类型,它的功能要么是修改子节点返回的结果、终止子节点,或是重复执行子节点等等。常见的修饰节点有以下几种:
  • 逆变节点
  • 成功节点
  • 重复节点

(1)逆变节点

Inverter(逆变节点)可以将子节点的结果倒转,比如子节点返回了 Failure,则这个修饰节点会向上返回 Success,就像上面的例子里所说的一样。

(2)成功节点

成功节点不管它的子节点向其返回的结果为何,它总是向它的父节点返回 Success 的结果。这个往往用在当你知道一个子节点一定会返回 Failure 的结果,而它的父节点是次序节点,会因为子节点的 Failure 而终止,那么你可以强行让这个子节点返回 Success,来避免这一情况的发生。我们并不需要一个专门的失败节点,因为一个逆变节点加上成功节点就可以达到这一效果。

(3)重复节点

重复节点会在它的子节点返回结果后反复继续执行它。重复节点常常被用在一棵树的最顶部来确保树的持续运行。另外重复节点也可以被设定重复执行的次数。

3、叶子节点

叶节点是最低层的节点,它们不会拥有子节点。叶节点是最强大的节点类型,它们是真正让你行为树做具体事情的节点。通过与组合节点和修饰节点的配合,再加上你自己对叶子节点功能的定义,你可以实现非常复杂的、智能的 AI 行为逻辑。拿代码作为类比的话,组合节点和修饰节点就好比那些改变代码 Flow( 工作流程 ) 的 If 判断和 While 循环等等,而叶节点就是那些真正起作用的被调用的方法,去让角色做具体的事情。
 
比如一个 Walk 叶子节点可以包含一个具体将要移动到的位置参数,而这个参数可以从其他变量里获得,比如角色将要前往的一个地点参数可以被 GetSafeLocation 这个节点所决定,存入一个变量里,然后 Walk 节点可以使用这个变量来定义它的目的地。在行为树中, 这样将一个节点的数据用在其他节点上的行为, 用好了是非常强大的, 行为树的运行中,这些不同的节点通过数据上下文来共同储存或使用一些持久数据(persistent data),使得行为树的功能变得强大。另一种叶节点的类型是调用其他的行为树并把当前行为树的数据传给对方,可见一种是获得数据另一种是产生数据。

2018-09-22  20:25:57
 
 

AI行为树的工作原理的更多相关文章

  1. How Javascript works (Javascript工作原理) (十四) 解析,语法抽象树及最小化解析时间的 5 条小技巧

    个人总结:读完这篇文章需要15分钟,文章介绍了抽象语法树与js引擎解析这些语法树的过程,提到了懒解析——即转换为AST的过程中不直接进入函数体解析,当这个函数体需要执行的时候才进行相应转换.(因为有的 ...

  2. 浏览器内部工作原理--作者:Tali Garsiel

    本篇内容为转载,主要用于个人学习使用,作者:Tali Garsiel 一.介绍 浏览器可以被认为是使用最广泛的软件,本文将介绍浏览器的工作原理,我们将看到,从你在地址栏输入google.com到你看到 ...

  3. Java中GC的工作原理

    转文: 一个优秀的Java程序员必须了解GC的工作原理.如何优化GC的性能.如何与GC进行有限的交互,有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率,才能提高整个 ...

  4. AJAX工作原理及其优缺点

    1.什么是AJAX?AJAX全称为"Asynchronous JavaScript and XML"(异步JavaScript和XML),是一种创建交互式网页应用的网页开发技术.它 ...

  5. Struts2(二):工作原理

    struts可查看源码:https://github.com/apache/struts 在学习struts2之前,我先看了一些比较早版本对struts2的工作原理相关的介绍,顺便抄写过来,用来帮助自 ...

  6. java gc的工作原理、如何优化GC的性能、如何和GC进行有效的交互

    java gc的工作原理.如何优化GC的性能.如何和GC进行有效的交互 一个优秀的Java 程序员必须了解GC 的工作原理.如何优化GC的性能.如何和GC进行有效的交互,因为有一些应用程序对性能要求较 ...

  7. 【转】Linux Page Cache的工作原理

    1 .前言 自从诞生以来,Linux 就被不断完善和普及,目前它已经成为主流通用操作系统之一,使用得非常广泛,它与Windows.UNIX 一起占据了操作系统领域几乎所有的市场份额.特别是在高性能计算 ...

  8. AJAX的工作原理及其优缺点

    1.什么是AJAX?AJAX全称为“Asynchronous JavaScript and XML”(异步JavaScript和XML),是一种创建交互式网页应用的网页开发技术.它使用:使用XHTML ...

  9. DDNS 的工作原理及其在 Linux 上的实现--转

    http://www.ibm.com/developerworks/cn/linux/1305_wanghz_ddns/index.html DDNS (Dynamic DNS) 扩展了 DNS 将客 ...

随机推荐

  1. Scrollbox的滚动条事件scrollbar事件的处理(Lazarus)

    没办法,改源代码: 找到:Forms单元中scrollbox的父类: TScrollingWinControl = class(TCustomControl)    改: { TScrollingWi ...

  2. http_load测试初阶

    http_load的标准的两个例子是: 1.         http_load -parallel 5 -fetches 1000 urls.txt 2.         http_load -ra ...

  3. javascript相框echarts插件实现酷立方效果图的人

    最近由于项目需求,我们需要做的一类似网络效应的人立方效果,很多文件中的查询之后.百度发现echarts开源组件非常适合,而加载速度是伟大的.echarts图形主要使用html5这些新功能做,使用can ...

  4. Qt自定义弹窗屏蔽父窗口(QWidget设置setWindowModality(Qt::ApplicationModal);以后再show)

    写Qt程序时遇到一个问题: Qt自带的弹窗功能单一,所以须要自己用ui设计弹窗的内容,这样弹窗就和普通窗口一样了,但问题是这个弹窗显示后父窗口还是活动的.网上找了很久找到了解决办法: Qt::Wind ...

  5. code first迁移(add-migration)的时候 后面信息不能有中文 中文报错 "不支持给定路径的格式。"

  6. OpenMP 并行程序设计入门

    OpenMP 是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的. 0. 一段使用 OpenMP 的并行程序 #include <stdio.h> #inclu ...

  7. codeforces Round #259(div2) D解决报告

    D. Little Pony and Harmony Chest time limit per test 4 seconds memory limit per test 256 megabytes i ...

  8. VS2005下如何安装配置编译Qt4.6

    本文将使用简单的几个步骤说明在VC 2005下如何编译安装并开发Qt4.6应用程序,其实大部分方法和Qt4.6.0是一样的,不过Qt4.6.0集成了Qt Creater,目录的形式有点改变了,现在我就 ...

  9. MyEclipse和Eclipse区别与联系

    Eclipse:IBM花了4千万美金来开发这个IDE(Integrated Development Environment).第一版1.0在2001年11月释出,随后逐渐受到欢迎.Eclipse已经成 ...

  10. uwp - 解决使用EntityFramework时报错“unable to load dll 'sqlite3':the specified module could not be found”

    在使用uwp的ef过程中碰到一万个问题快折腾死我了,好在最后终于解决掉所有问题,但愿如此,因为在这之前先后发生不同的报错,不知道后面还会碰到新的问题不. 其中一个问题是这样的,生成能正常生成,但是启动 ...