refactor(enemy): 清行为树债——StopBehaviorTree 改名,删 ConsumeParryEvent

StopBehaviorTree → NotifyDecisionStop(方法体早已只是发 Died 信号)。
EnemyDeathSequence 的 _stopBehaviorTree 字段同步改名,带 FormerlySerializedAs 保值。
ConsumeParryEvent 及其 TTL 状态删除:零调用者,且 ForceState(Stagger) +
IsControllable 让位门已是更优表达,不需要第二条受击通道。

顺带清掉这三个文件里残留的行为树插件措辞(BD_* / BD Task / 停行为树)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-30 16:23:47 +08:00
co-authored by Claude Opus 5
parent a44aafee3a
commit 822dc90c21
3 changed files with 26 additions and 47 deletions
@@ -9,7 +9,7 @@ namespace BaseGames.Enemies.Behaviors
/// <summary> /// <summary>
/// 死亡前摇无敌演出(零代码替代每敌人专属的 <c>Die()</c> 重写)。 /// 死亡前摇无敌演出(零代码替代每敌人专属的 <c>Die()</c> 重写)。
/// <para> /// <para>
/// <see cref="EnemyBase.Die"/> 会委托本组件:停行为树 / 停移动、停用受击框,播放前摇动画并等待, /// <see cref="EnemyBase.Die"/> 会委托本组件:通知决策层终止 / 停移动、停用受击框,播放前摇动画并等待,
/// 期间敌人处于无敌(<see cref="EnemyBase.IsInvincible"/>),演出结束后回调真正的死亡清理。 /// 期间敌人处于无敌(<see cref="EnemyBase.IsInvincible"/>),演出结束后回调真正的死亡清理。
/// 前摇动画上可放置 SpawnProjectile 动画事件,配合 <see cref="EnemySpawnerOnEvent"/> 在演出中生成小怪。 /// 前摇动画上可放置 SpawnProjectile 动画事件,配合 <see cref="EnemySpawnerOnEvent"/> 在演出中生成小怪。
/// </para> /// </para>
@@ -26,8 +26,9 @@ namespace BaseGames.Enemies.Behaviors
[Header("演出期间")] [Header("演出期间")]
[Tooltip("演出期间停用的受击框(防止演出中被打断或二次受伤);对象池复用时 OnSpawn 自动恢复")] [Tooltip("演出期间停用的受击框(防止演出中被打断或二次受伤);对象池复用时 OnSpawn 自动恢复")]
[SerializeField] private HurtBox[] _hurtBoxesToDisable; [SerializeField] private HurtBox[] _hurtBoxesToDisable;
[Tooltip("演出开始时停止行为树(防止 BT 继续 Tick 覆盖演出动画)")] [UnityEngine.Serialization.FormerlySerializedAs("_stopBehaviorTree")]
[SerializeField] private bool _stopBehaviorTree = true; [Tooltip("死亡演出开始时通知决策层终止,防止 AI 继续覆盖演出")]
[SerializeField] private bool _stopDecisionLayer = true;
[Tooltip("演出开始时停止移动")] [Tooltip("演出开始时停止移动")]
[SerializeField] private bool _stopMovement = true; [SerializeField] private bool _stopMovement = true;
@@ -60,7 +61,7 @@ namespace BaseGames.Enemies.Behaviors
private IEnumerator Sequence(Action onComplete) private IEnumerator Sequence(Action onComplete)
{ {
if (_stopBehaviorTree) _enemy?.StopBehaviorTree(); if (_stopDecisionLayer) _enemy?.NotifyDecisionStop();
if (_stopMovement) _enemy?.StopMovement(); if (_stopMovement) _enemy?.StopMovement();
SetHurtBoxesEnabled(false); SetHurtBoxesEnabled(false);
@@ -18,7 +18,7 @@ namespace BaseGames.Enemies.Boss
/// 1. BossBase.BeginPhaseTransition → OnBeginPhaseTransition(1) 立即播放过渡动画 + 开始浮空 /// 1. BossBase.BeginPhaseTransition → OnBeginPhaseTransition(1) 立即播放过渡动画 + 开始浮空
/// 2. 无敌期结束(≥ _riseDuration+buffer)→ BossBase.EnterPhase(1) 广播阶段切换事件 /// 2. 无敌期结束(≥ _riseDuration+buffer)→ BossBase.EnterPhase(1) 广播阶段切换事件
/// ///
/// ⚠️ 动画由本脚本通过 Animancer.Play() 完整控制,不在 BD 中调用 BD_PlayAnimation /// ⚠️ 动画由本脚本通过 Animancer.Play() 完整控制,不由 AI 决策层驱动播放
/// </summary> /// </summary>
public class ChaoFengBoss : BossBase public class ChaoFengBoss : BossBase
{ {
@@ -57,7 +57,7 @@ namespace BaseGames.Enemies.Boss
/// <summary> /// <summary>
/// 阶段过渡开始时立即播放过渡动画并启动浮空协程。 /// 阶段过渡开始时立即播放过渡动画并启动浮空协程。
/// invincibleDuration 需在 BD_BossPhaseTransition 中配置为 ≥ _riseDuration + 缓冲(约 2.0s)。 /// invincibleDuration 需在触发阶段过渡的调用处配置为 ≥ _riseDuration + 缓冲(约 2.0s)。
/// </summary> /// </summary>
protected override void OnBeginPhaseTransition(int targetPhase) protected override void OnBeginPhaseTransition(int targetPhase)
{ {
@@ -185,7 +185,7 @@ namespace BaseGames.Enemies.Boss
private IEnumerator DefeatSequence() private IEnumerator DefeatSequence()
{ {
StopBehaviorTree(); NotifyDecisionStop();
_knockdownCounter?.ForceEnd(); _knockdownCounter?.ForceEnd();
// Phase 2(空中)先落地 // Phase 2(空中)先落地
+18 -40
View File
@@ -11,8 +11,8 @@ namespace BaseGames.Enemies
{ {
/// <summary> /// <summary>
/// 敌人基类(架构 07_EnemyModule §1)。 /// 敌人基类(架构 07_EnemyModule §1)。
/// 实现 IDamageable,为 Behavior Designer 任务提供统一虚方法接口。 /// 实现 IDamageable,为 AI 决策层提供统一虚方法接口。
/// 包含:BD 接口、受击、死亡流程。 /// 包含:决策层接口、受击、死亡流程。
/// ⚠️ _nav 字段类型为 IEnemyNavigator(在 BaseGames.Enemies.Navigation 中实现具体类)。 /// ⚠️ _nav 字段类型为 IEnemyNavigator(在 BaseGames.Enemies.Navigation 中实现具体类)。
/// 实现 IPoolable:配合 PooledObject 支持对象池复用,避免频繁 Destroy/Instantiate。 /// 实现 IPoolable:配合 PooledObject 支持对象池复用,避免频繁 Destroy/Instantiate。
/// </summary> /// </summary>
@@ -49,7 +49,7 @@ namespace BaseGames.Enemies
[SerializeField] private Collider2D _bodyCollider; [SerializeField] private Collider2D _bodyCollider;
[Header("区域(可选)")] [Header("区域(可选)")]
[Tooltip("地图固定巡逻/追击区域;配置后 BD_ChasePlayer 以区域边界替代 MaxChaseDistanceBD_ReturnToHome 归位至区域中心。留空则沿用出生点 + MaxChaseDistance 旧逻辑。")] [Tooltip("地图固定巡逻/追击区域;配置后追击以区域边界替代 MaxChaseDistance,归位至区域中心。留空则沿用出生点 + MaxChaseDistance 旧逻辑。")]
[SerializeField] private EnemyPatrolZone _patrolZone; [SerializeField] private EnemyPatrolZone _patrolZone;
[Header("事件频道")] [Header("事件频道")]
@@ -97,18 +97,18 @@ namespace BaseGames.Enemies
public EnemyStateType CurrentState => _currentState; public EnemyStateType CurrentState => _currentState;
// ── 导航语义(归位 / 搜查)──────────────────────────────────────────── // ── 导航语义(归位 / 搜查)────────────────────────────────────────────
/// <summary>Awake/Start 时记录的初始世界坐标,供 BD_ReturnToHome 归位使用。</summary> /// <summary>Awake/Start 时记录的初始世界坐标,供归位行为使用。</summary>
public Vector2 HomePosition { get; private set; } public Vector2 HomePosition { get; private set; }
/// <summary> /// <summary>
/// 玩家最后一次可见的世界坐标。 /// 玩家最后一次可见的世界坐标。
/// 由 BD_ChasePlayer 在持有视线时每帧更新;视线丢失后保留最后记录值供 BD_InvestigateLastKnown 使用。 /// 由追击行为在持有视线时每帧更新;视线丢失后保留最后记录值供「搜查最后已知位置」行为使用。
/// </summary> /// </summary>
public Vector2 LastKnownPlayerPosition { get; set; } public Vector2 LastKnownPlayerPosition { get; set; }
/// <summary> /// <summary>
/// 地图固定巡逻/追击区域(可选)。 /// 地图固定巡逻/追击区域(可选)。
/// 配置后 BD_ChasePlayer 以区域边界为追击上限;BD_ReturnToHome 归位至区域中心。 /// 配置后追击以区域边界为上限;归位至区域中心。
/// 未配置时退回旧逻辑(HomePosition + MaxChaseDistance)。 /// 未配置时退回旧逻辑(HomePosition + MaxChaseDistance)。
/// </summary> /// </summary>
public EnemyPatrolZone PatrolZone => _patrolZone; public EnemyPatrolZone PatrolZone => _patrolZone;
@@ -222,7 +222,7 @@ namespace BaseGames.Enemies
public IEnemyLocomotion Locomotion => _locomotion ?? (_locomotion = GetComponentInChildren<IEnemyLocomotion>(true)); public IEnemyLocomotion Locomotion => _locomotion ?? (_locomotion = GetComponentInChildren<IEnemyLocomotion>(true));
public EnemyMovement Movement => _movement; public EnemyMovement Movement => _movement;
public EnemyStats Stats => _stats; public EnemyStats Stats => _stats;
/// <summary>敌人配置 SO,供 BD Task / 状态对象读取配置数据(如 knockUpDuration)。</summary> /// <summary>敌人配置 SO,供 AI 决策层 / 状态对象读取配置数据(如 knockUpDuration)。</summary>
public EnemyStatsSO StatsSO => _statsSO; public EnemyStatsSO StatsSO => _statsSO;
public AnimancerComponent Animancer => _animancer; public AnimancerComponent Animancer => _animancer;
public EnemyAnimationConfigSO AnimConfig => _animConfig; public EnemyAnimationConfigSO AnimConfig => _animConfig;
@@ -234,9 +234,9 @@ namespace BaseGames.Enemies
private Abilities.EnemyAttackSelector _attackSelector; private Abilities.EnemyAttackSelector _attackSelector;
/// <summary>身体碰撞体几何的唯一权威查询口。移动/导航/能力一律经此获取身体尺寸。</summary> /// <summary>身体碰撞体几何的唯一权威查询口。移动/导航/能力一律经此获取身体尺寸。</summary>
public IEnemyBody Body => _body ??= new EnemyBody(gameObject, _bodyCollider); public IEnemyBody Body => _body ??= new EnemyBody(gameObject, _bodyCollider);
/// <summary>由 _onPlayerSpawned 事件缓存的玩家 Transform,供 BD 任务读取。</summary> /// <summary>由 _onPlayerSpawned 事件缓存的玩家 Transform,供 AI 决策层读取。</summary>
public Transform PlayerTransform => _playerTransform; public Transform PlayerTransform => _playerTransform;
/// <summary>感知 Hub;供 BD 任务及 QuotaManager 暂停/恢复感知使用。</summary> /// <summary>感知 Hub;供 AI 决策层及 QuotaManager 暂停/恢复感知使用。</summary>
public Perception.IPerceptionSystem SensorHub => _sensorHub; public Perception.IPerceptionSystem SensorHub => _sensorHub;
private Perception.IPerceptionSystem _sensorHub; private Perception.IPerceptionSystem _sensorHub;
/// <summary>威胁评估器(可选):为原始 LOS 结果叠加反应延迟,使感知更自然。</summary> /// <summary>威胁评估器(可选):为原始 LOS 结果叠加反应延迟,使感知更自然。</summary>
@@ -253,7 +253,7 @@ namespace BaseGames.Enemies
public bool IsEngaged { get; private set; } public bool IsEngaged { get; private set; }
public void SetEngaged(bool engaged) => IsEngaged = engaged; public void SetEngaged(bool engaged) => IsEngaged = engaged;
// ── BD 行为树接口(虚方法)──────────────────────────────────────── // ── AI 决策层接口(虚方法)──────────────────────────────────────
public virtual void MoveTo(Vector2 target) public virtual void MoveTo(Vector2 target)
=> _nav?.MoveTowards(target); => _nav?.MoveTowards(target);
@@ -402,37 +402,16 @@ namespace BaseGames.Enemies
} }
// ── 弹反(Parry)响应 ────────────────────────────────────────────── // ── 弹反(Parry)响应 ──────────────────────────────────────────────
private bool _wasParried;
private float _parryTimestamp;
// BD 树因阶段切换/死亡未能消费弹反事件时,超过此时长自动过期
private const float ParryEventTTL = 2f;
/// <summary> /// <summary>
/// 消费弹反事件标志(读取并清除) /// 被弹反时调用:强制进入 Stagger 并在 staggerDuration 秒后恢复
/// BD_OnParried Conditional Task 在每次 Tick 时调用此方法 /// Stagger 期间 IsControllable 为 falseAI 决策自动让位——不需要额外的 AI 信号
/// 若 BD 树因阶段切换或死亡未能及时消费,超过 TTL 后自动过期返回 false。 /// ⚠️ 目前**零调用者**:弹反管线(HurtBox → ParrySystem.ConsumeParry)拿不到攻击者引用,
/// </summary> /// 接线是一件独立任务(DamageInfo 加 attacker,或弹反成功时回调发起攻击的 HitBox)。
public bool ConsumeParryEvent()
{
if (!_wasParried) return false;
if (Time.time - _parryTimestamp > ParryEventTTL)
{
_wasParried = false;
return false;
}
_wasParried = false;
return true;
}
/// <summary>
/// 被弹反时调用:强制进入 Stagger 状态并在 <paramref name="staggerDuration"/> 秒后恢复。
/// 由近战攻击碰到玩家弹反框时触发(例如 BossParryDetector 或通用 ParryDetector)。
/// </summary> /// </summary>
public virtual void ReceiveParry(float staggerDuration = 0.5f) public virtual void ReceiveParry(float staggerDuration = 0.5f)
{ {
if (!IsAlive) return; if (!IsAlive) return;
_wasParried = true;
_parryTimestamp = Time.time;
ForceState(EnemyStateType.Stagger); ForceState(EnemyStateType.Stagger);
_abilities.InterruptAll(InterruptReason.Stagger); _abilities.InterruptAll(InterruptReason.Stagger);
ScheduleStateRecovery(EnemyStateType.Stagger, staggerDuration); ScheduleStateRecovery(EnemyStateType.Stagger, staggerDuration);
@@ -626,7 +605,7 @@ namespace BaseGames.Enemies
protected virtual void Start() protected virtual void Start()
{ {
// 记录出生位置,供 BD_ReturnToHome 归位使用 // 记录出生位置,供归位行为使用
HomePosition = transform.position; HomePosition = transform.position;
LastKnownPlayerPosition = transform.position; LastKnownPlayerPosition = transform.position;
@@ -659,11 +638,11 @@ namespace BaseGames.Enemies
protected virtual void OnDestroy() { } protected virtual void OnDestroy() { }
/// <summary> /// <summary>
/// 通知决策层终止(死亡演出 / 出场演出期间调用,防止决策继续覆盖演出逻辑)。 /// 通知决策层终止(死亡演出 / 出场演出期间调用,防止决策继续覆盖演出逻辑)。
/// 语义为"通知决策层终止",与 PerformDeath 的 Died 信号幂等(重复 Died 无害)。 /// 与 PerformDeath 的 Died 信号幂等(重复 Died 无害)。
/// 供配置型行为组件(如 EnemyDeathSequence)调用,故为 public。 /// 供配置型行为组件(如 EnemyDeathSequence)调用,故为 public。
/// </summary> /// </summary>
public void StopBehaviorTree() public void NotifyDecisionStop()
{ {
_brain?.Send(BaseGames.AI.AiSignal.Died); _brain?.Send(BaseGames.AI.AiSignal.Died);
} }
@@ -752,7 +731,6 @@ namespace BaseGames.Enemies
// 重置对象池复用相关的运行时感知数据 // 重置对象池复用相关的运行时感知数据
// 注意:_playerTransform 不重置(场景中玩家仍存在),只重置追踪历史 // 注意:_playerTransform 不重置(场景中玩家仍存在),只重置追踪历史
LastKnownPlayerPosition = transform.position; LastKnownPlayerPosition = transform.position;
_wasParried = false;
_deathSequenceActive = false; _deathSequenceActive = false;
// 重置生命值 // 重置生命值