refactor(enemy): 删除 AiPhase 影子层,动画统一由 locomotion/反应态/能力驱动
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using Animancer;
|
using Animancer;
|
||||||
|
using BaseGames.AI;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace BaseGames.Enemies.Abilities
|
namespace BaseGames.Enemies.Abilities
|
||||||
@@ -67,8 +68,8 @@ namespace BaseGames.Enemies.Abilities
|
|||||||
|
|
||||||
yield return EnemyAbilityWaits.Get(_recoveryTime);
|
yield return EnemyAbilityWaits.Get(_recoveryTime);
|
||||||
|
|
||||||
// SetAiPhase(Patrol) 自动播放 AnimConfig.Walk(地面移动动画)
|
// PlayLocomotionClip(Patrol) 播放 AnimConfig.Walk(地面移动动画)
|
||||||
_enemy.SetAiPhase(AiPhase.Patrol);
|
_enemy.PlayLocomotionClip(LocomotionMode.Patrol);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsGrounded()
|
private bool IsGrounded()
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace BaseGames.Enemies.Abilities
|
|||||||
|
|
||||||
_animancer.Play(_appearClip);
|
_animancer.Play(_appearClip);
|
||||||
yield return EnemyAbilityWaits.Get(_appearClip.Clip.length);
|
yield return EnemyAbilityWaits.Get(_appearClip.Clip.length);
|
||||||
_enemy.SetAiPhase(AiPhase.Combat);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ namespace BaseGames.Enemies.Abilities
|
|||||||
protected override IEnumerator ExecuteCoroutine()
|
protected override IEnumerator ExecuteCoroutine()
|
||||||
{
|
{
|
||||||
Phase = AbilityRunState.Active;
|
Phase = AbilityRunState.Active;
|
||||||
|
_enemy.SetEngaged(true);
|
||||||
|
|
||||||
// 追击行为的速度/阶段声明归本能力(AI 只触发能力,不做这些执行)
|
// 追击行为的速度/阶段声明归本能力(AI 只触发能力,不做这些执行)
|
||||||
if (_enemy.Nav != null && _enemy.Stats != null)
|
if (_enemy.Nav != null && _enemy.Stats != null)
|
||||||
_enemy.Nav.SetSpeed(_enemy.Stats.RunSpeed);
|
_enemy.Nav.SetSpeed(_enemy.Stats.RunSpeed);
|
||||||
_enemy.SetAiPhase(AiPhase.Chase);
|
|
||||||
|
|
||||||
if (_loopClip.Clip != null)
|
if (_loopClip.Clip != null)
|
||||||
_animancer.Play(_loopClip);
|
_animancer.Play(_loopClip);
|
||||||
@@ -55,6 +55,7 @@ namespace BaseGames.Enemies.Abilities
|
|||||||
{
|
{
|
||||||
if (_contactDamage != null)
|
if (_contactDamage != null)
|
||||||
_contactDamage.enabled = false;
|
_contactDamage.enabled = false;
|
||||||
|
_enemy.SetEngaged(false);
|
||||||
_enemy.Locomotion?.Stop();
|
_enemy.Locomotion?.Stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
namespace BaseGames.Enemies
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 敌人 AI 行为阶段枚举(独立于 EnemyStateType 的物理/战斗状态)。
|
|
||||||
///
|
|
||||||
/// 两套状态正交关系:
|
|
||||||
/// - <see cref="EnemyStateType"/>:受击/击飞/死亡等物理事件驱动的瞬时状态。
|
|
||||||
/// - <see cref="AiPhase"/>:行为树主动切换的持续阶段,反映 AI 当前的战术意图。
|
|
||||||
///
|
|
||||||
/// 典型切换路径:
|
|
||||||
/// <code>
|
|
||||||
/// Idle / Patrol
|
|
||||||
/// → [感知触发] → Alert(短暂警觉动作)
|
|
||||||
/// → Chase(追击)
|
|
||||||
/// → Combat(攻击范围内)
|
|
||||||
/// ↓ 丢失视线超时
|
|
||||||
/// → Investigate(前往最后可见位置搜查)
|
|
||||||
/// ↓ 搜查完成或超出范围
|
|
||||||
/// → ReturnHome(归位)→ Patrol
|
|
||||||
/// </code>
|
|
||||||
/// </summary>
|
|
||||||
public enum AiPhase
|
|
||||||
{
|
|
||||||
/// <summary>未激活,静止或播放待机循环动画。</summary>
|
|
||||||
Idle,
|
|
||||||
|
|
||||||
/// <summary>沿路点或随机目的地正常巡逻,无感知威胁。</summary>
|
|
||||||
Patrol,
|
|
||||||
|
|
||||||
/// <summary>感知到威胁后的短暂警觉过渡阶段,通常播放抬头/发现动作后进入 Chase。</summary>
|
|
||||||
Alert,
|
|
||||||
|
|
||||||
/// <summary>全力追击玩家,使用奔跑速度。</summary>
|
|
||||||
Chase,
|
|
||||||
|
|
||||||
/// <summary>玩家在攻击距离内,准备或正在执行攻击。</summary>
|
|
||||||
Combat,
|
|
||||||
|
|
||||||
/// <summary>丢失目标后前往最后可见坐标搜查,若无发现则切 ReturnHome 或 Patrol。</summary>
|
|
||||||
Investigate,
|
|
||||||
|
|
||||||
/// <summary>超出追击范围或搜查结束后归位到初始位置。</summary>
|
|
||||||
ReturnHome,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: f2390565562baf84fb61b592c2dade24
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -88,14 +88,6 @@ namespace BaseGames.Enemies
|
|||||||
private EnemyStateType _currentState;
|
private EnemyStateType _currentState;
|
||||||
public EnemyStateType CurrentState => _currentState;
|
public EnemyStateType CurrentState => _currentState;
|
||||||
|
|
||||||
// ── AI 行为阶段(独立于物理/战斗状态)────────────────────────────────
|
|
||||||
private AiPhase _currentAiPhase = AiPhase.Idle;
|
|
||||||
/// <summary>当前 AI 行为阶段(BD 任务读取;ForceState 不会改变此值)。</summary>
|
|
||||||
public AiPhase CurrentAiPhase => _currentAiPhase;
|
|
||||||
|
|
||||||
/// <summary>AI 行为阶段变更时触发,可用于驱动外部系统(音效/视觉效果等)。</summary>
|
|
||||||
public event System.Action<AiPhase> OnAiPhaseChanged;
|
|
||||||
|
|
||||||
// ── 导航语义(归位 / 搜查)────────────────────────────────────────────
|
// ── 导航语义(归位 / 搜查)────────────────────────────────────────────
|
||||||
/// <summary>Awake/Start 时记录的初始世界坐标,供 BD_ReturnToHome 归位使用。</summary>
|
/// <summary>Awake/Start 时记录的初始世界坐标,供 BD_ReturnToHome 归位使用。</summary>
|
||||||
public Vector2 HomePosition { get; private set; }
|
public Vector2 HomePosition { get; private set; }
|
||||||
@@ -116,7 +108,6 @@ namespace BaseGames.Enemies
|
|||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
[Header("── 运行时调试(仅 Editor)──")]
|
[Header("── 运行时调试(仅 Editor)──")]
|
||||||
[SerializeField] private EnemyStateType _dbg_CurrentState;
|
[SerializeField] private EnemyStateType _dbg_CurrentState;
|
||||||
[SerializeField] private AiPhase _dbg_AiPhase;
|
|
||||||
[SerializeField] private bool _dbg_HasPlayer;
|
[SerializeField] private bool _dbg_HasPlayer;
|
||||||
[SerializeField] private Vector2 _dbg_LastKnownPos;
|
[SerializeField] private Vector2 _dbg_LastKnownPos;
|
||||||
#endif
|
#endif
|
||||||
@@ -243,6 +234,10 @@ namespace BaseGames.Enemies
|
|||||||
public EnemyAiBrain Brain => _brain;
|
public EnemyAiBrain Brain => _brain;
|
||||||
private EnemyAiBrain _brain;
|
private EnemyAiBrain _brain;
|
||||||
|
|
||||||
|
/// <summary>是否处于交战(追击/攻击)中——供群体警戒判断"已交战不降级"。由 Chase 能力置位。</summary>
|
||||||
|
public bool IsEngaged { get; private set; }
|
||||||
|
public void SetEngaged(bool engaged) => IsEngaged = engaged;
|
||||||
|
|
||||||
// ── BD 行为树接口(虚方法)────────────────────────────────────────
|
// ── BD 行为树接口(虚方法)────────────────────────────────────────
|
||||||
|
|
||||||
public virtual void MoveTo(Vector2 target)
|
public virtual void MoveTo(Vector2 target)
|
||||||
@@ -446,45 +441,13 @@ namespace BaseGames.Enemies
|
|||||||
public void ReceiveAlert(Transform sharedPlayerTransform, Vector2 lastKnownPos)
|
public void ReceiveAlert(Transform sharedPlayerTransform, Vector2 lastKnownPos)
|
||||||
{
|
{
|
||||||
if (!IsAlive) return;
|
if (!IsAlive) return;
|
||||||
if (_currentAiPhase == AiPhase.Chase || _currentAiPhase == AiPhase.Combat) return;
|
if (IsEngaged) return;
|
||||||
|
|
||||||
// 同步玩家引用(若本敌人尚未感知到玩家)
|
// 同步玩家引用(若本敌人尚未感知到玩家)
|
||||||
if (sharedPlayerTransform != null && _playerTransform == null)
|
if (sharedPlayerTransform != null && _playerTransform == null)
|
||||||
_playerTransform = sharedPlayerTransform;
|
_playerTransform = sharedPlayerTransform;
|
||||||
LastKnownPlayerPosition = lastKnownPos;
|
LastKnownPlayerPosition = lastKnownPos;
|
||||||
SetAiPhase(AiPhase.Alert);
|
PlayLocomotionClip(LocomotionMode.Face);
|
||||||
}
|
|
||||||
|
|
||||||
[Header("AI 行为阶段")]
|
|
||||||
[Tooltip("SetAiPhase 变更时是否自动播放 AnimConfig 中对应的阶段动画(如 Alert/Investigate)")]
|
|
||||||
[SerializeField] private bool _autoPlayPhaseAnimation = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置 AI 行为阶段,并广播 <see cref="OnAiPhaseChanged"/> 事件。
|
|
||||||
/// 若 <see cref="_autoPlayPhaseAnimation"/> 为 true,自动播放 AnimConfig 对应动画。
|
|
||||||
/// 由 BD Task(BD_SetAiPhase / BD_ChasePlayer 等)调用;
|
|
||||||
/// 不触发受击/死亡等 EnemyStateType 流程。
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>按当前移动模式播放步态动画(由 EnemyLocomotion 在模式变化时调用)。</summary>
|
/// <summary>按当前移动模式播放步态动画(由 EnemyLocomotion 在模式变化时调用)。</summary>
|
||||||
@@ -620,7 +583,6 @@ namespace BaseGames.Enemies
|
|||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
_dbg_CurrentState = _currentState;
|
_dbg_CurrentState = _currentState;
|
||||||
_dbg_AiPhase = _currentAiPhase;
|
|
||||||
_dbg_HasPlayer = _playerTransform != null;
|
_dbg_HasPlayer = _playerTransform != null;
|
||||||
_dbg_LastKnownPos = LastKnownPlayerPosition;
|
_dbg_LastKnownPos = LastKnownPlayerPosition;
|
||||||
#endif
|
#endif
|
||||||
@@ -749,7 +711,7 @@ namespace BaseGames.Enemies
|
|||||||
|
|
||||||
// 重置状态(对象池复用:跳过 Dead 终态守卫,强制恢复到 Controlled)
|
// 重置状态(对象池复用:跳过 Dead 终态守卫,强制恢复到 Controlled)
|
||||||
ForceStateRespawn(EnemyStateType.Controlled);
|
ForceStateRespawn(EnemyStateType.Controlled);
|
||||||
_currentAiPhase = AiPhase.Idle;
|
IsEngaged = false;
|
||||||
|
|
||||||
// 重置对象池复用相关的运行时感知数据
|
// 重置对象池复用相关的运行时感知数据
|
||||||
// 注意:_playerTransform 不重置(场景中玩家仍存在),只重置追踪历史
|
// 注意:_playerTransform 不重置(场景中玩家仍存在),只重置追踪历史
|
||||||
@@ -809,21 +771,11 @@ namespace BaseGames.Enemies
|
|||||||
// ── 运行时:AI 状态标签(常态可见,无需选中)────────────────
|
// ── 运行时:AI 状态标签(常态可见,无需选中)────────────────
|
||||||
if (Application.isPlaying)
|
if (Application.isPlaying)
|
||||||
{
|
{
|
||||||
Color phaseColor = _currentAiPhase switch
|
Color phaseColor = Color.yellow;
|
||||||
{
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
UnityEditor.Handles.color = phaseColor;
|
UnityEditor.Handles.color = phaseColor;
|
||||||
UnityEditor.Handles.Label(
|
UnityEditor.Handles.Label(
|
||||||
transform.position + Vector3.up * 1.2f,
|
transform.position + Vector3.up * 1.2f,
|
||||||
$"[{_currentAiPhase}] {_currentState}");
|
$"[{(_brain != null ? _brain.CurrentStateName : "-")}] {_currentState}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 运行时:LOS 连线 ────────────────────────────────────────
|
// ── 运行时:LOS 连线 ────────────────────────────────────────
|
||||||
@@ -860,20 +812,10 @@ namespace BaseGames.Enemies
|
|||||||
// 感知范围圆形 Gizmo 由 PhysicsPerceptionSystemEditor [DrawGizmo] 统一绘制,
|
// 感知范围圆形 Gizmo 由 PhysicsPerceptionSystemEditor [DrawGizmo] 统一绘制,
|
||||||
// 此处不重复绘制。
|
// 此处不重复绘制。
|
||||||
|
|
||||||
// 运行时:选中时绘制 AiPhase 彩色外圆(突出显示当前状态)
|
// 运行时:选中时绘制 AI 状态外圆(突出显示当前决策状态)
|
||||||
if (Application.isPlaying)
|
if (Application.isPlaying)
|
||||||
{
|
{
|
||||||
Color phaseColor = _currentAiPhase switch
|
Color phaseColor = Color.yellow;
|
||||||
{
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
Gizmos.color = phaseColor;
|
Gizmos.color = phaseColor;
|
||||||
Gizmos.DrawWireSphere(transform.position, 0.5f);
|
Gizmos.DrawWireSphere(transform.position, 0.5f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace BaseGames.Enemies
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 运行时 AI 状态调试叠加层(仅 Editor)。
|
/// 运行时 AI 状态调试叠加层(仅 Editor)。
|
||||||
/// 挂到敌人 Prefab 根节点,运行时在场景视图 / GameView 上显示:
|
/// 挂到敌人 Prefab 根节点,运行时在场景视图 / GameView 上显示:
|
||||||
/// - AiPhase / EnemyStateType
|
/// - AI 决策状态 / EnemyStateType
|
||||||
/// - 当前激活的能力名称及运行状态
|
/// - 当前激活的能力名称及运行状态
|
||||||
/// - LOS 状态 / 到玩家距离
|
/// - LOS 状态 / 到玩家距离
|
||||||
/// - Boss:技能执行状态 + 各技能冷却倒计时
|
/// - Boss:技能执行状态 + 各技能冷却倒计时
|
||||||
@@ -85,8 +85,8 @@ namespace BaseGames.Enemies
|
|||||||
// 名称
|
// 名称
|
||||||
list.Add($"<b>{gameObject.name}</b>");
|
list.Add($"<b>{gameObject.name}</b>");
|
||||||
|
|
||||||
// AiPhase + EnemyState
|
// AI 决策状态 + EnemyState
|
||||||
list.Add($"Phase: <color=#ffcc44>{_enemy.CurrentAiPhase}</color> State: <color=#88ddff>{_enemy.CurrentState}</color>");
|
list.Add($"Phase: <color=#ffcc44>{(_enemy.Brain != null ? _enemy.Brain.CurrentStateName : "-")}</color> State: <color=#88ddff>{_enemy.CurrentState}</color>");
|
||||||
|
|
||||||
// 距离 + LOS
|
// 距离 + LOS
|
||||||
float dist = _enemy.Stats != null ? Mathf.Sqrt(_enemy.Stats.SqrDistanceToPlayer) : -1f;
|
float dist = _enemy.Stats != null ? Mathf.Sqrt(_enemy.Stats.SqrDistanceToPlayer) : -1f;
|
||||||
|
|||||||
Reference in New Issue
Block a user