【游戏开发教程】BehaviorDesigner插件制作AI行为树(Unity | 保姆级教程 | 动态图演示 | Unity2021最新版)

目录

文章目录

一、前言

嗨,大家好,我是新发。之前用过Behavior Designer行为树插件,最近又需要用到,我发现网上的教程很多写得不够好,今天我就来写写Behavior Designer行为树的教程吧,希望可以帮到大家。

二、插件下载

1、AssetStore下载

插件可以在AssetStore下载到,
地址:https://assetstore.unity.com/packages/tools/visual-scripting/behavior-designer-behavior-trees-for-everyone-15277
在这里插入图片描述
目前最新的版本是1.7.2
在这里插入图片描述
可以在官网查看每个版本的迭代内容:
https://opsive.com/news/category/release-notes/behavior-designer-release-notes/
在这里插入图片描述

2、GitCode下载

我放了1.7.1版本的到GitCode上,地址:https://gitcode.net/linxinfa/unitybehaviordesigner
在这里插入图片描述

下载下来后,放入你的工程即可,
在这里插入图片描述

其中我加了一个FixGUIStyle脚本,
在这里插入图片描述

因为在Unity专业版上,打开Behavior Designer编辑器是这个鬼样,

注:如果你使用的是个人版,则不会有这个问题

在这里插入图片描述

它的GUIStyleUnity专业版本有问题,插件编辑器是个dll文件,我们无法修改编辑器源码,
在这里插入图片描述

然后我进行了反编译,找到了它的封装各种GUIStyle的类:BehaviorDesignerUtility
在这里插入图片描述
通过反射,修改这个类的私有静态成员对象,代码如下:

using UnityEngine;
using UnityEditor;
using System.Reflection;
using BehaviorDesigner.Editor;


/// <summary>
/// 修复BehaviorDesigner编辑器的GUIStyle问题
/// </summary>
public class FixGUIStyle
{

    [MenuItem("Tools/Behavior Designer/修复界面GUIStyle #b", false, 0)]
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void ShowWindow()
    {

        var guiStyle = new GUIStyle();
        guiStyle.alignment = TextAnchor.UpperCenter;
        guiStyle.fontSize = 12;
        guiStyle.wordWrap = true;
        guiStyle.normal.textColor = Color.white;
        guiStyle.active.textColor = Color.white;
        guiStyle.hover.textColor = Color.white;
        guiStyle.focused.textColor = Color.white;

        Texture2D tex2D = new Texture2D(1, 1, TextureFormat.RGB24, false);
        tex2D.SetPixel(1, 1, new Color(0.1f, 0.1f, 0.1f, 1f));
        tex2D.hideFlags = HideFlags.HideAndDontSave;
        tex2D.Apply();
        guiStyle.normal.background = tex2D;

        // 反射动态修改私有静态成员,修改GUIStyle
        var t = typeof(BehaviorDesignerUtility);
        var graphBackgroundGUIStyle = t.GetField("graphBackgroundGUIStyle", BindingFlags.Static | BindingFlags.NonPublic);
        graphBackgroundGUIStyle.SetValue(null, guiStyle);


        var taskCommentGUIStyle = t.GetField("taskCommentGUIStyle", BindingFlags.Static | BindingFlags.NonPublic);
        taskCommentGUIStyle.SetValue(null, guiStyle);


        var selectedBackgroundGUIStyle = t.GetField("selectedBackgroundGUIStyle", BindingFlags.Static | BindingFlags.NonPublic);
        selectedBackgroundGUIStyle.SetValue(null, guiStyle);


        var preferencesPaneGUIStyle = t.GetField("preferencesPaneGUIStyle", BindingFlags.Static | BindingFlags.NonPublic);
        preferencesPaneGUIStyle.SetValue(null, guiStyle);

    }
}

点击菜单Tools/Behavior Designer/修复界面GUIStyle,或者按一下Shift+B快捷键,即可修复GUIStyle问题,

在这里插入图片描述

效果如下
请添加图片描述

三、官方教程

学习插件的时候,建议先看下官方文档。

1、在线文档

官方在线文档: https://opsive.com/support/documentation/behavior-designer/overview/
在这里插入图片描述

2、离线文档

