示例 · 行为树 BT
一棵有真实行为数据的敌人 AI 树(不是占位 NoOp):Root → Selector[ Sequence( IsTargetInRange → Attack ), Patrol ],三个叶子是 GameCustom 节点,进 Play 后控制台真的逐帧打印执行日志。
文件:HYCSimpleBTDemoSystem.cs(运行时)/ HYCSimpleEnemyBTNodes.cs(自定义节点)/ Editor/HYCSimpleBTTreeBuilder.cs(编辑器建树+导出)
心智模型
- 编辑器:在
BTTreeAsset里画节点 / 连线,或用代码EnsureRootNode()程序化建树。 - 导出:
BTBlobExporter.ExportAll()(菜单HYC Framework/BT/Export Blob (For Build))→Assets/StreamingAssets/BTBlob/*.blob+manifest.txt。 - 运行期:
BTBlobLoader.LoadAll()把 blob 读回并BTManager.Register(treeId, blob)。 - 驱动:给实体挂
RunningBT { TreeId },框架自带BTInterpreterSystem : ISystem每帧 Tick,结果写回BTLastResult。 - 游戏层自定义行为:
GameCustom节点用LongParams[0]当子类型 subType,BTNodeRuntimeRegistry运行期反射扫描程序集、把BTCustomNode子类按 (TreeKind, subType) 自动注册并分发执行。
示例树结构
Root
└─ Selector(EnemyAI) 优先战斗,否则巡逻
├─ Sequence(Combat)
│ ├─ GameCustom(1) IsTargetInRange (条件)
│ └─ GameCustom(2) Attack (动作)
└─ GameCustom(3) Patrol (动作, 持续 Running)
跑菜单 HYC Framework/Samples/创建全部示例资产(或单独 BT/创建示例树并导出)会程序化造这棵树并直接导出 Blob。
运行期加载 + 挂组件
BTBlobLoader.LoadAll(); // 读 StreamingAssets/BTBlob/*.blob 注册
entityManager.AddComponentData(e, new RunningBT { TreeId = 900001, RunState = default, TickInterval = 0f });
// BTInterpreterSystem 已 RequireForUpdate<RunningBT>(),会自动驱动
编辑器自动注册 vs 真机导出 Blob(最关键)
✕
编辑器能跑 ≠ 真机能跑。编辑器 Play 时
⚠️ 改完树一定要重新导出,否则真机跑的是旧版本(编辑器走自动注册,看不出来)。
BTAutoRegisterOnPlay 会自动把工程内 BTTreeAsset 注册进 BTManager,所以不导出也能跑。但 BTTreeAsset / BTBlobBuilder 在 Editor 程序集,打包后不存在,BTAutoRegisterOnPlay 也不执行 ⇒ 真机一棵树都拿不到。必须走「导出 Blob → BTBlobLoader.LoadAll() 读回」。⚠️ 改完树一定要重新导出,否则真机跑的是旧版本(编辑器走自动注册,看不出来)。
给 AI 的雷区
BTTreeAsset/BTBlobBuilder是 Editor 程序集:引用它们(建树/导出)的代码必须放 Editor 汇编或#if UNITY_EDITOR。运行期只用BTBlobLoader/BTManager/RunningBT/BTInterpreterSystem(均 Runtime)。- 连接即 children:
BTConnectionData.PortIndex是源节点输出端口序号,导出时据此构造 children 顺序;漏连或 PortIndex 重复会导出失败。 RunningBT.RunState是BTRunState(跨帧状态),用default即可;TickInterval=0每帧,>0按秒节流。BTManager.Register重复覆盖:同 TreeId 再次注册会释放旧 blob;编辑器自动注册 + 运行期 LoadAll 都注册同 Id,无害。BTBlobLoader已跨端,游戏层零平台代码:内部按StreamingAssets.NeedsPreload分流,你只管照常LoadAll()。唯一硬性要求:引导入口用await FrameworkBootstrap.InitializeAsync("World"),否则缓存是空的,加载会全部 miss。- GameCustom 节点 必须放在运行时程序集(不能只在 Editor),否则反射扫不到、节点变空操作。
- ⚠️ 示例树 Id 必须避开业务常用 Id:导出会写
StreamingAssets/BTBlob/<treeId>.blob,用1001这类 Id 极易覆盖宿主自己的树。本例固定900001。
验证清单
- 跑菜单后
Assets/HYCSampleTrees/HYCSimpleDemoTree.asset+StreamingAssets/BTBlob/900001.blob+manifest.txt出现。 - 进 Play,日志显示「行为树加载:成功 1 棵」「已为实体挂载 RunningBT」。
- 控制台持续打印
[HYCSimple] BT条件[目标在范围?] = 是/否、BT动作[攻击]…、BT动作[巡逻]…(说明 GameCustom 叶子被真正执行)。 - 改动树节点后重新导出,确认真机链路拿到新版。