feat: Update enemy AI and movement systems
- Enhanced Physics2D layer collision report with new interactions between Player and Enemy layers. - Refactored BD_InvestigateLastKnown to streamline animation handling and improve readability. - Simplified BD_MaintainCombatDistance by consolidating movement stop logic. - Updated BD_MoveToPlayer to set AI phase on start. - Improved BD_Patrol logic with better handling of stuck states and path failures. - Enhanced BD_PatrolWaypoints to manage stuck conditions and retry logic more effectively. - Refined BD_ReturnToHome to remove unnecessary animation calls. - Updated BD_WalkRandom to ensure AI phase is set correctly on start. - Improved EnemyAbilityBase to delegate target facing to the movement system. - Enhanced EnemyBase with new movement methods for better control. - Refactored EnemyMovement to introduce a new input system for handling movement and facing. - Added EnemyMoveInput struct to encapsulate movement intentions. - Updated Physics2DSettings to reflect new layer collision matrix. - Introduced RTK CLI instructions for optimized command usage.
This commit is contained in:
@@ -100,6 +100,18 @@ namespace BaseGames.Enemies
|
||||
/// 由 BD_ChasePlayer 在持有视线时每帧更新;视线丢失后保留最后记录值供 BD_InvestigateLastKnown 使用。
|
||||
/// </summary>
|
||||
public Vector2 LastKnownPlayerPosition { get; set; }
|
||||
|
||||
#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;
|
||||
#if GRAPH_DESIGNER
|
||||
[SerializeField] private float _dbg_BtTickInterval;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// POCO 状态对象字典:枚举保持对外 API 不变。
|
||||
// 子类可在 Awake() 重写条目注入自定义状态对象。
|
||||
protected readonly System.Collections.Generic.Dictionary<EnemyStateType, IEnemyState> _stateObjs
|
||||
@@ -219,12 +231,24 @@ namespace BaseGames.Enemies
|
||||
=> _nav?.RequestMoveTo(target);
|
||||
|
||||
public virtual void MoveInDirection(float dir)
|
||||
=> _movement?.MoveHorizontal(dir);
|
||||
{
|
||||
if (_movement == null) return;
|
||||
_movement.PendingInput.MoveDir = dir;
|
||||
_movement.PendingInput.WantStop = false; // 移动意图覆盖停止脉冲
|
||||
}
|
||||
|
||||
public virtual void MoveInDirectionWithSpeed(float dir, float speed)
|
||||
{
|
||||
if (_movement == null) return;
|
||||
_movement.PendingInput.MoveDir = dir;
|
||||
_movement.PendingInput.MoveSpeed = speed;
|
||||
_movement.PendingInput.WantStop = false; // 移动意图覆盖停止脉冲
|
||||
}
|
||||
|
||||
public virtual void StopMovement()
|
||||
{
|
||||
_nav?.StopNavigation();
|
||||
_movement?.StopHorizontal();
|
||||
if (_movement != null) _movement.PendingInput.WantStop = true;
|
||||
}
|
||||
|
||||
/// <summary>施加状态效果(需要 EnemyStatusEffectManager 组件)。同类型效果自动刷新。</summary>
|
||||
@@ -279,8 +303,24 @@ namespace BaseGames.Enemies
|
||||
|
||||
public virtual void FacePlayer()
|
||||
{
|
||||
if (_playerTransform != null)
|
||||
_movement?.FaceTarget(_playerTransform.position);
|
||||
if (_movement == null || _playerTransform == null) return;
|
||||
_movement.PendingInput.WantFace = true;
|
||||
_movement.PendingInput.FaceTargetPos = _playerTransform.position;
|
||||
_movement.PendingInput.FaceDir = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜查"环顾"子步骤:停止移动,播放原地环顾动画。
|
||||
/// 由搜查行为触发;动画细节由角色自己决定,外部无需感知 AnimConfig。
|
||||
/// </summary>
|
||||
public void BeginLookAround()
|
||||
{
|
||||
StopMovement();
|
||||
if (_animancer != null && _animConfig != null)
|
||||
{
|
||||
var clip = _animConfig.Investigate ?? _animConfig.Idle;
|
||||
if (clip != null) _animancer.Play(clip);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Knockback(DamageInfo info)
|
||||
@@ -416,6 +456,7 @@ namespace BaseGames.Enemies
|
||||
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,
|
||||
@@ -499,6 +540,7 @@ namespace BaseGames.Enemies
|
||||
_stateObjs[EnemyStateType.Dead] = new EnemyDeadState();
|
||||
|
||||
_nav = GetComponent<IPathAgent>() ?? new NullPathAgent();
|
||||
if (_movement == null) _movement = GetComponent<EnemyMovement>();
|
||||
_poiseSource = GetComponent<IPoiseSource>();
|
||||
_sensorHub = GetComponentInChildren<Perception.EnemySensorHub>();
|
||||
_statusEffects = GetComponent<StatusEffects.EnemyStatusEffectManager>();
|
||||
@@ -507,8 +549,9 @@ namespace BaseGames.Enemies
|
||||
_abilities.CollectFrom(gameObject);
|
||||
_colliders = GetComponentsInChildren<Collider2D>(true);
|
||||
|
||||
Debug.Assert(_statsSO != null, "[EnemyBase] _statsSO 未赋值,请在 Prefab Inspector 中指定 EnemyStatsSO。", this);
|
||||
Debug.Assert(_stats != null, "[EnemyBase] _stats 未绑定,请在 Prefab Inspector 中绑定 EnemyStats 组件。", this);
|
||||
Debug.Assert(_statsSO != null, "[EnemyBase] _statsSO 未赋值,请在 Prefab Inspector 中指定 EnemyStatsSO。", this);
|
||||
Debug.Assert(_stats != null, "[EnemyBase] _stats 未绑定,请在 Prefab Inspector 中绑定 EnemyStats 组件。", this);
|
||||
Debug.Assert(_movement != null, "[EnemyBase] _movement 未找到,请确保同 GameObject 上挂有 EnemyMovement 组件。", this);
|
||||
_stats.Initialize(_statsSO);
|
||||
|
||||
// 订阅玩家生成事件(PlayerController.Start 广播),避免每个敌人独立 FindWithTag
|
||||
@@ -556,6 +599,15 @@ namespace BaseGames.Enemies
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if UNITY_EDITOR
|
||||
_dbg_CurrentState = _currentState;
|
||||
_dbg_AiPhase = _currentAiPhase;
|
||||
_dbg_HasPlayer = _playerTransform != null;
|
||||
_dbg_LastKnownPos = LastKnownPlayerPosition;
|
||||
#if GRAPH_DESIGNER
|
||||
_dbg_BtTickInterval = _btCurrentInterval;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -768,6 +820,26 @@ namespace BaseGames.Enemies
|
||||
UnityEditor.Handles.matrix = prevM;
|
||||
}
|
||||
|
||||
// ── 运行时: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,
|
||||
};
|
||||
UnityEditor.Handles.color = phaseColor;
|
||||
UnityEditor.Handles.Label(
|
||||
transform.position + Vector3.up * 1.2f,
|
||||
$"[{_currentAiPhase}] {_currentState}");
|
||||
}
|
||||
|
||||
// ── 运行时:LOS 连线 ────────────────────────────────────────
|
||||
if (!Application.isPlaying || _playerTransform == null) return;
|
||||
|
||||
@@ -814,7 +886,7 @@ namespace BaseGames.Enemies
|
||||
UnityEditor.Handles.matrix = prevM;
|
||||
}
|
||||
|
||||
// 运行时:AiPhase 彩色圆 + 状态标签
|
||||
// 运行时:选中时绘制 AiPhase 彩色外圆(突出显示当前状态)
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Color phaseColor = _currentAiPhase switch
|
||||
@@ -830,11 +902,6 @@ namespace BaseGames.Enemies
|
||||
};
|
||||
Gizmos.color = phaseColor;
|
||||
Gizmos.DrawWireSphere(transform.position, 0.5f);
|
||||
|
||||
UnityEditor.Handles.color = phaseColor;
|
||||
UnityEditor.Handles.Label(
|
||||
transform.position + Vector3.up * 1.0f,
|
||||
$"{_currentAiPhase} | {_currentState}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user