插件的Behavior Designer目录中有个Documentation.pdf文档,不过可能很多同学都没去看这份文档,建议还是看一看,
在这里插入图片描述

四、插件界面

1、打开编辑器

点击菜单Tools/Behavior Designer/Editor即可打开编辑器窗口,
在这里插入图片描述

如下
在这里插入图片描述

2、界面介绍

界面可以划分为3个区域,如下
在这里插入图片描述

1个区域是行为树的组织区域,我们的行为树节点就是在这个区域上进行连线的;
在这里插入图片描述

2个区域顶部是四个标签页:BehaviorTasksVariablesInspector,默认是Tasks标签页,显示的是节点列表;
在这里插入图片描述

标签页说明
Behavior对整个行为树的设置,比如行为树名称、是否激活时立即执行、执行完毕后是否重新执行等设置
Tasks任务节点列表,比如Composities(符合节点)、Decorators(装饰节点)、Actions(行为节点)等等
Variables行为树变量,可以设置全局变量,也可以设置行为树自身的变量
Inspector查看节点详细信息,设置节点参数

3个区域按钮功能如下
在这里插入图片描述

在这里插入图片描述

五、快速制作一棵行为树

在讲解节点之前,我先演示一遍如何快速制作一棵行为树,让大家对整个流程有个概念。

1、创建物体

首先在场景中创建一个物体,可以是任何物体,比如你想控制角色,那么这个物体就可以是你的角色模型,这里我创建一个空物体,
在这里插入图片描述

重命名为BehaviorTreeObj
在这里插入图片描述

2、挂BehaviorTree脚本

打开Behavior Designer编辑器,先选中刚刚的BehaviorTreeObj物体,然后在编辑器网格空白处右键鼠标,点击菜单Add Behavior Tree
请添加图片描述

这一步其实就是给物体挂上BehaviorTree组件,我们也可以通过Add Component手动添加BehaviorTree组件,
在这里插入图片描述

3、添加Task节点

我以最简单的Log节点为例,它的功能就是输出日志,在空白处点击鼠标左键,按键盘的空格键即可弹出节点搜索框,搜索log,可以看到Log节点,点击即可创建出Log任务节点,它会自动帮我添加一个Entry入口节点,Entry连向Log,如下
请添加图片描述

我们给Log节点设置Text字段的值为Hello World,并添加注释,如下
请添加图片描述

4、运行测试

运行Unity,可以看到Log节点被执行了,显示了一个绿色的勾,日志窗口也输出了HelloWorld
在这里插入图片描述

5、导出BehaviorTree

点击Export按钮,将行为树导出保存为资源文件,
在这里插入图片描述

如下,这样子就可以复用行为树资源了,
在这里插入图片描述

6、手动引用BehaviorTree树资源

6.1、设置External Behavior

我们把上面导出的行为树资源赋值给BehaviorTree组件的External Behavior成员,如下
在这里插入图片描述
运行时,就会根据这个External Behavior的行为树来执行了。

6.2、使用Behavior Tree Reference

另外,我们还可以通过Behavior Tree Reference节点来引用行为树资源,
先创建一个Behavior Tree Reference节点,点击Inspector,设置它引用的外部行为树资源即可,
在这里插入图片描述

一般我们会做一个通用的行为树保存为资源文件,然后对特殊的怪物另外做行为树,并让其引用通用行为树资源,像这样子
在这里插入图片描述

7、动态加载行为树资源并设置External Behavior

我们也可以通过代码动态设置External Behavior,我这里使用Addressables插件来做资源加载,

注:关于Addressables的使用,可以参见我之前写的这篇文章:【游戏开发探究】Unity Addressables资源管理方式用起来太爽了,资源打包、加载、热更变得如此轻松(Addressable Asset System | 简称AA)

首先给行为树资源文件添加Addressable管理,分配到一个Addressable索引名,如下

注:勾上Addressable就会自动分配一个索引名了

在这里插入图片描述

接着我们可以使用代码加载行为树资源,动态赋值给BehaviorTree组件的ExternalBehavior 对象,
创建一个AddBehaviorTree.cs脚本,
在这里插入图片描述

代码如下

using System.Collections;
using UnityEngine;
using BehaviorDesigner.Runtime;
using UnityEngine.AddressableAssets;

