分享

手把手教你使用Behavior Designer

 罘罳_图书馆 2018-09-03

首先科普下:

什么是行为树
如果了解过状态机,会知道在行为树之前,在实现AI用得比较多的技术是状态机,状态机理解起来是比较简单的,即一个状态过渡到另一个状态,通过判断将角色的状态改变即可,如果学习过Unity的Mecanim动画系统,会更加直观的理解。


但是状态机在状态较多的情况下会使状态之间的切换变得异常繁琐,同时状态之间很难复用。


在这种情况下,行为树被发明出来,行为树的优点如下:

行为树提供大量的流程控制方法,使得状态之间的改变更加直观;
整个游戏AI使用树型结构,方便查看与编辑;
方便调试和代码编写;
最重要的:行为树方便制作编辑器,可以交由策划人员使用;

 

ai到底是啥子呢,玩过游戏的应该都知道,那些小兵跟boss之所以会智能得追着你打跟放招,其实那些就是ai啦,说高达上一点就是“人工智能”

看看下面这个boss

你想知道boss是的ai怎么做的吗,来,跟着我一起学Behavior Designer

---------------------------------------------------------------------------------------------------------------------------------------------

Behavior Designer就是Unity的一个制作行为树的插件啦

Behavior Designer的使用教程网上比较少,今天我就来写下教程好了,毕竟是个好东西,跟大家分享下。

导入Behavior Designer后,可以在Tools/Behavior Designer/Editor打开编辑器,如下图所示

编辑器视图如下

1部分:

行为树的组织区域

 

2部分:

一堆Task,主要先掌握Composities(符合节点)、Decorators(装饰节点)和Actions(行为节点),如何组织是难点

 

3部分:

Behavior标签,可以设置行为树的一些属性

注意这个Restart When Complete,如果勾选了,则行为树遍历完后会再重新启动一次,不断循环

这些属性也可以通过代码进行设置,一般我们编辑好行为树后会导成asset文件作为ExternalBehavior,这样可以复用这个行为树。

假设我们已经导出在Resources目录下,叫Behavio.asset,然后现在有一个cube,我们想给这个cube绑上行为树,我们可以这样做

 

  1. var bt = cube.AddComponent<BehaviorTree> ();
  2. var extBt = Resources.Load<ExternalBehaviorTree> ("Behavior");
  3. bt.StartWhenEnabled = false;
  4. bt.ExternalBehavior = extBt;

注意:以上用到了Resources.Load方法,所以行为树资源务必放在工程目录Resources中,如果没有Resources目录,则在Assets目录中新建一个Resources目录。

其中之所以把StartWhenEnabled置为false是因为我们不想让行为树立刻启动,我们可能有一些初始化操作需要进行

如果无需做额外的初始化,也可直接设置bt.StartWhenEnabled=true,否则行为树启动后运行一次就不在循环运行了。

我们昨晚初始化操作后,可以通过bt.EnableBehavior ();来启动行为树。

 

Variables标签,可以给行为树添加变量,这些变量可以通过GetVariable跟SetVariableValue来读取和赋值,并可以在各种Task节点里进行传递和赋值,这个后面讲下方法

 

Inspector标签就是Task的检视窗口啦,我们可以在这个标签里设置Task的属性什么的

 

4部分:

这里是当前的行为树物体和对象,可以点击进行切换不同的行为树物体和行为树对象,一个物体可以绑定多个行为树,不过一般只绑一个就可以了,左边的左右箭头可以切换行为树物体,右边的+-好可以怎加和删除行为树。

 

5部分:

Export,就是把行为树导出成外部文件,可以选择二进制文件或者Json文件,建议使用二进制文件,导出的格式可以在6部分那里进行设置

二进制文件的话导出是一个xxxx.asset文件,它是作为ExternalBehaviorTree存储的,所以使用的时候要注意下。

如果是保存成Json文件,那就是一个xxxx.prefab文件,可以理解为就是个EmptyObject上挂着个BehaviorTree

 

