Files
Cielonos/.agents/skills/unity-technician/knowledge/BehaviorDesignerPro.md
2026-04-18 13:57:19 -04:00

20 KiB
Raw Blame History

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 节点: 应当仅返回 SuccessFailure,不应返回 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 立即返回 FailureAND 逻辑)
Selector 顺序执行子节点,任一 Success 立即返回 Success全部 Failure 才返回 FailureOR 逻辑)
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")]  // 明暗两套图标

// TooltipInspector 中悬浮提示)
[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)