public class AddBehaviorTree : MonoBehaviour
{
    IEnumerator Start()
    {
        // 初始化Addressables
        yield return Addressables.InitializeAsync();
        // 添加BehaviorTree
        var bt = gameObject.AddComponent<BehaviorTree>();
        // 设置不立即执行
        bt.StartWhenEnabled = false;
        // 加载行为树资源
        var loader = Addressables.LoadAssetAsync<ExternalBehaviorTree>("Assets/BtRes/Behavior.asset");
        yield return loader;
        // 设置ExternalBehavior
        bt.ExternalBehavior = loader.Result;
        // 执行行为树
        bt.EnableBehavior();
    }
}

我们创建一个空物体,重命名BtObj,并给它挂上AddBehaviorTree脚本,如下
在这里插入图片描述

我们运行Unity,可以看到BtObj物体动态挂了BehaviorTree组件,并且动态设置了ExternalBehavior对象,行为树也被执行了,输出了Hello World,如下
在这里插入图片描述

我们可以点击BehaviorTree组件上的Open按钮,
在这里插入图片描述

可以看到行为树已经执行完毕,
在这里插入图片描述

如果我们想让行为树执行完毕后重复执行,可以设置RestartWhenCompletetrue
在这里插入图片描述

我们串一个Wait节点,方便观察,
在这里插入图片描述

执行效果如下,可以看到行为树可以重复执行了,
请添加图片描述

上面我们用到了Squence顺序节点,它是一个复合节点,下文我会进行节点的详细介绍。

六、复合节点:Composities

行为树任务节点中最重要的节点就是复合节点了,它决定了行为树的执行流,也就是Tasks列表中Composities分组的这些节点,
在这里插入图片描述
我先简单做个日常生活的行为树给大家看看,如下
在这里插入图片描述

上面的行为树的执行过程是怎么样的呢?想要搞清楚就得先知道行为树的执行顺序:从左到右,并且深度优先
另外,复合节点又控制着子节点的执行规则,是顺序执行还是并行执行,亦或者随机执行,是遇到成功就立即返回成功还是遇到失败就立即返回失败,亦或者遇到成功继续执行下一个,或者遇到失败继续执行下一个呢?
这些都是复合节点来控制的,这里头的逻辑需要大家好好琢磨,下面我带大家挨个过一遍复合节点,讲解过程中我会穿插顺带讲解一下用到的其他任务节点,并举一些实际的应用场景,方便大家学以致用,也希望大家能够触类旁通。

1、Sequence:顺序节点

1.1、节点介绍

在这里插入图片描述

顾名思义,顺序节点就是从左到右挨个顺序执行,当所有子节点都返回Success时,它才返回Success
请添加图片描述

当某个子节点返回Failure时,顺序节点就会立刻返回Failure
请添加图片描述

1.2、新发口诀

顺序节点,从左到右,为真继续,全真才真,一假即假,一假即停。
类似于逻辑。

1.3、案例1:通知开饭了

我们使用Has Received Event来监听开饭的事件,当它检测收到事件时才会返回Success,此时顺序节点就会去执行下一个子节点,即干饭的逻辑,达到响应事件的功能,如下在这里插入图片描述

其中Has Received Event节点要设置监听的事件名称,定义一个字符串即可,
在这里插入图片描述

抛出事件有两种方式,一种是代码调用行为树的SendEvent接口,如下

public void SendEvent(string name);
public void SendEvent<T>(string name, T arg1);
public void SendEvent<T, U>(string name, T arg1, U arg2);
public void SendEvent<T, U, V>(string name, T arg1, U arg2, V arg3);

示例:

// bt是BehaviorTree对象
bt.SendEvent("EAT_EVENT");

另外一种是使用SendEvent节点,
在这里插入图片描述
设置要发送的事件名称即可,
在这里插入图片描述

1.4、案例2:吃几口饭吃一口菜

想象一下你吃饭的过程,扒拉吃几口饭,然后吃一口菜,我们可以用顺序节点来组织,
在这里插入图片描述

2、Parallel:并行节点

2.1、节点介绍

在这里插入图片描述

