◆ 持续开发中 · 当前为非稳定版本,如需抢先体验请通过 GitHub Issues 联系作者

行为树 BT

可视化编辑行为树 → 导出为不可变 Blob → 按 TreeId 注册 → 由 ECS 系统对实体逐帧驱动。

编辑器能跑 ≠ 真机能跑:真机必须导出 Blob 并在引导期读回。

全链路

BTTreeAsset(画布编辑)
    ↓  BTBlobBuilder.Build(编辑器:进 Play 由 BTAutoRegisterOnPlay 自动全量注册)
BTRootBlob(纯数据树,Blob)
    ↓  BTManager.Register(treeId, blobRef)
注册完成
    ↓  BTInterpreterSystem(ISystem)对挂 RunningBT 的实体
逐帧 Tick → BTInterpreter 指针遍历

─── 打包(真机)另有一条路 ───
BTTreeAsset
    ↓  BTBlobExporter.ExportAll()(菜单 HYC Framework/BT/Export Blob (For Build))
StreamingAssets/BTBlob/<treeId>.blob + manifest.txt
    ↓  运行时 BTBlobLoader.LoadAll()(TryRead → BTManager.Register)
注册完成 → 同上逐帧 Tick

两种驱动方式

方式入口适用
ECS(推荐)BTInterpreterSystem(ISystem),自动对挂 RunningBT{TreeId} 的实体 TickDOTS / ECS 项目
非 ECSBTManager + BTEventBus + BTNodeRuntimeRegistry 手动驱动纯 MonoBehaviour 场景

两者共用同一份 BTRootBlob 与同一个 BTInterpreter,差别只在「谁调用 Tick、状态存在哪」。

数据模型:BTTreeAsset

位于 Editor/BT/BTTreeAsset.cs(编辑器侧资产):

字段说明
TreeId (long)树唯一 ID,运行期按它注册与查找
Kind (BTTreeKind)Skill / AI / Other,用于节点语义分区
Nodes (List<BTNodeData>)节点:NodeId / Type / Position / Note + 三个参数池
Connections连线:SourceNodeId → TargetNodeId + PortIndex(决定子节点顺序)
Blackboard黑板定义:Key / ValueType / DefaultValue

节点参数用三个定长池 FloatParams / LongParams / StringParams,每个节点类型占用的参数槽位有明确约定(见枚举注释)。

创建行为树资产(BTAssetFactory)

BTTreeAsset 的唯一程序化创建入口是 BTAssetFactory,与 ConfigAssetFactory 平行:

// 自动在 Assets/BTTrees 下建资产并派发唯一 TreeId
BTAssetFactory.Create("EnemyAI");
BTAssetFactory.CreateMany("Mob", 10);
⚠
禁止 ScriptableObject.CreateInstance<BTTreeAsset>() + 手填 TreeId:原 DateTime.Now.Ticks % 100000 同毫秒会撞车、[CreateAssetMenu] 默认 TreeId=0 与「(无)」哨兵撞;子树引用按 TreeId 选树,撞车会选错树。

TreeId 由工厂以本机指纹(MAC)派生派发唯一值:高 48 位取机器 MAC、低 15 位为本机段内序号,跨机天然隔离、无需服务器也不会撞(行为树是独立 long 空间,不接 ConfigIdService)。节点 ID(BTNodeData.NodeId)同样由工厂统管:BTAssetFactory.NextNodeId(tree) 派发树内最小未占用正整数,外部不得手填。

节点类型全景

分类节点
入口 / 出口Root(唯一入口)· End(显式终止,返回 Success)
组合(有子节点)Sequence · Selector · Parallel · RandomSelector / RandomSequence · FlipSelector · UtilitySelector · ProbabilitySelector · StepSequencer · Switch · BinarySelector
装饰(包 1~2 子节点)Invert · Repeat · UntilSuccess · UntilFail · AlwaysSuccess · AlwaysFail · CooldownGate · Conditional · TimeLimit · Timeout · WaitUntil · Guard · Interruptor · Filter · Iterator · Monitor · Optional · Remapper
条件 / 动作(叶子)条件:CheckDistance · CheckBlackboard · CheckEvent;动作:Wait · NoOp · SendEvent
特殊 / 扩展SubTree(执行另一棵树,Long[0] = 目标树 ID)· GameCustom = 128(游戏层注册)

参数槽位约定(节选)

节点参数
Sequence / SelectorLong[0] bit0 = dynamic(每帧从 0 重估高优先级子节点)
ParallelLong[0] = policy;Long[1] = dynamic
RepeatLong[0] = 次数(≤0 无限)
SwitchLong[0] = case 黑板键;Long[2] = dynamic;Long[3] = 越界模式
SubTreeLong[0] = 目标树 ID
SendEvent / CheckEventLong[0] = 事件 key 哈希;Long[1] = 范围(0 本实体 / 1 全局)

完整的槽位约定以 BTNodeType.cs 的注释为准 —— 改节点参数前先读那里的注释。

编辑器入口

菜单用途
HYC Framework / BT / BT Editor打开行为树编辑器画布
HYC Framework / BT / Export Blob (For Build)把全部树导出到 Assets/StreamingAssets/BTBlob(真机必做)
✕
出包前必须做两件事:① 跑 HYC Framework/BT/Export Blob (For Build) 导出文件;② 在引导期调用 BTBlobLoader.LoadAll() 注册。只改了树却忘了导出,真机会静默跑旧版本。

常见坑

  • Root 必须唯一且必须连线,否则 BTBlobBuilder.Build 直接失败并打 LogError。
  • BTNodeType 是 byte 枚举,新增类型必须追加在末尾,插在中间会让旧资产序列化错位。
  • 节点跨帧状态靠 BTNodeRuntimeState 持久化,不是存在节点上。
  • GameCustom 节点走托管路径(委托 + 反射),不参与 Burst 编译;纯内建节点的树才是指针遍历、无托管分配。
  • 移动 / 改名 BTTreeAsset 不影响引用(运行期按 TreeId 查找);但改 TreeId 会断引用。
  • 编辑器靠 BTAutoRegisterOnPlay 自动注册,真机没有这回事,必须导出 + LoadAll()。