Unity-Behavior Designer详解

本文详细介绍了Unity中的Behavior Designer,涵盖了行为树的基本概念、任务类型(如Action、Composite和Decorator)、状态管理、插件使用、自定义节点以及实战应用。通过实例解析,帮助读者掌握如何设计和执行AI决策流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Unity-Behavior Designer详解


理论

基本概念

行为树是一个包含逻辑节点和行为节点的树结构,每次需要找出一个行为的时候,会从树的根节点出发,遍历各个节点,找出第一个和当前数据相符合的行为。

如下图,就是一个简单的行为树

在这里插入图片描述

当我们要决策一个AI要做什么样的行为的时候, 我们就会自顶向下的,通过一些条件来搜索这颗树,最终确定需要做的行为(叶节点),并且执行它,这就是行为树的基本原理。

Task&Status

有四种不同类型的 task(任务): 包括 action(行为),composite(复合),

conditional(条件),decorator(修饰符)

复合(Composites)

主要有三种:

  • Sequence
    • 顺序执行,会按照从左向右的顺序依次执行子节点
  • Selector
    • 选择执行,根据条件判断,选择一个子节点执行
  • Parallel
    • 平行执行,同时执行所有子节点
修饰(Decorator)

这个类型的节点只能有一个子节点。它的功能是修改子任务的行为。在上面的例子中,我们没有使用 decorator(修饰符),如果你需要类似于打断操作的话会用得到这个 decorator(修饰符)类型!举个例子:一个收集资源的操作,它可能有一个中断节点,这个节点判断是否被攻击,如果被攻击则中断收集资源操作!decorator(修饰符)的另一个应用场合是重复执行子任务 X 次,或

者执行子任务直到完成

条件(Conditinals)

用来判断某些游戏属性是否合适!

行为(Action)

行为很容易理解,即为具体的动作,他们在某种程度上改变游戏的状态和结果

返回状态(status)

有时候一个 task(任务)需要多帧才能完成。例如,大多数动画不会在一帧开始并结束。此外有 conditional(条件)的任务需要一种方法来告诉他们的父任务条件是否正确,以便让父节点确定子节点的执行顺序。这两个问题都可以使用 status(状态)来解决。一个任务有三种不同状态:运行,成功或者失败

行为树插件

插件下载链接:https://download.csdn.net/download/qq_52324195/85400525

基本介绍

在这里插入图片描述

行为树整体的编辑界面

  • Behavior Name,行为树名称
  • Behavior Description,行为树简介
  • Extenral Description,外部行为树
    • 关于这一属性,可以通过右上角的Export将行为树导出,创建一个行为树“Prefab”,他就成为了外部行为树,然后将这个外部行为树拖入该属性,就会将外部行为树应用到当前行为树
  • Group
    • 行为树的分组编号,用来将行为树分组!可以用来方便的查找到特定的行为树
  • Start When Enabled
    • 如果设置为 true,那么当这个行为树组件 enabled 的时候,这个行为树就会被执行
  • Asynchronous Load,异步加载
  • Pause When Disabled
    • 如果设置为 true,那么当这个行为树组件 disabled 的时候,这个行为树就会被暂停
  • Restart When Complete
    • 如果设置为 true,那么当这个行为树组件执行结束的时候,这个行为树就会被重新执行
  • Reset Values On Restart
    • 如果设置为 true,那么当这个行为树组件 reset 的时候,这个行为树就会被重新执行
  • Log Task Changes
    • 当设置为 true 是,这个行为树下只要 task 流程发生变化就会打印一条 log 日志到控制台中

Variable

全局变量,如图,你可以自己创建规定类型的变量

在这里插入图片描述

Inspector

点击任意Task,可以在Inspector界面查看该节点的属性

类似于Unity的编辑界面,出了前三个基本属性外,其他的都是该Task的独有属性,我们可以通过每个属性右边的点按钮,来使用我们的全局变量

在这里插入图片描述

Task

在整个任务树的最高层的节点我们称之为 Task(任务)。这些 task 任务拥有类似于 MonoBehavior 那样的接口用于实现和扩展。

基本属性

Task 任务有三个基础的公共属性:name, comment, instant

如下图,你会发现任意Task都有这三种属性

在这里插入图片描述

前两个很简单,名称与简介。

不过instant可能并不好理解,解释如下:

行为树中,当一个 task 任务返回成功或者失败后,行为树会在同一帧中立刻移动到下一个 task 任务。如果,你没有选择 instant 选项,那么在当前 task 任务执行完毕后,都会停留在当前节点中,直到收到了下一个 tick,才会移动到下一个 task 任务!