并行节点,顾名思义,它会并行执行所有的子节点,所有的子节点都返回Success,并行节点才会返回Success
请添加图片描述
只要有一个子节点返回Failure,并行节点就会立刻返回Failure。如下图,我们可以看到,等10秒和等20秒的两个节点还没执行完就被停止了,因为中间那个等1秒的节点执行完毕并返回了Failure给并行节点,整个并行以Failure结束,那些被终止的Wait也会直接返回Failure
请添加图片描述

2.2、新发口诀

并行节点,并行执行,全真才真,一假即假,一假即停,被停即假。

2.3、案例:边跑步边听歌

比如我们边跑步边听歌,同时还要监听是否有下雨,有下雨的话要中断跑步,如下
在这里插入图片描述
这个例子我用到了Selector节点,并且启用了Self中断,看不懂的同学不要着急,下文我会讲。

3、Selector:选择节点

3.1、节点介绍

在这里插入图片描述
选择节点与顺序节点类似,也是从左到右执行,不同的是,当有子节点返回Success,选择节点就会立刻返回Success不会执行下一个子节点

请添加图片描述

如果子节点返回Failure,会就执行下一个子节点,其实很好理解,就是从左到右遍历选一个Success,有Success就返回Success,否则执行下一个子节点,

请添加图片描述
如果所有子节点都返回Failure,选择节点才返回Failure
请添加图片描述

3.2、新发口诀

选择节点,从左到右,一真即真,一真即停,全假才假。

3.3、案例:诈骗举报概率

比如收到诈骗短信后,有99%的概率会执行举报,有1%的概率会上当受骗,如下
在这里插入图片描述

4、Parallel Selector:并行选择节点

4.1、节点介绍

在这里插入图片描述

顾名思义,就是并行执行选择节点,有一个Success就立即返回Success,并且会终止其他子节点,被终止的子节点会返回Failure,如下
请添加图片描述

所有子节点都返回Failure时,并行选择节点才返回Failure

请添加图片描述

4.2、新发口诀

并行选择节点,并行执行,一真即真,一真即停,全假才假。

4.3、案例:考研和找工作

现在人生道路有两条路:考研、找工作,同时进行,其中一件事情成了,就吃一顿好的庆祝一下,如下
在这里插入图片描述

5、Priority Selector:优先级选择节点

5.1、节点介绍

在这里插入图片描述

这个节点和Selector类似,Selector的执行顺序是从左到右,而Priority Selector会先检查子节点的优先级(priority)进行排序,优先级高的优先执行。
问题来了,我们如何设置子节点的优先级呢?节点的优先级默认是0,如果要修改优先级,需要重写TaskGetPriority方法,
在这里插入图片描述
这里我封装一个带优先级参数的日志节点吧:PriorityLog,创建一个PriorityLog脚本,代码如下

using UnityEngine;

namespace BehaviorDesigner.Runtime.Tasks
{
    [TaskDescription("带优先级参数的日志节点")]
    [TaskIcon("{SkinColor}LogIcon.png")]
    public class PriorityLog : Action
    {
        [Tooltip("Text to output to the log")]
        public SharedString text;
        [Tooltip("Is this text an error?")]
        public SharedBool logError;
        [Tooltip("Should the time be included in the log message?")]
        public SharedBool logTime;

        [Tooltip("优先级")]
        public SharedFloat priority;

        /// <summary>
        /// 重写获取优先级的方法
        /// </summary>
        /// <returns></returns>
        public override float GetPriority()
        {
            return priority.Value;
        }
        public override TaskStatus OnUpdate()
        {
            // Log the text and return success
            if (logError.Value) {
                Debug.LogError(logTime.Value ? string.Format("{0}: {1}", Time.time, text) : text);
            } else {
                Debug.Log(logTime.Value ? string.Format("{0}: {1}",Time.time, text) : text);
            }
            return TaskStatus.Success;
        }

        public override void OnReset()
        {
            // Reset the properties back to their original values
            text = "";
            logError = false;
            logTime = false;
        }
    }
}

现在我们就可以在Tasks列表中看到我们新封装的PriorityLog节点了,
在这里插入图片描述
使用它连个简单的行为树,如下
在这里插入图片描述
我们可以选中节点,在Inspector中设置节点的Priority
在这里插入图片描述
由于右边的节点优先级我们设置为了1,比左边的节点优先级高,所以会优先执行右边的节点,因为右边的节点执行返回了Success,此时Priority Selector就会直接返回Success,不会去执行左边的节点了,如下
请添加图片描述

