20 KiB
Behavior Designer Pro — 行为树节点编写完全指南
版本: Behavior Designer Pro (基于 DOTS 混合架构) 适用项目: Cielonos (3D 第三人称动作游戏)
1. 架构总览
Behavior Designer Pro 采用 DOTS 混合架构:行为树的遍历始终由 ECS (Entity Component System) 驱动,但节点的逻辑可以选择 GameObject 工作流或纯 ECS 工作流。
1.1 两种工作流对比
| 维度 | GameObject 工作流 (推荐) | Entity (ECS) 工作流 |
|---|---|---|
| 基类 | Action, Conditional, ActionNode, ConditionalNode, DecoratorNode, CompositeNode |
ECSActionTask<TSystem, TComponent> 等 |
| 编写难度 | 低,类似 MonoBehaviour | 高,需要定义 IBufferElementData, ISystem, Flag |
| 是否可引用对象 | ✅ 可以引用 GameObject, Component |
❌ 只能传值类型 |
| 是否支持 SharedVariable | ✅ | ❌ |
| 是否支持 Burst/Job | ❌ | ✅ |
| 适用场景 | < 50 个 AI Agent | 成千上万个 Agent |
本项目 (Cielonos) 统一使用 GameObject 工作流。
1.2 核心命名空间
using Opsive.BehaviorDesigner.Runtime; // BehaviorTree 组件
using Opsive.BehaviorDesigner.Runtime.Tasks; // TaskStatus, Task 基类
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions; // Action, ActionNode
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals; // Conditional, ConditionalNode
using Opsive.BehaviorDesigner.Runtime.Tasks.Composites; // CompositeNode
using Opsive.BehaviorDesigner.Runtime.Tasks.Decorators; // DecoratorNode
using Opsive.BehaviorDesigner.Runtime.Tasks.Events; // EventNode (OnReceivedEvent等)
using Opsive.GraphDesigner.Runtime; // NodeIcon, Description 等特性
using Opsive.GraphDesigner.Runtime.Variables; // SharedVariable<T>
using Opsive.Shared.Utility; // Category, Description, MemberVisibility
using Opsive.Shared.Events; // EventHandler (事件系统)
2. TaskStatus 枚举
public enum TaskStatus : byte
{
Inactive, // 未激活
Queued, // 下一帧将执行
Running, // 正在执行(持续多帧)
Success, // 执行成功
Failure, // 执行失败
}
核心规则:
- Action 节点: 可以返回
Running(多帧持续),Success,Failure - Conditional 节点: 应当仅返回
Success或Failure,不应返回Running(单帧内完成判定)
3. Task 基类生命周期
Task 是所有 GameObject 工作流节点的终极基类,其生命周期方法如下:
BehaviorTree 初始化
└─ Initialize() [internal, 自动调用]
├─ 缓存 m_GameObject, m_Transform, m_BehaviorTree
├─ 注册物理回调(如果子类启用了对应的 Receive*Callback)
└─ OnAwake() ← 【初始化:缓存组件引用,仅调用一次】
BehaviorTree 启动
└─ OnBehaviorTreeStarted() ← 【树启动时回调】
每次节点被激活
└─ OnStart() ← 【节点开始执行,每次激活都会调用】
└─ OnUpdate() (每帧) ← 【核心逻辑,返回 TaskStatus】
└─ OnEnd() ← 【节点执行结束(Success/Failure 后)】
BehaviorTree 停止/暂停
└─ OnBehaviorTreeStopped(bool paused)
BehaviorTree 销毁
└─ OnDestroy() ← 【清理,取消事件注册】
3.1 关键属性/字段
| 成员 | 类型 | 说明 |
|---|---|---|
m_GameObject / gameObject |
GameObject |
行为树挂载的 GameObject |
m_Transform / transform |
Transform |
行为树的 Transform |
m_BehaviorTree |
BehaviorTree |
行为树组件引用 |
m_RuntimeIndex |
ushort |
节点运行时索引 |
4. 四种节点类型详解
4.1 Action (行为节点)
用途: 改变游戏状态(播放动画、移动、设置变量等)。
两种基类选择:
Action— 可堆叠 (Stacked):多个 Action 可放在同一个 StackedAction 节点中ActionNode— 不可堆叠 (Independent):独立节点
编写模板(最简):
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using Opsive.GraphDesigner.Runtime;
using Opsive.Shared.Utility;
using UnityEngine;
[Description("描述此节点的功能")]
[NodeIcon("Assets/路径/图标.png")]
[Category("Cielonos")]
public class MyAction : Action
{
[Tooltip("参数说明")]
[SerializeField] float m_Speed = 5f;
public override void OnAwake()
{
// 缓存组件 (仅一次)
}
public override void OnStart()
{
// 每次节点激活时的初始化
}
public override TaskStatus OnUpdate()
{
// 核心逻辑
// 返回 Running = 继续执行, Success = 完成, Failure = 失败
return TaskStatus.Success;
}
public override void OnEnd()
{
// 节点结束时的清理
}
public override void Reset()
{
// 编辑器重置默认值
m_Speed = 5f;
}
}
4.2 Conditional (条件节点)
用途: 检查游戏状态(距离、血量、事件等),不改变游戏状态。
两种基类选择:
Conditional— 可堆叠 + 支持 Conditional Abort 重评估ConditionalNode— 不可堆叠 (Independent)
关键方法: OnReevaluateUpdate() — 在 Conditional Abort 重评估阶段调用,默认实现会调用 OnUpdate()。
编写模板:
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
using Opsive.Shared.Utility;
using UnityEngine;
[Category("Cielonos")]
[Description("检查某个条件")]
public class MyConditional : Conditional
{
public override void OnAwake()
{
// 缓存组件
}
public override TaskStatus OnUpdate()
{
// 检查条件:Success = 条件满足, Failure = 不满足
return someCondition ? TaskStatus.Success : TaskStatus.Failure;
}
// 可选:Conditional Abort 重评估时走不同逻辑
public override TaskStatus OnReevaluateUpdate()
{
return OnUpdate(); // 默认行为
}
}
4.3 Composite (组合节点)
常用内置 Composite:
| 节点 | 行为 |
|---|---|
Sequence |
顺序执行子节点,全部 Success 才返回 Success,任一 Failure 立即返回 Failure(AND 逻辑) |
Selector |
顺序执行子节点,任一 Success 立即返回 Success,全部 Failure 才返回 Failure(OR 逻辑) |
Parallel |
并行执行所有子节点 |
ParallelSelector |
并行执行,任一 Success 立即停止 |
RandomSelector |
随机顺序的 Selector |
RandomSequence |
随机顺序的 Sequence |
PrioritySelector |
按优先级排序的 Selector |
UtilitySelector |
效用评分选择器 |
自定义 Composite: 继承 CompositeNode,不可堆叠。
4.4 Decorator (装饰节点)
用途: 修改子节点的返回状态或控制子节点的执行方式。只能有一个子节点。
常用内置 Decorator:
| 节点 | 行为 |
|---|---|
Inverter |
反转子节点结果 (Success↔Failure) |
Repeater |
重复执行子节点指定次数或无限次 |
ReturnSuccess |
无论子节点结果如何,始终返回 Success |
ReturnFailure |
无论子节点结果如何,始终返回 Failure |
UntilSuccess |
重复执行直到子节点返回 Success |
UntilFailure |
重复执行直到子节点返回 Failure |
Cooldown |
子节点执行后进入冷却 |
ConditionalEvaluator |
条件评估装饰器 |
Iterator |
遍历列表 |
自定义 Decorator: 继承 DecoratorNode,不可堆叠。
5. Conditional Aborts (条件打断)
核心机制: 在行为树运行时,Conditional 节点可以被持续重评估。当条件状态变化时(Success→Failure 或 Failure→Success),触发打断。
5.1 四种打断类型 (ConditionalAbortType)
public enum ConditionalAbortType : byte
{
None, // 不打断
LowerPriority, // 打断右侧(低优先级)分支
Self, // 打断当前分支内的任务
Both // LowerPriority + Self
}
设置在 Composite 节点(Sequence/Selector)上。
5.2 经典设计模式
Selector (根)
├── Sequence [AbortType=LowerPriority] ← 最高优先级
│ ├── HasTakenDamage (Conditional)
│ └── ReactToDamage (Action)
├── Sequence [AbortType=LowerPriority]
│ ├── CanSeeObject (Conditional)
│ └── ChaseAndAttack (子树)
├── Sequence [AbortType=LowerPriority]
│ ├── WithinDistance (Conditional)
│ └── Investigate (Action)
└── Patrol (Action) ← 最低优先级(兜底行为)
5.3 OnReevaluateUpdate 注意事项
- 默认
Conditional.OnReevaluateUpdate()调用OnUpdate() - 如果需要在重评估时走特殊逻辑(如避免消费事件),应覆写此方法
- 项目中
HasReceivedContextEvent就是一个好例子:OnReevaluateUpdate()只检查不消费
6. SharedVariable (共享变量)
SharedVariable 是行为树节点间共享数据的机制。
6.1 基本用法
using Opsive.GraphDesigner.Runtime.Variables;
// 声明
[SerializeField] SharedVariable<float> m_Speed = 5f; // 带默认值
[SerializeField] SharedVariable<GameObject> m_Target; // 引用类型
[SerializeField] SharedVariable<Vector3> m_Position;
[SerializeField] SharedVariable<string> m_EventName;
[SerializeField] SharedVariable<bool> m_IsEnabled;
// 读取
float speed = m_Speed.Value;
GameObject target = m_Target.Value;
// 设置
m_Speed.Value = 10f;
// 无类型 SharedVariable (用于泛型存储)
[RequireShared] [SerializeField] SharedVariable m_StoredValue1;
// 设置值
m_StoredValue1.SetValue(someObject);
// 检查是否已共享
if (m_StoredValue1 != null && m_StoredValue1.IsShared) { ... }
// 值变化回调
m_Speed.OnValueChange += OnSpeedChanged;
6.2 通过 BehaviorTree 组件访问
// 获取变量
var variable = m_BehaviorTree.GetVariable("VariableName");
// 设置变量值
m_BehaviorTree.SetVariableValue("VariableName", newValue);
7. 事件系统
7.1 Opsive.Shared.Events.EventHandler
BD Pro 内置事件系统,用于节点间和外部脚本通信。
// 发送事件 (到特定 BehaviorTree)
EventHandler.ExecuteEvent(behaviorTree, "EventName");
EventHandler.ExecuteEvent<object>(behaviorTree, "EventName", arg1);
EventHandler.ExecuteEvent<object, object>(behaviorTree, "EventName", arg1, arg2);
// 注册事件
EventHandler.RegisterEvent(behaviorTree, "EventName", OnEventReceived);
EventHandler.RegisterEvent<object>(behaviorTree, "EventName", OnEventReceived);
// 注销事件
EventHandler.UnregisterEvent(behaviorTree, "EventName", OnEventReceived);
// 全局事件
EventHandler.RegisterEvent("GlobalEventName", OnGlobalEvent);
EventHandler.ExecuteEvent("GlobalEventName");
7.2 内置事件节点
HasReceivedEvent(Conditional): 当收到指定事件时返回 Success(支持 Conditional Abort)OnReceivedEvent(EventNode): 收到事件时启动一个独立分支(类似中断)SendEvent(Action): 向自身或其他 BehaviorTree 发送事件
7.3 自定义事件系统 (Cielonos)
本项目使用 BehaviorSubcontroller.EventMemory 实现了带有效窗口期的上下文事件系统:
// HasReceivedContextEvent 的核心逻辑:
// 1. 通过 behaviorSc.EventMemory 读取事件
// 2. 用 validWindow (秒) 判断事件是否在有效期内
// 3. OnUpdate 中消费事件 (Remove)
// 4. OnReevaluateUpdate 中只检查不消费
8. 常用特性 (Attributes)
// 节点分类(在创建节点菜单中的路径)
[Category("Cielonos")]
[Category("Cielonos/Events")]
[Category("Movement Pack")]
// 节点描述(选中节点后右下角显示)
[Description("此节点的功能描述")]
// 节点图标(自定义 PNG 或 GUID)
[NodeIcon("Assets/Sprites/Icon/Play.png")]
[NodeIcon("GUID_Light", "GUID_Dark")] // 明暗两套图标
// Tooltip(Inspector 中悬浮提示)
[Tooltip("参数说明")]
// 隐藏在过滤窗口中
[HideInFilterWindow]
// 强制要求 SharedVariable 必须为 Shared 模式
[RequireShared]
// 隐藏字段不显示在 Inspector
[HideInInspector]
// 允许同类型多实例
[AllowMultipleTypes]
9. 本项目自定义基类
9.1 AutomataActionBase (Action 基类)
[Category("Cielonos")]
public abstract class AutomataActionBase : Action
{
protected Automata self;
protected BehaviorSubcontroller behaviorSc;
protected BehaviorTree mainBehaviorTree;
public override void OnAwake()
{
self = gameObject.GetComponent<Automata>();
behaviorSc = self.behaviorSc;
mainBehaviorTree = behaviorSc.mainBehaviorTree;
}
}
9.2 AutomataConditionalBase (Conditional 基类)
[Category("Cielonos")]
public abstract class AutomataConditionalBase : Conditional
{
protected Automata self;
protected BehaviorSubcontroller behaviorSc;
protected BehaviorTree mainBehaviorTree;
public override void OnAwake()
{
self = gameObject.GetComponent<Automata>();
behaviorSc = self.behaviorSc;
mainBehaviorTree = behaviorSc.mainBehaviorTree;
}
}
使用这些基类后,子类可直接访问 self, behaviorSc, mainBehaviorTree。
10. 本项目已有自定义节点清单
10.1 Action 节点
| 节点名 | 基类 | 功能 |
|---|---|---|
PlayFuncAnim |
AutomataActionBase |
播放功能动画,检测打断,支持循环动画/转向目标/根吸附 |
SetBreakthroughResistance |
AutomataActionBase |
设置可打断状态(白/橙/红光) |
SetRootMotionMultipliers |
Action |
设置根运动各方向的倍率,支持自动计算 |
GetPlayer |
AutomataActionBase |
获取玩家对象到 SharedVariable |
GetGlobalVariable |
AutomataActionBase |
获取全局属性值 |
ResetTimer |
AutomataActionBase |
重置计时器 |
10.2 Conditional 节点
| 节点名 | 基类 | 功能 |
|---|---|---|
HasReceivedContextEvent |
AutomataConditionalBase |
检查上下文事件(带有效窗口期),完美支持 Conditional Abort |
CheckAttribute |
Conditional |
检查数值属性(大于/小于/等于) |
CheckStatus |
Conditional |
检查状态效果(单个/任意/全部) |
CheckTimer |
AutomataConditionalBase |
检查计时器是否完成 |
CheckString |
Conditional |
字符串检查(完全匹配/包含/正则) |
IsPlayingFuncAnim |
AutomataConditionalBase |
检查是否正在播放指定功能动画 |
10.3 自定义 Editor
| 编辑器类 | 目标 | 功能 |
|---|---|---|
SetBreakthroughResistanceControl |
SetBreakthroughResistance |
条件显示高级设置列表 |
11. 自定义 Inspector 编辑器
在 BD Pro 中自定义节点的 Inspector 面板:
using UnityEngine.UIElements;
using Opsive.Shared.Editor.UIElements;
using Opsive.Shared.Editor.UIElements.Controls;
using Opsive.Shared.Editor.UIElements.Controls.Types;
[ControlType(typeof(目标Task类型))]
public class MyTaskControl : TypeControlBase
{
public override bool UseLabel => false; // 不使用外层 Label
protected override VisualElement GetControl(TypeControlInput input)
{
var task = input.Value as MyTask;
var container = new VisualElement();
// 使用 FieldInspectorView.AddField 自动生成标准控件
FieldInspectorView.AddField(
input.UnityObject, // UnityObject 引用
task, // 目标实例
"fieldName", // 字段名
container, // 父容器
(object newValue) => // 值变更回调
{
task.fieldName = (FieldType)newValue;
input.OnChangeEvent(task); // 通知 BD 数据已修改
}
);
return container;
}
}
12. Movement Pack Add-On
用于 NavMesh 导航的 Action 节点,所有移动类节点继承自 MovementBase。
12.1 MovementBase 架构
public abstract class MovementBase : Action
{
protected Pathfinder m_Pathfinder; // 寻路器抽象(默认 NavMeshAgentPathfinder)
// 核心方法
protected virtual bool SetDestination(Vector3 destination);
protected bool HasPath();
public bool HasArrived();
protected bool SamplePosition(ref Vector3 position);
// 生命周期
public override void OnAwake() → m_Pathfinder.Initialize(gameObject);
public override void OnStart() → m_Pathfinder.OnStart();
public override void OnEnd() → m_Pathfinder.Stop(); + OnEnd();
}
12.2 常用移动节点
| 节点 | 功能 |
|---|---|
Seek |
移向目标 GameObject 或 Position,到达返回 Success |
Flee |
远离目标 |
Pursue |
追逐(预测目标位置) |
Patrol |
巡逻路径 |
Wander |
随机漫游 |
Follow |
跟随目标 |
Evade |
逃避 |
Cover |
寻找掩体 |
RotateTowards |
转向目标 |
MoveTowards |
直线移向目标 |
13. Senses Pack Add-On
用于感知系统的节点。
13.1 感知传感器类型
Visibility— 视觉检测Distance— 距离检测Sound— 声音检测Luminance— 光照检测Temperature— 温度检测Surface— 地面类型检测Tracer— 痕迹追踪
13.2 常用感知节点
CanDetectObject— 检测是否能感知到物体WithinRange— 检测是否在范围内GetSensorAmount— 获取传感器数值
14. 最佳实践与性能避坑
14.1 OnAwake vs OnStart
| 方法 | 调用频率 | 用途 |
|---|---|---|
OnAwake() |
行为树初始化时仅一次 | 缓存组件引用(GetComponent) |
OnStart() |
节点每次激活时 | 初始化运行时状态 |
严禁在 OnUpdate() 中调用 GetComponent!
14.2 Conditional Abort 安全编写
public class SafeConditional : AutomataConditionalBase
{
public override TaskStatus OnUpdate()
{
// 正常评估 + 消费事件/状态
if (CheckAndConsume()) return TaskStatus.Success;
return TaskStatus.Failure;
}
public override TaskStatus OnReevaluateUpdate()
{
// 仅检查,不消费!
if (CheckOnly()) return TaskStatus.Success;
return TaskStatus.Failure;
}
}
14.3 节点命名规范
- Action 节点: 动词开头 (
PlayFuncAnim,SetBreakthroughResistance,GetPlayer) - Conditional 节点:
Is*,Has*,Check*,Can*(IsPlayingFuncAnim,HasReceivedContextEvent,CheckAttribute)
14.4 物理回调
如需接收物理回调,必须覆写对应属性:
protected override bool ReceiveTriggerEnterCallback => true;
protected override void OnTriggerEnter(Collider other)
{
// 处理触发器进入
}
14.5 协程支持
Task 支持在节点内启动协程:
protected void StartCoroutine(string methodName);
protected Coroutine StartCoroutine(IEnumerator routine);
protected void StopCoroutine(string methodName);
protected void StopAllCoroutines();
15. 新节点编写 Checklist
- 选择正确的基类 (
Action/Conditional/AutomataActionBase/AutomataConditionalBase) - 添加
[Category("Cielonos")]和[Description("...")] - 在
OnAwake()中缓存所有组件引用 - 在
OnStart()中初始化每次运行的状态 OnUpdate()返回正确的TaskStatus- 如果是 Conditional 且需要支持 Abort,考虑覆写
OnReevaluateUpdate() - 使用
SharedVariable<T>实现节点间数据共享 - 可选:实现
Reset()提供编辑器默认值 - 可选:实现
OnEnd()进行清理 - 确保引用了正确的 Assembly Definition (
Opsive.BehaviorDesigner.Runtime)