疑问:什么是tick?在解释这个之前,我们来讲解另外一个知识:BehaviorManager

其实,你会发现,当运行一个行为树的时候,会在场景中自动创建一个名称为 BehaviorManager的 GameObject,并添 BehaviorManage.cs

最初你看到的是下图这样的:

在这里插入图片描述

第一行:Update Interval,这很好理解,行为树的更新间隔,他有三种选择

在这里插入图片描述

  • Every Frame 每帧更新
  • Specify Seconds 自定义时间更新

在这里插入图片描述

  • Manual 手动更新

如果你选择了,这个选项。你需要通过脚本来手动调用以下函数来执行所有行为树的更新

BehaviorManager.instance.Tick()

它还有另外一种重载:BehaviorManager.instance.Tick(BehaviorTree);,很显然,它传入了一个行为树,那么仅仅就是只对该行为树进行更新,而不是全部。

第二行:Task Execution Type 它可以指定这次更新中行为树的执行次数

默认是No Dullicates,也就是无复制无重复的意思,也就是1次,每次你更新行为树,行为树会执行一次。

如果你指定了5次,那么每次更新就会执行5次

在这里插入图片描述

让我们回到之前的话题,到这里我想你已经明白了什么事Tick,简单来说也就是行为树更新指令

让我们来看看执行顺序流程图:

在这里插入图片描述

API
// 当行为树被启用时,OnAwake被调用一次。可以把它看作一个构造函数
public virtual void OnAwake();
// OnStart在执行之前立即被调用。它用于设置需要在上次运行后重新设置的任何变量
public virtual void OnStart();
// OnUpdate运行实际的任务
public virtual TaskStatus OnUpdate();
// 在执行成功或失败后调用OnEnd。
public virtual void OnEnd();
// 当行为暂停并恢复时,调用OnPause
public virtual void OnPause(bool paused);
// 优先级选择需要知道该任务的运行优先级
public virtual float GetPriority();
// OnBehaviorComplete在行为树完成执行后被调用
public virtual void OnBehaviorComplete();
// 检查器调用OnReset来重置公共属性
public propertiespublic virtual void OnReset();
// 允许从任务中调用OnDrawGizmos
public virtual void OnDrawGizmos();
// 保留对拥有此任务的行为的引用
public Behavior Owner;

父任务 Parent Tasks

behavior tree 行为树中的父任务 task 包括:composite(复合),decorator(修饰符)!

以下是他的可扩展API,虽然 Monobehaviour 没有类似的 API,但是并不难去理解这些功能

//一个父任务可以拥有的子任务的最大数量。通常为1或int。MaxValue
public virtual int MaxChildren();
//布尔值,以确定当前任务是否为并行任务
public virtual bool CanRunParallelChildren();
//当前活动子节点的索引
public virtual int CurrentChildIndex();
//布尔值,以确定当前任务是否可以执行
public virtual bool CanExecute();
//为执行状态应用装饰器,输入参数为被修饰节点的状态
public virtual TaskStatus Decorate(TaskStatus status);
//通知parenttask子任务已被执行,其状态为childStatus
public virtual void OnChildExecuted(TaskStatus childStatus);
//通知父任务,其子任务childIndex已被执行,其状态为childStatus
public virtual void OnChildExecuted(int childIndex, TaskStatus childStatus);
//通知任务子进程已经开始运行
public virtual void OnChildStarted();
//通知并行任务,索引为childIndex的子任务已开始运行
public virtual void OnChildStarted(int childIndex);
//一些父任务需要能够覆盖状态,例如并行任务
public virtual TaskStatus OverrideStatus(TaskStatus status);
//如果中断节点被中断,它将覆盖状态。
public virtual TaskStatus OverrideStatus();
//通知复合任务,条件中止已被触发,子索引应重置
public virtual void OnConditionalAbort(int childIndex);

条件节点的终止

一共有四种中断类型的 abort types: None, Self, Lower Priority, and Both.

在这里插入图片描述

如上图,所有的条件节点都有一个属性:Abort Type,也就是中止类型

  • None
    • 无中止
  • Self
    • 这是一种自包含中断类型。也就是会检测此节点下所有条件判断节点,即便是被执行过的节点,如果判断条件不满足则打断当前执行顺序从新回到判断节点判断,并返回判断结果!
  • Lower Priority
    • 当运行到后续节点时,本节点的判断条件生效了的话则打断当前执行顺序,返回本节点执行!
  • Both
    • 同时包含Self与LowerPriority