5.2、新发口诀

优先级选择节点,优先级排序,一真即真,一真即停,全假才假。

5.3、案例:先吵架还是先道歉

我先给行为树创建一Foat类型的变量:SorryPriority,如下
在这里插入图片描述
以它作为道歉优先级变量,
在这里插入图片描述
接着制作如下的行为树,当按下A键的时候,设置SorryPriority变量,提高道歉的优先级,
在这里插入图片描述
其中Set Shared Float节点的设置如下,让它去修改SorryPriority变量为1

请添加图片描述
道歉节点的Priority参数设置为SorryPriority变量,如下
请添加图片描述
我们运行,如果我没有按下A键,则道歉优先级是02秒情绪酝酿完毕后,会默认先执行吵架,

请添加图片描述
如果我在2秒情绪酝酿期间按下了A键,则会设置道歉优先级变量为1,情绪酝酿完毕后就会优先执行道歉,就不会吵架了,
请添加图片描述

6、Random Selector:随机选择节点

6.1、节点介绍

在这里插入图片描述

这个很好理解,按选择节点的规则,只是不是从左到右执行,而是随机顺序执行。
如果有子节点返回Success,则立即返回Success,否则继续执行下一个节点,

请添加图片描述
只有所有的子节点都返回Failure时,Random Selector才返回Failure

请添加图片描述

6.2、新发口诀

随机选择节点,随机执行,一真即真,一真即停,全假才假。

6.3、案例:吃米饭、面条、饺子

中午吃什么好呢?随机选一个吧,好的,吃面条~
请添加图片描述

7、Random Sequence:随机顺序节点

7.1、节点介绍

在这里插入图片描述

这个也很好理解,就是顺序节点的规则,但不是从左到右执行,而是随机顺序执行。

当所有子节点都返回Success时,它才返回Success
请添加图片描述

当某个子节点返回Failure时,顺序节点就会立刻返回Failure
请添加图片描述

7.2、新发口诀

随机顺序节点,随机顺序,为真继续,全真才真,一假即假,一假即停。

7.3、案例:番茄炒蛋

我现在要做一道番茄炒蛋,有的人先炒番茄,有的人先炒鸡蛋,这里我们就可以使用随机顺序节点了,如果炒鸡蛋或者炒番茄失败,那么我们就吃不到番茄炒蛋了,只有两个都成功了,才能吃到番茄炒蛋,
在这里插入图片描述

8、Parallel Complete:并行竞争节点

8.1、节点介绍

在这里插入图片描述
这个节点其实就是并行执行所有的子节点,只要有一个子节点返回了结果,它就结束,并以这个子节点的结果为结果,
请添加图片描述
请添加图片描述

8.2、新发口诀

并行竞争节点,并行执行,最速之子,以子为果。

9、Selector Evaluator:评估选择节点

9.1、节点介绍

在这里插入图片描述
这个节点不是很好理解,它的逻辑是这样的,从左到右顺序执行,如果遇到子节点返回Success,则立即结束,并返回Success,否则继续执行下一个子节点。

请添加图片描述
请添加图片描述
有同学会问了,那它与Selector节点有啥区别?
上面我用的是Log节点,会立即返回结果,如果是用Wait节点,则会有Running状态,这个Selector Evaluator节点是个固执的强迫症节点,它认为子节点应该给它返回Success,如果子节点返回了Failure,它会硬着头皮执行下一子节点,但是,一旦下一个子节点处于Running状态,它就会像个渣男一样毫不犹豫地打断并返回第一个子节点,重新执行,只要有机会它就想要一个Success结果,如果后续的子节点都是立即返回成功或失败,而没有Running状态,则它会接受后续子节点的Success,也就是说,它也不是非得第一个子节点不娶,后面的子节点只要立刻Success,它就不打断去重新找第一个子节点了,相当地渣男啊!

看,第二任只要考虑一下(Running状态),它就会立即打断回去执行初恋,
请添加图片描述
如果第二任立即答应了,它就会接受第二任的结果,
请添加图片描述
如果第二任直接给个Failure,那么这个渣男会去骚扰路人甲,如果路人甲给它Success,它就会爱上路人甲,

