共享性行为树的新实践-C#描述

这次和大家分享一下,前段时间学习unity和c#的时候,用到的一个行为树的实现方式,使用到了c#语言,并且选择了共享性行为树的方式。行为树的构建,还是用纯代码的方式,没有做工具,也没有做数据驱动的方式来编辑和加载。做工具的话,可能需要很多时间,至于数据驱动,难度不大,如果大家有需要,稍加改进,就应该就能自己实现。

行为树的概念这里就不再赘述,博客上已经有很多了,至于共享性行为树,以前写过两篇文章(12),也做过一个例子,这里再简要提一下,原本的行为树的实现是一个智能体上绑定一个行为树的实例,这个实例里面就包括了构建行为树的结构数据,已经行为树在运行过程中的运行时数据,而对于同一种行为树来说,它的结构数据是一样的,多个实例的话,其实是多个同样的拷贝,这个在内存上是不划算的,特别如果这个智能体是运行在服务器上,比如要跑1000个使用同一种行为树AI的怪,那其实它们可以共享这颗行为树的结构,而只需要实例化运行时数据就可以了,具体可以看下面的图

SharedBevtreeNode.png

这就是共享性行为树的基本概念,可以说这种改进对于内存消耗是有利的(虽然内存很便宜,但能省一点是一点)。我在上次文章中的实现方式是参考了aigamedev.com上的内容,没有真正在项目里用过,这个实现主要由几个问题:

  • 存在大量的内存分配和释放:需要用内存池来管理,增加了复杂度
  • 增加了复杂性:这个实现写起来不够简洁,如果为了省一些内存,平白增加了复杂性,有些得不偿失。

所以我就想用共享性行为树的概念,重新实现一下,其实客户端用不用共享性行为树没什么大关系,因为客户端一般很少会存在同时有大量智能体的情况,对于移动平台,cpu和gpu也会首先撑不住。那为什么我会选用呢?原因有两个:

  • 直接移植了服务器的代码:也是因为学习golang的关系,我先写了个golang语言版本的共享性行为树,所以客户端其实是移植于这个版本的。
  • 新的实现基本和原来的行为树使用方式一致:由于我用的方法,没有很大的增加使用的复杂性,所以秉着“能省一点是一点”的原则,就都用了共享性行为树的方式

这次我还是做了一个untiy能跑的demo,demo里演示的是和原本的行为树demo里类似的行为,不过换成了3D表现,感觉一下子高大上了很多~

old-demo

new-demo

基本行为描述如下,首先会每隔一段时间会产生一个移动目标点(白色立方体),然后智能体(僵尸)会旋转并朝向目标点后,朝目标点移动,当到达目标点后,会开始攻击,整个攻击行为会持续5秒钟,最后倒地结束,一直到新的目标点产生,智能体循环执行上述行为。

整个demo可以在untiy5里面执行,打开Scenes目录下面的demo1,执行后,屏幕左上角有一个按钮,每点一次,就会加一个僵尸到场景中,还有一个滑动条,可以加速和减速整个游戏。

demo_debugui

整个demo工程可以点击这里下载

首先介绍一下这次新写的行为树的一些东西。

整个计算要做到结构上的共享,最重要的部分就是做到结构和数据的分离,也就是说整个树的计算是无态的,只要做到了这一点,就可以做到共享行为树的结构,所以在设计上,我把单个智能体在执行的状态数据都放到了传入的Workingdata的结构中,包括控制节点和行为节点上所有定义的状态数据。在这个结构中,我维护了一个字典,当新建行为树每一个节点的时候,都会附带生成一个当前行为树唯一的id值,把这个id做为这个节点的标示,也作为索引状态数据的key值。这样我就可以方便的从WorkingData里取出当前节点所对应的状态数据了

仔细看每一个控制节点的代码的时候,可以发现,所有的运行时数据都包在了一个context的数据结构里,当我要进行行为树计算的时候,就需要先从WorkingData中取出当前节点的context数据,然后根据context数据再进行下一步的计算操作。用户定义的行为节点也一样,例子的话,大家可以看AIBehavior.cs里的NOD_Attack类,看我是如何让僵尸攻击5秒钟的,唯一有一点小小的区别是,在用户的行为节点中所定义的运行时数据是放在TBTActionLeaf::TBTActionLeafContext里的userdata字段中的,我也提供了封装了方法,可以安全的访问。

在这样的封装和改造后,大家可以发现,现在对于行为树行为节点的写法和原本的行为树的写法几乎没有什么区别,但结构上已经变成了共享性的行为树结构。所以,不管我在场景中放了多少个僵尸,其实他们所使用的行为树实例只有一份,具体的可以参看AIBehavior.cs里面的AIEntityBehaviorTreeFactory类

行为树的部分就说这么多了,因为其实大部分的内容和以前分享的差不多,只是这次在结构上做了一些改进,具体的大家还是直接看代码更直观一点。接下来再可以聊聊这个demo所演示的另外一些东西

  • 分层的架构:在GameUpdater这个类里面,我演示了如何分离决策层和行为层,目前的决策层只有一个逻辑,就是产生新的移动目标点,如果当决策层比较复杂的时候,在决策层也可以使用行为树来产生决策,我在现在的项目里就是这样用的,这就是层次化的行为树结构,这样的分离带来的一个额外的好处就是可以方便的分割服务器和客户端的逻辑分离点,换句话说,我们可以很容易的决定,哪些逻辑应该写在服务器上,哪些逻辑应该写在客户端,关于这个话题可以以后继续展开。
  • 决策层和行为层的”粘合剂”–请求:在决策层和行为层的中间,我定义了请求层,在博客的两篇文章(12)中,我也提到过,关于请求层的作用,大家可以去翻一下,在这个demo里,我演示了如何在决策层中产生一个请求,以及如何在行为层中处理一个请求,当然,一般来说,我们会在WorkingData中把请求直接传递到行为层,而不是像我这里所写的,仅仅把一个值通过黑板传递过去
  • 使用黑板:在AI库中,我定义了一个简单的黑板的结构,在demo中,也演示了如何利用黑板传递一些数据,一般我们会将一些行为树需要用的额外数据结构存在黑板中,比如从世界中收集的信息,从其他模块传来的数据等等,黑板的结构非常适合作为这些数据的存储器来使用