如果你是刚刚接触行为树,我相信,到这里你一定还不明白,接下来,我们结合例子进行讲解

行为树应用简单例子

如图:

在这里插入图片描述

这里我们首先说明Selector选择节点,这是系统默认的选择节点,你可以理解为if else,从左向右依次选择,如果能执行就选择该子树执行,如果不能执行则选择下一个子树。注意,我们的用词,子树,也就是说,如果要执行下去,那么左边这个子树的条件节点必须返回Success,否则,那么就会执行下一个子树。

当前的条件节点是两个Int变量的比较,其中一个我们使用了全局变量 A=100,另外一个则赋值初始值10。

其次,左边的Sequence我们赋予了Lower Priority中止

现在,我们执行它看看结果,最初是这样的:

在这里插入图片描述

方框呈现绿色,说明该节点正在运行,X则表示返回Failure或者不能执行,✔表示该状态本身返回Success

当我们修改第二个Int值为1000,使条件节点返回Success,行为树就变成下面这样:

这里有一点需要说明:中止只会发生在节点运行过程中,如果节点已经运行完毕,也就是出现了绿色的勾,此时说明执行完毕,行为树已经结束,中止就无效了。

如果你之前使用的都是状态机,我想你需要转变过来,行为树并非一种状态,它仅仅包含了一系列行为的逻辑模式,一旦它执行完毕,不会向状态机一样执行完就回到Idle,行为树就结束了。如果你想要再次执行它,就需要再次通过脚本来启用他。

当我们的条件节点通过了,此时中止就生效了,他会中止第二个Sequence,然后执行本节点

在这里插入图片描述

此外,你可以通过点击节点左上角的X,来禁用该子树

在这里插入图片描述

自定义Task

自定义行为节点

这是代码

首先,需要引入命名空间:

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

对于行为节点,我们仅仅需要让该类继承Action,然后重写OnUpdate函数即可,你想要实现的功能就写在这个函数中,我这里实现的就是让对象朝向目标移动

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class MyMoveTo : Action
{
    public GameObject enemy;
    public override TaskStatus OnUpdate()
    {
        if (GetComponent<Player>())
        {
            if (Vector3.Distance(transform.position, enemy.transform.position) < 0.1f)
            {
                return TaskStatus.Success;
            }
            else
            {
                transform.position = Vector3.MoveTowards(transform.position,enemy.transform.position,Time.deltaTime);
                return TaskStatus.Running;
            }
        }
        else
        {
            return TaskStatus.Failure;
        }
    }
}
自定义条件节点

同样引入命名空间,然后继承Conditional

判断敌人是否存活,如果否,那么返回Failure

public class MyConditional : Conditional
{
    public GameObject enemy;
    public override TaskStatus OnUpdate()
    {
        if (enemy.GetComponent<EnemyState>().alive)
        {
            return TaskStatus.Success;
        }
        else
        {
            return TaskStatus.Failure;
        }
    }
}
自定义修饰节点

继承Decorator

修饰节点比较特殊,我这里重写了四个函数

第一个函数是CanExecute(),这个函数的返回值表明该节点能否通过

第二个函数是OnChildExecuted(TaskStatus childStatus),输入参数为孩子节点状态,该函数会在孩子节点执行的时候调用

第三个函数为Decorate(TaskStatus status),输入参数仍然是孩子节点状态,该函数的返回值会改变孩子节点的返回值

最后一个函数就没什么可说的了,这里我使用的行为树如下:

在这里插入图片描述

这里我仔细讲解一下,行为树调用过程,首先通过Selector尝试调用Sequence,当他调用到MyBreak的时候,由于我们一开始的CanExecute通过条件为

executionStatus == TaskStatus.Inactive || executionStatus == TaskStatus.Running

也就是未激活或者正在运行时都允许通过,此时,通过MyBreak,走到MyConditional,然后判断MyConditional返回的状态,如果返回Success,此时通过OnChildExecuted检测到孩子节点状态为Success,我们定义的变量executionStatus被赋予Success,这导致CanExecute返回false,此时行为树从这条路中退出,能够继续运行,接下来继续执行MyMoveTo,这里有一点要注意,因为我们已经执行了条件判断,因此才可以执行MyMoveTo,我们之所以要在执行完条件判断后将该修饰节点的CanExecute返回false,是为了能够继续执行下去,如果这个函数一直返回true,这会导致一直执行条件判断,就无法继续执行MyMoveTo了!;如果条件节点返回Failure那么该Sequence执行失败,Selector选择执行第二个Sequence。