请添加图片描述
如果路人甲立即给它Failure,这个渣男才会死心,
请添加图片描述

9.2、新发口诀

评估选择节点,渣男一个,从左到右,遇真即真,遇假继续,Runing时机打断,从头来过。

七、中断

复合节点有中断的权利,我们可以选中某个复合节点,然后点击Inspector,设置它的中断模式,
在这里插入图片描述
默认为None,即无中断,另外三个中断模式,下文我会挨个进行演示。
这里我先解释一下什么是中断,比如你正在睡觉,突然地震了,这个时候睡觉就要被中断,执行逃跑的逻辑,命要紧。
复合节点的中断控制是由谁来调度的呢?细心的同学应该会发现,执行行为树的时候,会自动创建一个Behavior Manager对象,上面挂着BehaviorManager脚本,我们的行为树的执行都是由这个BehaviorManager来调度的,它就是最高司令,
在这里插入图片描述
感兴趣的同学可以反编译BehaviorDesigner.Runtime.dll,查看BehaviorManager的源码,
在这里插入图片描述

1、Self中断:老妈喊你回家吃饭

一个复合节点下可以有多个子节点,假设现在有两个子节点,假设此时正在执行第二个子节点(Running状态),突然来了一个事件,需要中断第二个子节点,立即执行第一个子节点,这里就可以使用Self中断,Self其实是站在复合节点的角度的,就是中断复合节点自己。

假设你去朋友家打游戏,你决定打完游戏就回家,如果打游戏过程中,老妈打电话喊你回家吃饭,那么就中断打游戏,回家。

行为树如下
在这里插入图片描述

没有按下A键的执行效果如下,打完游戏开开心心回家,

请添加图片描述
打游戏过程中,按下A键,中断打游戏,回家,
请添加图片描述

2、Lower Priority中断:睡觉时地震

我们以睡觉的时候发生地震为例,演示一下Lower Priority中断。
睡觉的时候如果发生地震,需要中断睡觉,执行逃命逻辑。
如下,睡觉节点与Sequence节点是平级关系,并且睡觉节点在右边,属于低优先级,当正在执行睡觉节点时,左边的Sequence节点想要中断右边的睡觉节点,设置Lower Priority中断即可,
请添加图片描述
我们从中断的图标也可以看出,它指向右边,表示中断右边的节点的意思,
在这里插入图片描述

3、Both中断:两者都中断

Both中断其实就是SelfLower Priority都中断。如下
请添加图片描述

八、Variables:行为树成员变量

我们点击Variables,可以给行为树添加成员变量,也可以查看已添加的成员变量,
在这里插入图片描述

1、添加变量

先起一个变量名,然后选择变量的数据类型,我们也可以添加自定义的数据类型,需要进行变量类型拓展,下文会讲解拓展变量类型的步骤,这里我们先选一个String好了,最后点击Add按钮即可,
在这里插入图片描述
我们可以修改变量名,给变量设置初始值,如下
在这里插入图片描述

2、读取变量

现在我们想通过Log节点把myName这个变量输出出来。
先选中Log节点,切到Inspector页,点击Text值最右边的小点点,如下
在这里插入图片描述
此时会出现红色的感叹号
在这里插入图片描述
我们点击下拉框,选中myName变量即可,
在这里插入图片描述
我们运行行为树,可以看到日志输出了变量值,如下
在这里插入图片描述

3、修改变量

我们在Log节点左边串一个Set String节点,

注:如果你的变量类型是Bool,则这里要使用Set Bool节点,其他类型同理,选择对应的Set节点,如果是自定义的数据类型,则要自己拓展一个Set变量值的节点才行。

在这里插入图片描述
此时带有一个红色感叹号,我们需要给它设置要修改的变量,如下
在这里插入图片描述
我们运行行为树,可以看到输出的变量值是皮皮猫了,
在这里插入图片描述

4、拓展变量类型

实际项目中,我们大概率是要拓展行为树的变量类型的,比如我现在有个Blog类,

/// <summary>
/// 博客
/// </summary>
public class Blog
{
    /// <summary>
    /// 作者
    /// </summary>
    public string author;
    
    /// <summary>
    /// 博客地址
    /// </summary>
    public string url;