6部分:

主要就是那个导出文件的格式设置啦,如下图:

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

一条华丽丽的分割线

简单说完了界面,我们现在开始讲下具体使用吧。

第一步,给一个物体添加行为树

方法1:打开行为树编辑器,然后选中要添加行为树的物体,然后在编辑器编辑区右键Add Behavior Tree

方法2:直接给物体AddComponent一个BehaviorTree组件,然后点击Open打开即可进行编辑

 

添加了行为树,现在以一个简单的例子讲下树的制作。

假设我们要做一个吃饭、睡觉、地震啦逃跑的ai,可以想象一下,我们需要有事件发送和接受,比如我们告诉小a说你去吃饭吧,那么他收到事件后就去执行吃饭,我们发送时间告诉小a说去睡觉吧,他就去睡觉了,我们要需要中断,比如正在睡觉,突然地震啦,赶紧逃命。

1 首先点击Unity顶部菜单栏Tools->Behavior Designer->Editor,打开行为树编辑器

2 在Hierarchy窗口中鼠标右键->Create Empty,创建一个GameObject

3 如果没有选中任何物体,行为树编辑器(上面说到的1区域)中会显示“Select a GameObject”,我们要给上面的GameObject添加行为树,所以选中GameObject,此时会显示"Right Click, Add a Behavior Tree Component",我们在行为树窗口中右键->Add Behavior Tree,此时会看到GameObject被添加了BehaviorTree组件,编辑器中显示“Add a Task”,接下来我们就可以开始添加节点了

4 我们先添加一个Selector节点,找到上文说的2区域,在Composites标签列表中找到Selector,点击,即可看到在组织区域中多了一个Entry节点和一个Selector节点,并且它们之间用连线连了起来。Selector节点就像or逻辑一样,它从做到右知行,遇到success则立刻返回success,如果遇到failure,则继续知行右边的节点。

5 接下来添加Sequence节点,同理,它在Composites标签中,点击Sequence,会在组织区域中创建一个Sequence节点,注意它可能被之前的节点挡住,可以稍微一动一下节点的位置。Sequence节点就像and逻辑,从做到右知行,遇到failure则立刻返回failure,如果遇到success则继续执行下一个节点,直到从左到右所有子节点都返回success,它才返回success。

6 创建事件接收节点:Has Received Event,它在Conditionals标签中,如果很难找,可以在2区域中上面的搜索框里直接输入名字查找,有个小小的放大镜图标那里。Has Received Event用来做事件接收检测,事件已字符串作为标识,比如事件"Eat"这样子,事件自己来定义。点中刚刚创建的Has Received Event节点,然后在3区域中点Inspector标签,可以看到节点的属性,我们会看到Event Name这个属性,这就是事件标识,在这里设置好Event Name,我这里做了三个事件:"Eat","Sleep","EarthQuake"。

7 添加Log节点,它被执行的时候会输出log,打log的内容可以在它的节点属性Text设置

8 新创建两个自定义的节点Eat和Sleep,一般我们在做特殊行为的时候,都需要自己拓展出一些新自定义的节点,来实现对应的需求。Eat和Sleep的代码看下文,它要继承Action。

9 新建行为树变量,在3区域中点击Variables标签,会看到Name,Type和一个Add按钮,在Name中输入变量的名字,在Type中选择变量的类型,然后点击Add按钮即可添加变量。我们添加一个String类型的food变量,和一个Int类型的sleepTime变量。

最终如下:

行为树的变量如下:

行为Eat和Sleep是另外写的Action,代码如下:

Eat.cs

 

  1. using UnityEngine;
  2. using System.Collections;
  3. using BehaviorDesigner.Runtime;
  4. using BehaviorDesigner.Runtime.Tasks;
  5. public class Eat : Action
  6. {
  7. public SharedString food;
  8. public override TaskStatus OnUpdate ()
  9. {
  10. Debug.Log ("eat: " + food.GetValue());
  11. return TaskStatus.Success;
  12. }
  13. }


