From d718b99685ab83c9abc499f52ca653d6d7428606 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Fri, 10 Jul 2026 13:38:27 +0800 Subject: [PATCH] =?UTF-8?q?refactor(enemy):=20=E5=88=A0=E9=99=A4=20AiPhase?= =?UTF-8?q?=20=E5=BD=B1=E5=AD=90=E5=B1=82=EF=BC=8C=E5=8A=A8=E7=94=BB?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E7=94=B1=20locomotion/=E5=8F=8D=E5=BA=94?= =?UTF-8?q?=E6=80=81/=E8=83=BD=E5=8A=9B=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Abilities/AnimatedCeilingDropAbility.cs | 5 +- .../Enemies/Abilities/AppearAbility.cs | 1 - .../Enemies/Abilities/ContactChaseAbility.cs | 3 +- Assets/_Game/Scripts/Enemies/AiPhase.cs | 45 ----------- Assets/_Game/Scripts/Enemies/AiPhase.cs.meta | 11 --- Assets/_Game/Scripts/Enemies/EnemyBase.cs | 80 +++---------------- .../Scripts/Enemies/EnemyDebugOverlay.cs | 6 +- 7 files changed, 19 insertions(+), 132 deletions(-) delete mode 100644 Assets/_Game/Scripts/Enemies/AiPhase.cs delete mode 100644 Assets/_Game/Scripts/Enemies/AiPhase.cs.meta diff --git a/Assets/_Game/Scripts/Enemies/Abilities/AnimatedCeilingDropAbility.cs b/Assets/_Game/Scripts/Enemies/Abilities/AnimatedCeilingDropAbility.cs index 555cbf40..947f3307 100644 --- a/Assets/_Game/Scripts/Enemies/Abilities/AnimatedCeilingDropAbility.cs +++ b/Assets/_Game/Scripts/Enemies/Abilities/AnimatedCeilingDropAbility.cs @@ -1,5 +1,6 @@ using System.Collections; using Animancer; +using BaseGames.AI; using UnityEngine; namespace BaseGames.Enemies.Abilities @@ -67,8 +68,8 @@ namespace BaseGames.Enemies.Abilities yield return EnemyAbilityWaits.Get(_recoveryTime); - // SetAiPhase(Patrol) 自动播放 AnimConfig.Walk(地面移动动画) - _enemy.SetAiPhase(AiPhase.Patrol); + // PlayLocomotionClip(Patrol) 播放 AnimConfig.Walk(地面移动动画) + _enemy.PlayLocomotionClip(LocomotionMode.Patrol); } private bool IsGrounded() diff --git a/Assets/_Game/Scripts/Enemies/Abilities/AppearAbility.cs b/Assets/_Game/Scripts/Enemies/Abilities/AppearAbility.cs index 98b0dfd5..be42f1e5 100644 --- a/Assets/_Game/Scripts/Enemies/Abilities/AppearAbility.cs +++ b/Assets/_Game/Scripts/Enemies/Abilities/AppearAbility.cs @@ -19,7 +19,6 @@ namespace BaseGames.Enemies.Abilities _animancer.Play(_appearClip); yield return EnemyAbilityWaits.Get(_appearClip.Clip.length); - _enemy.SetAiPhase(AiPhase.Combat); } } } diff --git a/Assets/_Game/Scripts/Enemies/Abilities/ContactChaseAbility.cs b/Assets/_Game/Scripts/Enemies/Abilities/ContactChaseAbility.cs index 5288c979..778f2e10 100644 --- a/Assets/_Game/Scripts/Enemies/Abilities/ContactChaseAbility.cs +++ b/Assets/_Game/Scripts/Enemies/Abilities/ContactChaseAbility.cs @@ -22,11 +22,11 @@ namespace BaseGames.Enemies.Abilities protected override IEnumerator ExecuteCoroutine() { Phase = AbilityRunState.Active; + _enemy.SetEngaged(true); // 追击行为的速度/阶段声明归本能力(AI 只触发能力,不做这些执行) if (_enemy.Nav != null && _enemy.Stats != null) _enemy.Nav.SetSpeed(_enemy.Stats.RunSpeed); - _enemy.SetAiPhase(AiPhase.Chase); if (_loopClip.Clip != null) _animancer.Play(_loopClip); @@ -55,6 +55,7 @@ namespace BaseGames.Enemies.Abilities { if (_contactDamage != null) _contactDamage.enabled = false; + _enemy.SetEngaged(false); _enemy.Locomotion?.Stop(); } } diff --git a/Assets/_Game/Scripts/Enemies/AiPhase.cs b/Assets/_Game/Scripts/Enemies/AiPhase.cs deleted file mode 100644 index b60a9b09..00000000 --- a/Assets/_Game/Scripts/Enemies/AiPhase.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace BaseGames.Enemies -{ - /// - /// 敌人 AI 行为阶段枚举(独立于 EnemyStateType 的物理/战斗状态)。 - /// - /// 两套状态正交关系: - /// - :受击/击飞/死亡等物理事件驱动的瞬时状态。 - /// - :行为树主动切换的持续阶段,反映 AI 当前的战术意图。 - /// - /// 典型切换路径: - /// - /// Idle / Patrol - /// → [感知触发] → Alert(短暂警觉动作) - /// → Chase(追击) - /// → Combat(攻击范围内) - /// ↓ 丢失视线超时 - /// → Investigate(前往最后可见位置搜查) - /// ↓ 搜查完成或超出范围 - /// → ReturnHome(归位)→ Patrol - /// - /// - public enum AiPhase - { - /// 未激活,静止或播放待机循环动画。 - Idle, - - /// 沿路点或随机目的地正常巡逻,无感知威胁。 - Patrol, - - /// 感知到威胁后的短暂警觉过渡阶段,通常播放抬头/发现动作后进入 Chase。 - Alert, - - /// 全力追击玩家,使用奔跑速度。 - Chase, - - /// 玩家在攻击距离内,准备或正在执行攻击。 - Combat, - - /// 丢失目标后前往最后可见坐标搜查,若无发现则切 ReturnHome 或 Patrol。 - Investigate, - - /// 超出追击范围或搜查结束后归位到初始位置。 - ReturnHome, - } -} diff --git a/Assets/_Game/Scripts/Enemies/AiPhase.cs.meta b/Assets/_Game/Scripts/Enemies/AiPhase.cs.meta deleted file mode 100644 index fd8a2528..00000000 --- a/Assets/_Game/Scripts/Enemies/AiPhase.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f2390565562baf84fb61b592c2dade24 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_Game/Scripts/Enemies/EnemyBase.cs b/Assets/_Game/Scripts/Enemies/EnemyBase.cs index fc27d1ef..bdaf42dc 100644 --- a/Assets/_Game/Scripts/Enemies/EnemyBase.cs +++ b/Assets/_Game/Scripts/Enemies/EnemyBase.cs @@ -88,14 +88,6 @@ namespace BaseGames.Enemies private EnemyStateType _currentState; public EnemyStateType CurrentState => _currentState; - // ── AI 行为阶段(独立于物理/战斗状态)──────────────────────────────── - private AiPhase _currentAiPhase = AiPhase.Idle; - /// 当前 AI 行为阶段(BD 任务读取;ForceState 不会改变此值)。 - public AiPhase CurrentAiPhase => _currentAiPhase; - - /// AI 行为阶段变更时触发,可用于驱动外部系统(音效/视觉效果等)。 - public event System.Action OnAiPhaseChanged; - // ── 导航语义(归位 / 搜查)──────────────────────────────────────────── /// Awake/Start 时记录的初始世界坐标,供 BD_ReturnToHome 归位使用。 public Vector2 HomePosition { get; private set; } @@ -116,7 +108,6 @@ namespace BaseGames.Enemies #if UNITY_EDITOR [Header("── 运行时调试(仅 Editor)──")] [SerializeField] private EnemyStateType _dbg_CurrentState; - [SerializeField] private AiPhase _dbg_AiPhase; [SerializeField] private bool _dbg_HasPlayer; [SerializeField] private Vector2 _dbg_LastKnownPos; #endif @@ -243,6 +234,10 @@ namespace BaseGames.Enemies public EnemyAiBrain Brain => _brain; private EnemyAiBrain _brain; + /// 是否处于交战(追击/攻击)中——供群体警戒判断"已交战不降级"。由 Chase 能力置位。 + public bool IsEngaged { get; private set; } + public void SetEngaged(bool engaged) => IsEngaged = engaged; + // ── BD 行为树接口(虚方法)──────────────────────────────────────── public virtual void MoveTo(Vector2 target) @@ -446,45 +441,13 @@ namespace BaseGames.Enemies public void ReceiveAlert(Transform sharedPlayerTransform, Vector2 lastKnownPos) { if (!IsAlive) return; - if (_currentAiPhase == AiPhase.Chase || _currentAiPhase == AiPhase.Combat) return; + if (IsEngaged) return; // 同步玩家引用(若本敌人尚未感知到玩家) if (sharedPlayerTransform != null && _playerTransform == null) _playerTransform = sharedPlayerTransform; LastKnownPlayerPosition = lastKnownPos; - SetAiPhase(AiPhase.Alert); - } - - [Header("AI 行为阶段")] - [Tooltip("SetAiPhase 变更时是否自动播放 AnimConfig 中对应的阶段动画(如 Alert/Investigate)")] - [SerializeField] private bool _autoPlayPhaseAnimation = true; - - /// - /// 设置 AI 行为阶段,并广播 事件。 - /// 若 为 true,自动播放 AnimConfig 对应动画。 - /// 由 BD Task(BD_SetAiPhase / BD_ChasePlayer 等)调用; - /// 不触发受击/死亡等 EnemyStateType 流程。 - /// - public void SetAiPhase(AiPhase phase) - { - if (_currentAiPhase == phase) return; - _currentAiPhase = phase; - OnAiPhaseChanged?.Invoke(phase); - - if (_autoPlayPhaseAnimation && _animancer != null && _animConfig != null) - { - var clip = phase switch - { - AiPhase.Alert => _animConfig.Alert, - AiPhase.Investigate => _animConfig.Investigate ?? _animConfig.Walk, - AiPhase.Patrol => _animConfig.Walk, - AiPhase.ReturnHome => _animConfig.Walk, - AiPhase.Chase => _animConfig.Run, - AiPhase.Idle => _animConfig.Idle, - _ => null, - }; - if (clip != null) _animancer.Play(clip); - } + PlayLocomotionClip(LocomotionMode.Face); } /// 按当前移动模式播放步态动画(由 EnemyLocomotion 在模式变化时调用)。 @@ -620,7 +583,6 @@ namespace BaseGames.Enemies #if UNITY_EDITOR _dbg_CurrentState = _currentState; - _dbg_AiPhase = _currentAiPhase; _dbg_HasPlayer = _playerTransform != null; _dbg_LastKnownPos = LastKnownPlayerPosition; #endif @@ -749,7 +711,7 @@ namespace BaseGames.Enemies // 重置状态(对象池复用:跳过 Dead 终态守卫,强制恢复到 Controlled) ForceStateRespawn(EnemyStateType.Controlled); - _currentAiPhase = AiPhase.Idle; + IsEngaged = false; // 重置对象池复用相关的运行时感知数据 // 注意:_playerTransform 不重置(场景中玩家仍存在),只重置追踪历史 @@ -809,21 +771,11 @@ namespace BaseGames.Enemies // ── 运行时:AI 状态标签(常态可见,无需选中)──────────────── if (Application.isPlaying) { - Color phaseColor = _currentAiPhase switch - { - AiPhase.Idle => Color.gray, - AiPhase.Patrol => Color.green, - AiPhase.Alert => Color.yellow, - AiPhase.Chase => new Color(1f, 0.5f, 0f), - AiPhase.Combat => Color.red, - AiPhase.Investigate => Color.cyan, - AiPhase.ReturnHome => Color.blue, - _ => Color.white, - }; + Color phaseColor = Color.yellow; UnityEditor.Handles.color = phaseColor; UnityEditor.Handles.Label( transform.position + Vector3.up * 1.2f, - $"[{_currentAiPhase}] {_currentState}"); + $"[{(_brain != null ? _brain.CurrentStateName : "-")}] {_currentState}"); } // ── 运行时:LOS 连线 ──────────────────────────────────────── @@ -860,20 +812,10 @@ namespace BaseGames.Enemies // 感知范围圆形 Gizmo 由 PhysicsPerceptionSystemEditor [DrawGizmo] 统一绘制, // 此处不重复绘制。 - // 运行时:选中时绘制 AiPhase 彩色外圆(突出显示当前状态) + // 运行时:选中时绘制 AI 状态外圆(突出显示当前决策状态) if (Application.isPlaying) { - Color phaseColor = _currentAiPhase switch - { - AiPhase.Idle => Color.gray, - AiPhase.Patrol => Color.green, - AiPhase.Alert => Color.yellow, - AiPhase.Chase => new Color(1f, 0.5f, 0f), - AiPhase.Combat => Color.red, - AiPhase.Investigate => Color.cyan, - AiPhase.ReturnHome => Color.blue, - _ => Color.white, - }; + Color phaseColor = Color.yellow; Gizmos.color = phaseColor; Gizmos.DrawWireSphere(transform.position, 0.5f); } diff --git a/Assets/_Game/Scripts/Enemies/EnemyDebugOverlay.cs b/Assets/_Game/Scripts/Enemies/EnemyDebugOverlay.cs index 911aae25..89f52dfd 100644 --- a/Assets/_Game/Scripts/Enemies/EnemyDebugOverlay.cs +++ b/Assets/_Game/Scripts/Enemies/EnemyDebugOverlay.cs @@ -8,7 +8,7 @@ namespace BaseGames.Enemies /// /// 运行时 AI 状态调试叠加层(仅 Editor)。 /// 挂到敌人 Prefab 根节点,运行时在场景视图 / GameView 上显示: - /// - AiPhase / EnemyStateType + /// - AI 决策状态 / EnemyStateType /// - 当前激活的能力名称及运行状态 /// - LOS 状态 / 到玩家距离 /// - Boss:技能执行状态 + 各技能冷却倒计时 @@ -85,8 +85,8 @@ namespace BaseGames.Enemies // 名称 list.Add($"{gameObject.name}"); - // AiPhase + EnemyState - list.Add($"Phase: {_enemy.CurrentAiPhase} State: {_enemy.CurrentState}"); + // AI 决策状态 + EnemyState + list.Add($"Phase: {(_enemy.Brain != null ? _enemy.Brain.CurrentStateName : "-")} State: {_enemy.CurrentState}"); // 距离 + LOS float dist = _enemy.Stats != null ? Mathf.Sqrt(_enemy.Stats.SqrDistanceToPlayer) : -1f;