Files
zeling_v2/Docs/Guides/08_BehaviorTree_Authoring_Guide.md
2026-06-08 16:05:00 +08:00

12 KiB
Raw Blame History

行为树创建指南 — 敌人 / BossBehavior Designer Pro

面向:在已用脚手架搭好的敌人/Boss Prefab 上挂载并搭建行为树BT跑通「巡逻 → 感知 → 技能」闭环。 配套《小怪与Boss实现计划-01》每个敌人的目标 BT 结构)、02_Enemy_Boss_Setup_Guide


0. 技术栈与前提

说明
行为树框架 Opsive Behavior Designer Procom.opsive.behaviordesigner,运行时组件 Opsive.BehaviorDesigner.Runtime.BehaviorTree
编辑器入口 Tools/Opsive/Behavior Designer;或选中带 BehaviorTree 组件的对象,在 Inspector 点 Open
自定义任务 Assets/_Game/Scripts/Enemies/AI/BD_*.cs,分类前缀 BaseGames/Enemy/*
编译宏 全部 BD_ 任务用 #if GRAPH_DESIGNER 门控。已在 ProjectSettings → Standalone 定义 GRAPH_DESIGNER。换其它构建目标需自行补该宏,否则任务不可见。

前提:敌人/Boss Prefab 已由 SceneObjectPlacerTool 脚手架生成,已具备 EnemyBase/EnemyMovement/PhysicsPerceptionSystem/HurtBox/HitBox/Abilities/* 与绑定好的 SO。BT 只负责决策与调用,不负责动画、不负责具体攻击实现。


1. 三条铁律(搭树前必读)

  1. 动画所有权:所有技能/触发型动画由对应 EnemyAbilityBase 子类在 ExecuteCoroutine()_animancer.Play() 播放。BT 禁止用 BD_PlayAnimation 触发技能动画。Idle/Walk/Run/Dead 由框架随 SetAiPhase / Die() 自动播放。
  2. 能力按 abilityId 调用BD_UseAbility / BD_CanUseAbility / BD_IsAbilityRunning 通过 abilityId或 SO 引用)查找挂在 Abilities/ 子节点上的能力组件。abilityId 必须全局唯一。
  3. 墙/崖不走感知BD_PatrolEnemyMovement.IsWallAhead/IsLedgeAhead 内置射线翻转,无需感知槽。感知槽只用于「发现玩家/攻击距离」等(见 §3

2. 创建一棵行为树(通用步骤)

  1. 挂组件:选中敌人 Prefab 根对象 → Add ComponentBehavior TreeOpsive
  2. 打开编辑器Inspector 里点 Open(或 Tools/Opsive/Behavior Designer)。
  3. 保存形式
    • 内联(推荐单怪专用树):直接在该 Prefab 的 BehaviorTree 组件上编辑,随 Prefab 保存。
    • 共享图(多怪复用):在编辑器中保存为独立 Behavior Tree 资产,再在组件上引用。约定路径 Assets/_Game/Data/AI/BT_{敌人}.asset,命名 BT_*
  4. 搭树:根用 Selector(见 §4 各结构),从任务面板(分类 BaseGames/Enemy/*)拖入 BD_ 任务,连边、填字段。
  5. 填字段BD_IsAiPhase 选枚举;BD_UseAbility/BD_CanUseAbility 填 abilityId 或拖 ABL SOBD_IsSensorDetecting 填槽名(见 §3
  6. 保存 Prefab / 图资产。进入 Play 验证§6

⚠️ 运行前确认:该 Prefab 的 EnemyBase._statsSO/_animConfig、各能力 _config、感知 _slots 均已绑定脚手架已做。BT 只是调度层。


3. 感知槽位(SensorSlotNames

BD_IsSensorDetecting.m_SlotName 必须等于 PhysicsPerceptionSystem 上配置的槽名,常量定义于 Assets/_Game/Scripts/Enemies/Perception/SensorSlotNames.cs

常量 字符串 类型 典型用途
Aggro aggro RangeCircle 主警戒/追击范围
Alert alert RangeCircle 警觉(比 aggro 小)
AttackMelee attack_melee RangeCircle 近战触发距离
AttackRange attack_range RangeCircle/BoxCast 远程/区域触发E002 用正下方 BoxCast
Patrol patrol RangeCircle 巡逻范围限制
LOS los BatchLOS 无遮挡视线
Sight sight Sight 视锥 + 遮挡("看见玩家"E004/E006/嘲风用)

m_AnyTarget=false 时对 EnemyBase.PlayerTransform 判定;true 时用 HasAnyDetection(任意目标)。


4. 各敌人/Boss 的目标树结构

节点缩进表示父子;Selector 选择器(命中即返回),Sequence 顺序(全成功才成功)。 abilityId 已随脚手架生成(见 §5 速查)。BD_CanUseAbilitym_CheckRange=true 时按 ABL SO 的 preferredMin/MaxRange 校验距离。

E001 草蛭(伪装→激活追击→失感知收招→巡逻)

Selector
├── Sequence [死亡] : BD_IsStateMatch(Dead) → BD_StopMovement
├── Sequence [激活]
│   ├── Selector [可激活]: BD_IsAiPhase(Idle) | BD_IsAiPhase(Patrol)
│   ├── BD_IsSensorDetecting("aggro")
│   ├── BD_UseAbility("e001_alert")        // PlayClipAbility播激活动画
│   └── BD_SetAiPhase(Chase)
├── Sequence [追击]
│   ├── BD_IsAiPhase(Chase)
│   └── BD_UseAbility("e001_chase")        // ContactChaseAbility循环追击失感知内部 SetAiPhase(Patrol)
└── Selector [巡逻]                          // ⚠ 必须 Selector
    ├── Sequence [待机]: BD_IsAiPhase(Idle) → BD_WaitRandom(min,max)
    └── Sequence [巡逻]: BD_IsAiPhase(Patrol) → BD_Patrol

E002 簧蛭(固定陷阱:下方检测→钻出三段+脆弱窗口)

Selector
├── Sequence [死亡]: BD_IsStateMatch(Dead) → BD_Wait(999)
└── Sequence
    ├── BD_IsSensorDetecting("attack_range")   // 正下方 BoxCast
    ├── BD_CanUseAbility("e002_ceiling_strike")
    └── BD_UseAbility("e002_ceiling_strike")   // CeilingHangStrikeAbility出击→脆弱悬挂→收回

E003 幼蛭(天花板落下→感知追击→兜底巡逻)

Selector
├── Sequence [死亡]: BD_IsStateMatch(Dead) → BD_StopMovement
├── Sequence [下落一次]: BD_IsAiPhase(Idle) → BD_UseAbility("e003_fall")  // AnimatedCeilingDropAbility 落地后内部 SetAiPhase(Patrol)
├── Sequence [追击]: BD_IsSensorDetecting("aggro") → BD_ChasePlayer
└── BD_Patrol

预置于天花板的 E003 由场景战斗触发器调用 E003_YouZhi.ActivateFromCeiling();由 E005 死亡生成的走 OnSpawn(),均自动触发 e003_fall

E004 蛭母出场→战斗循环Flip/撕咬/头槌/酸液/靠近)

Selector
├── Sequence [死亡]: BD_IsStateMatch(Dead) → BD_StopMovement
├── Sequence [出场一次]: BD_IsAiPhase(Idle) → BD_UseAbility("e004_appear")   // AppearAbility 内部 SetAiPhase(Combat)
├── Sequence [战斗]
│   ├── BD_IsAiPhase(Combat)
│   └── Selector
│       ├── Sequence [Flip]:   BD_CanUseAbility("e004_flip")  → BD_UseAbility("e004_flip")   // CanUse 内含"无其他技能+玩家在背后"
│       ├── Sequence [撕咬]:   BD_CanUseAbility("e004_bite", CheckRange) → BD_UseAbility("e004_bite")
│       ├── Sequence [头槌]:   BD_CanUseAbility("e004_headslam", CheckRange) → BD_UseAbility("e004_headslam")
│       ├── Sequence [酸液]:   BD_CanUseAbility("e004_acid") → BD_UseAbility("e004_acid")
│       └── BD_MoveToPlayer
└── BD_MoveToPlayer

E005 肥蛭(近撕咬+后摇脆弱 / 远酸液 / 追击;死亡生成 E003

Selector
├── Sequence [死亡]: BD_IsStateMatch(Dead) → BD_StopMovement   // 死亡生成 E003 由 E005_FeiZhi.SpawnProjectile(AnimationEvent) 处理,不走 BT
├── Sequence [撕咬]: BD_CanUseAbility("e005_bite", CheckRange) → BD_UseAbility("e005_bite")
├── Sequence [酸液]: BD_CanUseAbility("e005_acid") → BD_UseAbility("e005_acid")
└── BD_ChasePlayer

E006 讙(视锥感知→跳跃爪击 / 巡逻)

Selector
├── Sequence [死亡]: BD_IsStateMatch(Dead) → BD_StopMovement
├── Sequence [跳跃攻击]
│   ├── BD_IsSensorDetecting("sight")        // 正面视锥
│   ├── BD_CanUseAbility("e006_leap")
│   └── BD_UseAbility("e006_leap")           // LeapAttackAbilityRigidbody 冲量
└── BD_Patrol

嘲风 Boss单棵树Phase2 子树由 HP 守门,过渡节点内嵌)

Selector [嘲风]
├── Sequence [Phase 2 (HP<50%)]
│   ├── BD_IsHPBelow(0.5)
│   ├── BD_BossPhaseTransition(targetPhase=1, invincibleDuration≈2.0)   // 首次:Running→Success之后守护即时 Success
│   └── Selector [Phase2 战斗]
│       ├── BD_UseBossSkillWeighted          // 仅 wind_stone(availablePhaseIndices=[1])
│       └── BD_Wait(0.5)
└── Selector [Phase 1 地面战]
    ├── BD_UseBossSkillWeighted              // boomerang/fan_combo/tornado_small/tornado_large(均 phase[0])
    └── BD_MoveToPlayer
  • 不要把「过渡」和「Phase2 战斗」做成并列兄弟节点——过渡完成后会永远停在第一个 Success 节点,进不去战斗。必须把 BD_BossPhaseTransition 作为 Phase2 Sequence 的中间节点。
  • 击落机制不走 BT:由 ChaoFengKnockdownCounter(经 ChaoFengBoss.OnDamageTaken 转发)独立驱动。
  • 弹体经技能动画 AnimationEvent → ChaoFengBoss.SpawnProjectile("boomerang"/"tornado_small"/"tornado_large"/"wind_stone") 生成并按 ProjectileConfigSO 初始化。

5. abilityId / skillId 速查(脚手架已生成)

敌人 能力组件 abilityId ABL 资产
E001 PlayClipAbility / ContactChaseAbility e001_alert / e001_chase ABL_E001_Alert / ABL_E001_Chase
E002 CeilingHangStrikeAbility e002_ceiling_strike ABL_E002_CeilingStrike
E003 AnimatedCeilingDropAbility e003_fall ABL_E003_Fall
E004 Appear/Melee/RepeatSlam/Projectile/FacePlayer e004_appear/e004_bite/e004_headslam/e004_acid/e004_flip ABL_E004_*
E005 Melee(后摇脆弱)/Projectile e005_bite / e005_acid ABL_E005_*
E006 LeapAttack / ContactChase e006_leap / e006_chase ABL_E006_*

嘲风 BossSkillSOBD_UseBossSkill/BD_UseBossSkillWeighted 用 skillId boomerang / fan_combo / tornado_small / tornado_largePhase0wind_stonePhase1


6. 验证

  1. 把敌人放进可玩场景(脚手架的 Place E00X,或拖 Prefab确保场景有 NavSurface 已烘焙(追击/MoveTo 依赖)。
  2. 进入 Play。在 Inspector 看 EnemyBaseAiPhase 是否随逻辑切换Idle→Chase→Patrol…
  3. Scene 视图开 GizmoPhysicsPerceptionSystem 各槽以语义色绘制aggro 橙 / sight 浅蓝 / attack_* 红粉…),确认范围与玩家触发一致。
  4. Behavior Designer 编辑器在 Play 时高亮当前执行节点,逐节点排查卡点。

7. 常见坑

现象 原因 / 解法
巡逻分支用 Sequence 导致待机后不巡逻 巡逻必须用 Selector(待机/巡逻互斥分支)
BD_UseAbility 立刻返回成功、技能没播完 它会等 IsRunning==false;确认能力 ExecuteCoroutine 正确设置 Phase。技能不打断需 ABL SO interruptOnHurt=false
攻击不触发 BD_CanUseAbility 未勾 m_CheckRange 或 ABL 的 preferredRange/cooldown 不当;或感知槽名拼错(须用 SensorSlotNames 常量值)
找不到 BD_ 任务 未定义 GRAPH_DESIGNER(当前仅 Standalone 已定义)
嘲风进不了 Phase2 战斗 过渡节点做成了兄弟节点;须内嵌进 Phase2 Sequence见 §4
E002/固定怪 BT 里写了 Patrol/MoveTo 固定陷阱无移动,不要加移动节点
翻身/墙崖检测想用感知槽 不需要;BD_Patrol 内置 EnemyMovement.IsWallAhead/IsLedgeAhead

8. 小结

行为树 = 条件判断BD_IsXxx / BD_IsSensorDetecting+ 调用BD_UseAbility / BD_SetAiPhase / BD_UseBossSkill*。动画、攻击实现、感知计算、移动物理都在框架其它层BT 保持「薄」。按 §4 的结构逐怪搭建,用 §5 的 id、§3 的槽名填字段,再按 §6 验证即可。