Sleep.cs

 

 

  1. using UnityEngine;
  2. using System.Collections;
  3. using BehaviorDesigner.Runtime;
  4. using BehaviorDesigner.Runtime.Tasks;
  5. public class Sleep : Action
  6. {
  7. public SharedFloat sleepTime;
  8. private float m_sleepTime;
  9. private float m_startTime;
  10. public override void OnStart()
  11. {
  12. m_startTime = Time.time;
  13. m_sleepTime = (float)sleepTime.GetValue ();
  14. }
  15. public override TaskStatus OnUpdate()
  16. {
  17. if (m_startTime + m_sleepTime < Time.time)
  18. {
  19. return TaskStatus.Success;
  20. }
  21. Debug.Log ("I am Sleeping");
  22. return TaskStatus.Running;
  23. }
  24. }


其中Eat的属性如下

 

同理Sleep的属性如下:

需要注意一个点,就是Task中断,中断有3种类型:Self,Lower Priority,Both

怎么理解中断呢,行为树是从上到下从左到右知行的,假设知行到右边第二个节点,这个节点假设是一个持续运行的节点,比如“睡觉”,而此时突然收到一个“”地震”的消息,就必须立刻中断“睡觉”。

Self就是中断自己的意思,

Lower Priority是中断比自己低权限的节点,在行为树种,右边的节点比左边的节点权限低。

Both就是中断自己和比自己低权限的节点。

只有复合节点(Composites标签中的那些节点,比如上面的Selector和Sequence)有中断属性。我们选中Sequence,在它的Inspector中可以看到Abort Type属性,我们设置成Loawr Priority。

 

我们地震的优先级最高,所以放在最左边,优先级最低的是睡觉,地震要中断吃饭跟睡觉,吃饭要中断睡觉

我们把上面的行为树导成二进制文件,放到Resources目录下。(注:如何把行为树导成二进制文件?上文的5区域有个Export按钮,点它就可以导出了,这个二进制文件怎么用起来呢?可以通过代码动态绑定到物体上,也可以直接在物体上添加Behavior Tree组件然后把二进制文件拖到External Behavior属性中)

接下来,现在我们开始写控制代码:

我们创建一个Cube,然后新建一个Runner脚步挂在Cube上,代码如下:

Runner.cs

 

  1. using UnityEngine;
  2. using System.Collections;
  3. using BehaviorDesigner.Runtime;
  4. public class Runner : MonoBehaviour {
  5. private BehaviorTree m_bt;
  6. void Start ()
  7. {
  8. var bt = gameObject.AddComponent<BehaviorTree> ();
  9. var extBt = Resources.Load<ExternalBehaviorTree> ("Behavior");
  10. bt.StartWhenEnabled = false;
  11. bt.ExternalBehavior = extBt;
  12. bt.EnableBehavior ();
  13. m_bt = bt;
  14. }
  15. void Update () {
  16. if (Input.GetKeyDown (KeyCode.A))
  17. {
  18. //吃红烧牛肉面//
  19. m_bt.SetVariableValue ("food", "红烧牛肉面");
  20. m_bt.SendEvent ("Eat");
  21. }
  22. if (Input.GetKeyDown (KeyCode.B))
  23. {
  24. //谁10000秒//
  25. m_bt.SetVariableValue ("sleepTime", 10000f);
  26. m_bt.SendEvent ("Sleep");
  27. }
  28. if (Input.GetKeyDown (KeyCode.H))
  29. {
  30. //地震啦//
  31. m_bt.SendEvent ("EarthQuake");
  32. }
  33. }
  34. }

 

 

 

 

 

测试效果如下:

 

这只是一个简单的例子,还有很多复杂的ai,等后面有事件研究了再进行补充。

好啦,今天就先写到这里吧

 

 

 

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多