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:
2026-05-29 17:01:59 +08:00
parent e24ecc9589
commit bcd8b0e90b
19 changed files with 179534 additions and 175 deletions

View File

@@ -38,11 +38,22 @@ namespace BaseGames.Enemies.AI
[Tooltip("每个路点到达后等待时长s")]
[SerializeField] private float m_WaitAtWaypoint = 0f;
// 首次启动重试间隔OnStart() 的第一次 RequestCurrent() 可能因 NavAgent 尚未完成
// UpdateMappedPosition()(脚本执行顺序问题)而静默失败。此值只需大于一帧即可。
private const float InitialRetryDelay = 0.05f;
// 正常巡逻中卡住的重试间隔:必须足够长,使任何在途 PB2d 路径请求
// Pending → Finished → HandlePathRequest 消费)都已处理完毕,避免竞争覆盖。
private const float StuckRetryDelay = 0.5f;
private EnemyBase _enemy;
private int _index = 0;
private int _dir = 1;
private float _waitTimer = 0f;
private bool _waiting = false;
private int _index = 0;
private int _dir = 1;
private float _waitTimer = 0f;
private bool _waiting = false;
private float _stuckTimer = 0f;
private bool _pathFailed = false;
private bool _hasMoved = false; // 首次成功开始移动后置 true
// ── 统一路点访问 ────────────────────────────────────────────────────
private int WaypointCount =>
@@ -68,9 +79,19 @@ namespace BaseGames.Enemies.AI
public override void OnStart()
{
if (WaypointCount == 0) return;
_waiting = false;
_waitTimer = 0f;
_waiting = false;
_waitTimer = 0f;
_stuckTimer = 0f;
_pathFailed = false;
_hasMoved = false;
_enemy?.SetAiPhase(AiPhase.Patrol);
if (_enemy?.Nav != null)
{
_enemy.Nav.OnGoalReached += HandleGoalReached;
_enemy.Nav.OnNavPathFailed += HandlePathFailed;
}
RequestCurrent();
}
@@ -86,38 +107,73 @@ namespace BaseGames.Enemies.AI
_waiting = false;
Advance();
RequestCurrent();
return TaskStatus.Running;
}
else
{
Vector2 wp = GetWaypoint(_index);
float sqrDist = ((Vector2)_enemy.transform.position - wp).sqrMagnitude;
if (sqrDist <= m_ArriveRadius * m_ArriveRadius)
// 兜底重试:用时间门控避免与 PB2d 的 Finished-状态路径请求产生竞争。
// 首次启动前_hasMoved=false使用短延迟处理 NavAgent 位置映射尚未就绪的情况;
// 正常巡逻中使用长延迟,确保在途路径请求已被 HandlePathRequest 消费完毕。
if (_enemy.Nav != null)
{
if (!_enemy.Nav.IsMoving)
{
if (m_WaitAtWaypoint > 0f)
_stuckTimer += Time.deltaTime;
float retryDelay = _hasMoved ? StuckRetryDelay : InitialRetryDelay;
if (_stuckTimer >= retryDelay)
{
_waiting = true;
_waitTimer = m_WaitAtWaypoint;
_enemy.StopMovement();
}
else
{
Advance();
_stuckTimer = 0f;
_pathFailed = false;
RequestCurrent();
}
}
else
{
_enemy.MoveTo(wp);
_enemy.Movement?.FaceTarget(wp);
_hasMoved = true;
_stuckTimer = 0f;
}
}
return TaskStatus.Running;
}
public override void OnEnd() => _enemy?.StopMovement();
public override void OnEnd()
{
if (_enemy?.Nav != null)
{
_enemy.Nav.OnGoalReached -= HandleGoalReached;
_enemy.Nav.OnNavPathFailed -= HandlePathFailed;
}
_enemy?.StopMovement();
}
// ── 内部辅助 ────────────────────────────────────────────────────────
private void HandleGoalReached()
{
_hasMoved = true;
_stuckTimer = 0f;
_pathFailed = false;
if (m_WaitAtWaypoint > 0f)
{
_waiting = true;
_waitTimer = m_WaitAtWaypoint;
_enemy?.StopMovement();
}
else
{
Advance();
RequestCurrent();
}
}
// 路径失败时不在回调中立即重试——此时 NavAgent.HandlePathRequest 尚未调用
// currentPathRequest.Reset(),直接提交新请求会被随后的 Reset() 覆盖清除。
// 改为设置标志,交由 OnUpdate 的计时兜底在下一帧安全重试。
private void HandlePathFailed()
{
_pathFailed = true;
_stuckTimer = 0f; // 重置计时器,使兜底在 StuckRetryDelay 后触发
}
private void RequestCurrent()
{
if (WaypointCount == 0) return;