Files
zeling_v2/Assets/_Game/Scripts/Enemies/Navigation/EnemyLocomotion.cs
T

157 lines
5.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using BaseGames.AI;
namespace BaseGames.Enemies
{
/// <summary>
/// 敌人移动执行器:唯一的移动/朝向入口。AI 状态与能力经 IEnemyLocomotion
/// 声明意图,本组件每帧把当前模式翻译为对 EnemyBase/IPathAgent 的调用。
/// 取代散落的 MoveTo/StopMovement/FacePlayer/FaceTarget 直调与三个协程能力。
/// </summary>
[DisallowMultipleComponent]
public sealed class EnemyLocomotion : MonoBehaviour, IEnemyLocomotion
{
[Header("巡逻策略(Wander/Pace/Waypoints")]
[SerializeField] private PatrolStrategy _patrolStrategy = PatrolStrategy.Wander;
[Header("Waypoints 策略参数")]
[SerializeField] private Transform[] _waypoints;
[SerializeField] private bool _pingPong;
[SerializeField] private float _waypointArriveRadius = 0.4f;
private EnemyBase _enemy;
private LocomotionMode _mode = LocomotionMode.Idle;
private Transform _approachTarget;
private Vector2 _facePoint;
private LocomotionMode _gaitMode;
private bool _gaitInit;
private float _paceDir = 1f;
private int _wpIndex = -1;
private int _wpDir = 1;
private bool _warnedNoWaypoints;
public LocomotionMode CurrentMode => _mode;
public bool IsMoving => _enemy != null && _enemy.Nav != null && _enemy.Nav.IsMoving;
private void Awake()
{
_enemy = GetComponentInParent<EnemyBase>();
if (_enemy == null)
Debug.LogError("[EnemyLocomotion] 找不到 EnemyBase。", this);
}
// ── IEnemyLocomotion(声明意图,不立即 actuate 之外的副作用)──
public void SetMode(LocomotionMode mode)
{
if (_mode == mode) return;
_mode = mode;
if (mode == LocomotionMode.Idle) _enemy?.StopMovement();
if (mode == LocomotionMode.Patrol && _enemy?.Stats != null)
_enemy.Nav?.SetSpeed(_enemy.Stats.WalkSpeed);
}
public void Approach(Transform target)
{
_mode = LocomotionMode.Approach;
_approachTarget = target;
if (_enemy?.Stats != null) _enemy.Nav?.SetSpeed(_enemy.Stats.RunSpeed);
}
public void MoveTo(Vector2 point)
{
_mode = LocomotionMode.Approach;
_approachTarget = null;
_enemy?.MoveTo(point);
}
public void Face(Vector2 lookAt)
{
_mode = LocomotionMode.Face;
_facePoint = lookAt;
}
public void Stop()
{
_mode = LocomotionMode.Idle;
_enemy?.StopMovement();
}
// ── 每帧把模式翻译成执行 ──
private void Update()
{
if (_enemy == null) return;
if (!_gaitInit || _gaitMode != _mode)
{
_enemy.PlayLocomotionClip(_mode);
_gaitMode = _mode;
_gaitInit = true;
}
switch (_mode)
{
case LocomotionMode.Idle:
break; // SetMode(Idle) 已停;保持
case LocomotionMode.Patrol:
TickPatrol();
break;
case LocomotionMode.Face:
_enemy.StopMovement();
_enemy.FaceTarget(_facePoint);
break;
case LocomotionMode.Approach:
if (_approachTarget != null) _enemy.MoveTo(_approachTarget.position);
break;
}
}
private void TickPatrol()
{
switch (_patrolStrategy)
{
case PatrolStrategy.Wander:
if (_enemy.Nav != null && !_enemy.Nav.IsMoving) _enemy.Nav.WalkToRandom();
break;
case PatrolStrategy.Pace: // 撞墙/悬崖翻向的来回踱步(速度驱动,不依赖 nav)
var mv = _enemy.Movement;
if (mv != null && (mv.IsWallAhead || mv.IsLedgeAhead)) _paceDir = -_paceDir;
_enemy.MoveInDirection(_paceDir);
break;
case PatrolStrategy.Waypoints:
if (_waypoints == null || _waypoints.Length == 0)
{
if (!_warnedNoWaypoints)
{
Debug.LogWarning("[EnemyLocomotion] PatrolStrategy=Waypoints 但未配置 _waypoints,无法巡逻。", this);
_warnedNoWaypoints = true;
}
break;
}
// 仅首次或"已到达当前路点"时推进并寻路——不依赖 IsMoving
// PathBerserker2d 寻路跨帧异步,IsMoving 在寻路计算期间为 false,会误触发每帧推进)。
if (_wpIndex < 0 ||
Vector2.Distance(_enemy.transform.position, _waypoints[_wpIndex].position) <= _waypointArriveRadius)
{
AdvanceWaypoint();
_enemy.MoveTo(_waypoints[_wpIndex].position);
}
break;
}
}
private void AdvanceWaypoint()
{
if (_pingPong)
{
int next = _wpIndex + _wpDir;
if (next < 0 || next >= _waypoints.Length) { _wpDir = -_wpDir; next = _wpIndex + _wpDir; }
_wpIndex = Mathf.Clamp(next, 0, _waypoints.Length - 1);
}
else
{
_wpIndex = (_wpIndex + 1) % _waypoints.Length;
}
}
}
}