    public override string ToString()
    {
        return $"author: {author}, url: {url}";
    }
}

我想让他出现行为树Variables的变量类型列表中,我们需要给它包装一层SharedBlog
新建一个SharedBlog脚本,
在这里插入图片描述
按照下面这个格式写即可:

namespace BehaviorDesigner.Runtime
{
    [System.Serializable]
    public class SharedT : SharedVariable<T>
    {
        public static implicit operator SharedT(T value)
        {
            return new SharedT { mValue = value };
        }
    }
}

SharedBlog代码如下:


namespace BehaviorDesigner.Runtime
{
    [System.Serializable]
    public class SharedBlog : SharedVariable<Blog>
    {
        public static implicit operator SharedBlog(Blog value)
        {
            return new SharedBlog { mValue = value };
        }
    }
}

此时,我们就可以在变量类型列表中看到我们拓展的Blog变量类型了,
在这里插入图片描述
我们给行为树添加一个Blog变量吧,
在这里插入图片描述

5、代码读写变量

上面我们拓展了一个Blog变量类型,并给行为树添加了一个Blog变量,现在我们想在代码中对这个变量进行访问和修改,我们可以通过BehaviorTreeGetVariable方法和SetVariable方法,

public SharedVariable GetVariable(string name);
public void SetVariable(string name, SharedVariable item);

例:

// 获取行为树对象
var bt = GetComponent<BehaviorTree>();

// 访问成员变量-----------------------------------------
var sharedBlog = (SharedBlog)bt.GetVariable("myBlog");
Debug.Log(sharedBlog.Value.ToString());

// 修改变量值-----------------------------------------
sharedBlog.Value.author = "Unity3D技术博主:林新发";
bt.SetVariable("myBlog", sharedBlog);


// 构建新的成员对象-----------------------------------------
SharedBlog newSharedBlog  = new SharedBlog();
Blog newBlog = new Blog();
newBlog.author = "博客技术专家:林新发";
newBlog.url = "https://blog.csdn.net/linxinfa";
newSharedBlog.SetValue(newBlog);
// 赋值成员变量
bt.SetVariable("myBlog", newSharedBlog);

九、行为树事件

上文讲Sequence节点的时候已经举例讲了行为树事件,不过有些细节没有讲到,这里我进行补充。
与事件相关的节点是Send EventHas Received Event
在这里插入图片描述
另外,我们也可以通过代码来发出事件和监听事件,下面给大家演示一遍。

1、Send Event节点

我们首先看Send Event节点,可以在Inspector面板看到它的参数,
在这里插入图片描述

1.1、Target Game Object参数

行为树之间的事件是可以跨物体发送的,比如A物体上的行为树给B物体上的行为树发送事件。
默认为None,事件会发给自身的行为树。
我们可以查看SendEvent节点的源码,它调用了GetDefaultGameObject方法去获取目标物体,
在这里插入图片描述
这个方法的源码如下:
在这里插入图片描述
意思就是如果为空,就返回自身的gameObject,也就是行为树自身的物体。
如果我们想要设置这个SendEvent节点的Target Game Object参数,可以通过行为树变量来传递,比如在Variables中定义一个targetObj
在这里插入图片描述
然后SendEventTarget Game Object引用这个targetObj变量,
在这里插入图片描述
而这个targetObj变量的赋值,我们可以通过代码来赋值,例:

// go是目标物体GameObject对象,bt是自身行为树对象
SharedGameObject targetObj = new SharedGameObject();
targetObj.Value = go;
bt.SetVariable("targetObj", targetObj);
1.2、Event Name参数

Event Name就是事件名称,自己定义一个字符串即可,建议为XXX_EVENT

1.3、Group参数

在这里插入图片描述
这个Group参数又是干啥用的呢?因为一个物体可以挂多个BehaviorTree组件,假设挂了3BehaviorTree组件,我只想给其中的两个BehaviorTree发送事件,这个时候就要用到Group了。
我们可以在BehaviorTree组件上看到有一个Group参数,
在这里插入图片描述
我们可以给这些BehaviorTree分组,默认Group0
在这里插入图片描述
如果SendEventGroup也是0,那么这个物体下所有Group0BehaviorTree都会收到事件。

1.4、Argument参数