关于这个AIToolkit,有时间,我会持续的更新,我的目标是把一些AI中常常会用到的东西做成一个个模块,彼此相对独立,并且尽量轻量化。欢迎多多讨论,也欢迎来信,或者在讨论区交流。

今天就先聊到这里,最后,感谢所有关注和支持我的朋友们,很开心写的东西能对大家有所帮助。

————————————————————————
作者:Finney
Blog:AI分享站(http://www.aisharing.com/)
Email:finneytang@gmail.com
本文欢迎转载和引用,请保留本说明并注明出处
————————————————————————

(已被阅读9,060次)

12 评论

  1. Finney :让策划来使用行为树编辑器有几个需要预先准备的事情,也可以说是前提,一个是行为树的编辑器,一个是对于行为树概念的理解,一个是行为树模块的合理性,逻辑的抽象和分隔比较合理,另一个是行为树比较复杂,或者行为树的种类比较多。有了这些准备,策划才能参与得比较好,要不反而会事倍功半。目前我们还没有具备这样的条件,不管是编辑器,还是策划层面,还是需求层面(行为树比较简单而且单一),所以现在还是由程序员来搭建的行为树的

    我们这面也是c++的,想基于行为树来重构AI部分。和策划对接的部分不知道如何执行。如果是程序来搭建行为树,策划的需求该如何去实现。比如策划提出“在血量低于80%的时候释放一个技能A,其他时间里,每三秒释放一个技能B”。这里把这种需求做成一种机制,让策划去配置【80%,技能A,技能B】,这样的么?当有一个需求时,策划需要做什么?程序需要做什么?能具体些么

    1. 如果用你说的这个例子,想要策划来参与的话,首先需要对于策划提的这些条件和行为做抽象,比如“血量低于80%”是一个需要抽象东西,“每三秒”也是一个需要抽象的东西,“释放技能”是一个行为等等,程序就是需要预备这些抽象后模块,再结合行为树的控制节点,才能让策划参与的比较舒服。要不策划还是只能填填数值,没法真正参与搭建行为树

    2. 嗯嗯,策划只填数值的话,他也很难去理解这个AI,这些数值为什么存在。所以策划是参与到行为树的搭建的。就我的理解中,有一些需求是完全可以程序自己去做的,比如NPC的基础AI,这个不会涉及到数值,那么这种情况是完全可以程序自己做的,无论是用行为树还是用其他的都行。但如果AI涉及到数值,类似以上的各种数值的抽象,如果采用行为树的话,最后把这些数值整合到一起,还是得依赖编辑器。难道说必须撸个编辑器出来,囧

    3. 在这里看到了这个问题:我也来说说我的看法。
      1. 刚开始是让策划编辑整个行为树,从entity出生死亡攻击等等一切的行为。但是出问题了太难维护了。
      2.后来,还是使用传统方式,将entity行为抽象为,事件-动作接口。服务器生成事件后调用一颗事件行为树。
      策划在这颗树里调用程序提供的动作接口。如同最最开始接触c++/lua驱动的ai一样。事件接口,只是使用到了行为树可视化编辑这么一个好处。策划需要拖动各种节点来编辑逻辑。
      3. 对策划如何理解使用行为树,策划需要理解的是,行为树中的3个控制节点,条件节点,顺序节点,选择节点。剩下的将叶子节点抽象为entity可以执行的动作。受限的顺序节点,每个顺序节点执行都会返回成功,防止条件太多debug困难。

    1. 我现在基于你的文章实现了一套基于cocos2d-x的行为树。实际使用中发现如果有画图工具的话,跟策划交流起来更加直观(我都是在纸上画)。后来在网上找到个在线行为树编辑器,比较轻量级,导出json。看了下其作者的文章,实现行为树的思路基本跟你一样,唯一有区别的地方是在判断条件的处理上,你是把前置条件绑定在一个节点,他是把判断条件也作为一个节点,然后通过sequence将condition和action节点组合起来。http://behavior3.com/

    2. 这种实现也有,我只是觉得,太多的sequence有点啰嗦,所以改成专门的绑定条件的方式。在线的编辑器不错,我去看看。其实unity里面还有一种做法,是用编辑器本身的树形结构来搭建行为树,然后绑定对应的脚本,也是可以的。

    3. 用绑定条件的方式对于程序来说会比较简洁,但是在跟策划交流的时候不太容易说明白,因为不好用图形表示我在这个节点上绑定了多少个条件,必须要在旁边用文字标注清楚。unity对于AI设计支持还是很好的,cocos2dx这一块基本是空白,只能自己搭建。

    4. 让策划来使用行为树编辑器有几个需要预先准备的事情,也可以说是前提,一个是行为树的编辑器,一个是对于行为树概念的理解,一个是行为树模块的合理性,逻辑的抽象和分隔比较合理,另一个是行为树比较复杂,或者行为树的种类比较多。有了这些准备,策划才能参与得比较好,要不反而会事倍功半。目前我们还没有具备这样的条件,不管是编辑器,还是策划层面,还是需求层面(行为树比较简单而且单一),所以现在还是由程序员来搭建的行为树的

发表评论

邮箱地址不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

Copyright © 2011-2020 AI分享站    登录