这里要注意一点,中止导致的更新只有条件节点才能触发,并且在触发时只会遵循那时刻的所有节点的变量,如果你尝试更改其他节点使其无法执行,这是没有用的。只有条件节点才能触发此类行为

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

public class MyBreak : Decorator
{
    private TaskStatus executionStatus = TaskStatus.Inactive;

    public override bool CanExecute()
    {
        Debug.Log(executionStatus == TaskStatus.Inactive || executionStatus == TaskStatus.Running);
        return executionStatus == TaskStatus.Inactive || executionStatus == TaskStatus.Running;
    }
    public override void OnChildExecuted(TaskStatus childStatus)
    {
        // Update the execution status after a child has finished running.
        executionStatus = childStatus;
    }
    public override TaskStatus Decorate(TaskStatus status)
    {
        if (GetComponent<Player>() && GetComponent<Player>().ready)
        {
            return TaskStatus.Success;
        }
        else
        {
            return TaskStatus.Failure;
        }
    }
    public override void OnEnd()
    {
        executionStatus = TaskStatus.Inactive;
    }
}

使用脚本创建一个行为树

在某些情况下,你可能想要通过脚本在运行时创建一个行为树,而不是直接使用拖拽或者面板操作去创建!例如:如果你已经导

出了一个外部行为树,并想通过脚本创建它的话,可以如下这么做:

behaviorTree.StartWhenEnabled = false;使得行为树不要再一开始的时候执行

后续当你想要调用他的时候,使用behaviorTree.EnableBehavior();来主动执行行为树

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class BT_Test : MonoBehaviour
{
    public bool on;
    public BehaviorTree behaviorTree;
    public void Start()
    {
        behaviorTree = GetComponent<BehaviorTree>();
        behaviorTree.StartWhenEnabled = false;
    }
    public void Update()
    {
        if (on)
        {
            behaviorTree.EnableBehavior();
            on = false;
        }
    }
}

Event事件

为了说明事件,我们使用下面这个行为树

在这里插入图片描述

这里我们使用了一个条件节点:Has Received Event

通过该条件节点的Inspector界面,我们可以看到,它有一个属性和三个变量

  • Event 事件名称
  • 参数

我们来看看他的源码的一个函数

很明显,在行为树启动时,他会自动以给定的名称来注册事件,一共有四种注册事件(0个参数到3个参数)

public override void OnStart()
{
  // Let the behavior tree know that we are interested in receiving the event specified
  if (!registered) {
    Owner.RegisterEvent(eventName.Value, ReceivedEvent);
    Owner.RegisterEvent<object>(eventName.Value, ReceivedEvent);
    Owner.RegisterEvent<object, object>(eventName.Value, ReceivedEvent);
    Owner.RegisterEvent<object, object, object>(eventName.Value, ReceivedEvent);
    registered = true;
  }
}

我们再来看看注册的函数

首先将eventReceived变量赋为true,然后设置对应的参数,即获得参数

private void ReceivedEvent()
{
  eventReceived = true;
}

private void ReceivedEvent(object arg1)
{
  ReceivedEvent();

  if (storedValue1 != null && !storedValue1.IsNone) {
    storedValue1.SetValue(arg1);
  }
}

private void ReceivedEvent(object arg1, object arg2)
{
  ReceivedEvent();

  if (storedValue1 != null && !storedValue1.IsNone) {
    storedValue1.SetValue(arg1);
  }

  if (storedValue2 != null && !storedValue2.IsNone) {
    storedValue2.SetValue(arg2);
  }
}

通过Update函数,我们可以知道,当注册事件被调用时,eventReceived为true,该条件节点就会返回Success

public override TaskStatus OnUpdate()
{
  return eventReceived ? TaskStatus.Success : TaskStatus.Failure;
}

我们通过一个脚本来调用函数,调用对应的事件十分简单,因为我们使用了一个名字来注册事件,想要调用只需要使用behaviorTree.SendEvent()这个API即可调用,也就是发送,调用了它之后,Has Received Event条件节点就会返回Success,因此条件能够通过,你可以把这个函数当作对应名字事件的触发器,触发即可使得该条件节点返回Success。

另外,再次强调只有在行为树运行的时候才有效,如果你的行为树结束了,也就是出现绿色的勾了,说明行为树已经结束,此时再发送就无效了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