在这里插入图片描述
我们可以看到有三个Argument参数,一般是用来做跨行为树发送事件时的数据传递用的。
比如A打了BB收到被打事件,但它并不知道是谁打它的,我们可以给它们定义一个playerId成员变量,

注:这里你使用GameObject变量也可以,看具体需要~

在这里插入图片描述
发事件的时候把playerId传递过去,B在收到事件后会去读取这个参数,B就知道是A打了它了,
在这里插入图片描述

2、Has Receive Event节点

Has Receive Event节点参数如下,
在这里插入图片描述

2.1、Event Name参数

Event Name参数就是要监听的事件名称,与SendEventEvent Name要对应上。

2.2、Stored Value参数

Stored Value参数用来存储传递过来的数据的,与Send Event的三个Argument顺序对应。注意,这里是把传递过来的参数存储到Stored Value设置的变量中,比如A行为树传递了一个playerId过来,表示打人者的IdB行为树收到事件,把这个plaeyrId存储到fromPlayerId变量中,
在这里插入图片描述
然后我们可以在代码中通过B行为树的GetVariable方法读取到这个fromPlayerId

var fromPlayerId = (SharedInt)bt.GetVariable("fromPlayerId");
Debug.Log($"{fromPlayerId} 打了我");
3、代码中注册和注销事件

有时候我们需要在代码中自己注册事件的响应函数,可以调用行为树的RegisterEvent方法,

public void RegisterEvent<T, U, V>(string name, Action<T, U, V> handler);
public void RegisterEvent<T, U>(string name, Action<T, U> handler);
public void RegisterEvent<T>(string name, Action<T> handler);
public void RegisterEvent(string name, System.Action handler);

对应的,要注销事件的响应函数,则调用UnregisterEvent方法,

public void UnregisterEvent<T, U, V>(string name, Action<T, U, V> handler);
public void UnregisterEvent<T, U>(string name, Action<T, U> handler);
public void UnregisterEvent<T>(string name, Action<T> handler);
public void UnregisterEvent(string name, System.Action handler);

可以查阅Has Receive Event节点的源码
在这里插入图片描述

十、拓展行为树节点

1、继承

实际项目开发过程中,我们一般都需要自己拓展一些行为树节点,我们知道,所有的节点都是继承Task的,然后根据节点的功能分类,有细分了一些子类出来,比如复合节点的继承关系如下:
在这里插入图片描述
我们我们想要封装一个新的复合节点,也继承Composite然后进行拓展即可。
在这里插入图片描述

同理,Action类的节点也如此,
在这里插入图片描述

我们可以参考Log节点、Wait节点的写法,自行拓展自己的Action节点。

2、案例:MoveTo

举个例子,现在要写一个移动主角到指定Transform位置的Action,首先创建一个MoveTo脚本,
在这里插入图片描述
引入BehaviorDesigner的命名空间,然后继承Action

using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

// 移动主角到指定Transform位置
public class MoveTo: Action
{
}

接着定义一些必要的public成员变量,这些变量会在Inspector面板中显示,

using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

// 移动主角到指定Transform位置
public class MoveTo : Action
{
    public float speed = 0;
    public SharedTransform target;

}

再接着,我们重写OnUpdate方法,如下

public override TaskStatus OnUpdate()
{
   if (Vector3.SqrMagnitude(transform.position - target.Value.position) < 0.1f) {
      return TaskStatus.Success;
   }
   transform.position = Vector3.MoveTowards(transform.position, target.Value.position, speed * Time.deltaTime);
   return TaskStatus.Running;
}

这样,我们自己封装的MoveTo节点就完成了,
在这里插入图片描述
我们连个行为树,让Cub来回移动,效果如下
请添加图片描述

3、官方Sample包

BehaviorDesigner官方自己写了一些拓展包,我们可以把对应的包导入项目中使用,
在这里插入图片描述
这里分享给大家,
链接:https://pan.baidu.com/s/1mQUkxbeokOh6oyTmEG837w
提取码:oi74

十一、完毕

好了,就先写到这里吧,我是林新发:https://blog.csdn.net/linxinfa
一个在小公司默默奋斗的Unity开发者,希望可以帮助更多想学Unity的人,共勉~

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2023年2月24日 下午10:25
下一篇 2023年2月24日 下午10:26

相关推荐