12 KiB
行为树创建指南 — 敌人 / Boss(Behavior Designer Pro)
面向:在已用脚手架搭好的敌人/Boss Prefab 上,挂载并搭建行为树(BT),跑通「巡逻 → 感知 → 技能」闭环。 配套:《小怪与Boss实现计划-01》(每个敌人的目标 BT 结构)、
02_Enemy_Boss_Setup_Guide。
0. 技术栈与前提
| 项 | 说明 |
|---|---|
| 行为树框架 | Opsive Behavior Designer Pro(com.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. 三条铁律(搭树前必读)
- 动画所有权:所有技能/触发型动画由对应
EnemyAbilityBase子类在ExecuteCoroutine()内_animancer.Play()播放。BT 禁止用BD_PlayAnimation触发技能动画。Idle/Walk/Run/Dead 由框架随SetAiPhase/Die()自动播放。 - 能力按 abilityId 调用:
BD_UseAbility/BD_CanUseAbility/BD_IsAbilityRunning通过 abilityId(或 SO 引用)查找挂在Abilities/子节点上的能力组件。abilityId 必须全局唯一。 - 墙/崖不走感知:
BD_Patrol用EnemyMovement.IsWallAhead/IsLedgeAhead内置射线翻转,无需感知槽。感知槽只用于「发现玩家/攻击距离」等(见 §3)。
2. 创建一棵行为树(通用步骤)
- 挂组件:选中敌人 Prefab 根对象 →
Add Component→Behavior Tree(Opsive)。 - 打开编辑器:Inspector 里点 Open(或
Tools/Opsive/Behavior Designer)。 - 保存形式:
- 内联(推荐单怪专用树):直接在该 Prefab 的 BehaviorTree 组件上编辑,随 Prefab 保存。
- 共享图(多怪复用):在编辑器中保存为独立 Behavior Tree 资产,再在组件上引用。约定路径
Assets/_Game/Data/AI/BT_{敌人}.asset,命名BT_*。
- 搭树:根用 Selector(见 §4 各结构),从任务面板(分类
BaseGames/Enemy/*)拖入 BD_ 任务,连边、填字段。 - 填字段:
BD_IsAiPhase选枚举;BD_UseAbility/BD_CanUseAbility填 abilityId 或拖 ABL SO;BD_IsSensorDetecting填槽名(见 §3)。 - 保存 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_CanUseAbility勾m_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") // LeapAttackAbility:Rigidbody 冲量
└── 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_* |
嘲风 BossSkillSO(BD_UseBossSkill/BD_UseBossSkillWeighted 用 skillId):
boomerang / fan_combo / tornado_small / tornado_large(Phase0)、wind_stone(Phase1)。
6. 验证
- 把敌人放进可玩场景(脚手架的
Place E00X,或拖 Prefab);确保场景有NavSurface已烘焙(追击/MoveTo 依赖)。 - 进入 Play。在 Inspector 看
EnemyBase的AiPhase是否随逻辑切换(Idle→Chase→Patrol…)。 - Scene 视图开 Gizmo:
PhysicsPerceptionSystem各槽以语义色绘制(aggro 橙 / sight 浅蓝 / attack_* 红粉…),确认范围与玩家触发一致。 - 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 验证即可。