public class TaskA : MonoBehaviour
{
    public bool send;
    public bool on;
    public BehaviorTree behaviorTree;
    public void Start()
    {
        behaviorTree = GetComponent<BehaviorTree>();
        behaviorTree.StartWhenEnabled = false;
    }
    public void Update()
    {
        if (on)
        {
            behaviorTree.EnableBehavior();
            on = false;
        }
        if (send)
        {
            behaviorTree.SendEvent<object>("MyEvent",1);
            send = false;
        }
    }
}

行为树运行后,还未收到事件调用信息,此时我们使用Wait防止行为树结束,保持运行

在这里插入图片描述

我们发送信息后,条件节点收到信息后,通过执行

在这里插入图片描述

Task的引用

我们先创建两个行为节点,并在一个节点中创建第二个节点公共变量,并尝试打印信息

public class TaskF : Action
{
    public TaskS referencedTask;
    public override void OnAwake()
    {
        Debug.Log(referencedTask.some);
    }
}
public class TaskS : Action
{
    public float some;
}

我们来看看Inspector界面

在这里插入图片描述

在属性界面,它显示出了一个引用任务的选择按钮,因为对于行为节点我们无法直接赋值,因此采取选择的方式

点击Select,然后点击行为树中对应类型的行为节点,此时就是将选中行为节点赋予给本节点的此变量

在这里插入图片描述

通过点击X,可以取消引用

在这里插入图片描述

在引用后,我们启动行为树就会正常打印,如果不引用就会报出空指针异常。

变量同步器(Variable Synchronizer)

同步全局变量,箭头表示同步方向,下图中就是将A的值同步给B,点按Add添加同步操作

在这里插入图片描述

添加之后,仍然可以改变同步方向,点按箭头按钮即可,启动后才能看到同步效果

在这里插入图片描述

Task可用特性

HelpURL : web 连接

[HelpURL("http://www.opsive.com/assets/BehaviorDesigner/documentation.php?id=27")]
public class Parallel : Composite{
}

TaskIcon :任务的图标

[TaskIcon("Assets/Path/To/{SkinColor}Icon.png")]
public class MyTask : Action{
}

TaskCategory:任务的显示位置(在 Task 任务面板中的显示位置)

[TaskCategory("Common")]
public class Seek : Action{ 
}

[TaskCategory("RTS/Harvester")]
public class HarvestGold : Action{
}

TaskDescription:功能描述的文本内容,显示在编辑器布局区域的左下角

[TaskDescription("The sequence task is similar to an \"and\" operation. ..."]
public class Sequence : Composite{
}

LinkedTask:应用其他的 Task 任务

[LinkedTask]
public TaskGuard[] linkedTaskGuards = **null**;

InheritedField : 继承属性

[InheritedField]
public float moveSpeed;
This extension requires one license per seat Requires Unity 4.6.0 or higher. Behavior trees are used by AAA studios to create a lifelike AI. With Behavior Designer, you can bring the power of behaviour trees to Unity! Behavior Designer is a behaviour tree implementation designed for everyone - programmers, artists, designers. Behavior Designer offers an intuitive visual editor with a powerful API allowing you to easily create new tasks. It also includes hundreds of tasks, PlayMaker integration, and extensive third party integration making it possible to create complex AIs without having to write a single line of code! Behavior Designer was designed from the ground up to be as efficient as possible with zero allocations after initialization. As a result, it runs great on all platforms including mobile. Behavior Designer is dedicated to behavior trees, which means that each update will be a better behavior tree implementation. Features: - An intuitive visual editor - A powerful API - Visual runtime debugger - Variables to communicate between tasks - Conditional Aborts - Built in event system - Unity 5 multiplayer support - Use existing code with reflection tasks - Hundreds of tasks - Evaluate tasks using Utility Theory - Realtime error detection - Binary or JSON serialization - Data-oriented design - Zero runtime allocations after startup - Object drawers (property drawers) - Includes runtime source code - Extensive documentation and videos - Sample projects available online - And more Addon Packs: - Formations Pack - Movement Pack - Tactical Pack Complete Projects: - Deathmatch AI Kit Third Party Integrations: - 2D Toolkit - A* Pathfinding Project (Movement Pack) - Adventure Creator - Anti-Cheat Toolkit - Apex Path (Movement Pack) - Blox - Camera Path Animator - Chronos - Cinema Director - Control Freak - Core GameKit - Curvy - Dialogue System - DOTween - Final IK - Glow Effect - ICode - Inventory Pro - LeanTween - Love/Hate - Master Audio - NGUI - Particl
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值