using UnityEngine; using BaseGames.Enemies; namespace BaseGames.Enemies.Navigation { /// /// 飞行单位直线导航代理,实现 。 /// 不依赖寻路网格,直接通过 Rigidbody2D.MovePosition 向目标点直飞。 /// /// 特性: /// /// 空闲时施加正弦波形悬停偏移,产生飘浮感。 /// 飞行单位直飞,无地形阻挡概念, 恒为 false。 /// /// /// 使用方式:挂载到飞行怪 Prefab 根节点,替代 EnemyNavAgent; /// 的 GetComponent<IEnemyNavigator>() 自动发现此组件。 /// [RequireComponent(typeof(Rigidbody2D))] public sealed class FlyingNavigator : MonoBehaviour, IEnemyNavigator { [Header("移动参数")] [Tooltip("直飞速度(m/s)")] [SerializeField] private float _moveSpeed = 3f; [Tooltip("到达目标的判定距离(m)")] [SerializeField] private float _stoppingDist = 0.15f; [Header("游荡参数(TryPickWanderPoint)")] [Tooltip("随机游荡目标点距当前位置的最大半径(m)。")] [Min(0.1f)] [SerializeField] private float _randomWalkRadius = 3f; [Header("悬停参数(空闲 / 无目标时)")] [Tooltip("悬停横向摆动速度(m/s)")] [SerializeField] private float _hoverLateralSpeed = 0.5f; [Tooltip("悬停纵向正弦频率(Hz)")] [SerializeField] private float _hoverSineFrequency = 1.2f; [Tooltip("悬停纵向正弦振幅(m/s)")] [SerializeField] private float _hoverSineAmplitude = 0.25f; [Tooltip("横向方向翻转周期(s)")] [SerializeField] private float _hoverFlipInterval = 1.5f; // ── 状态 ─────────────────────────────────────────────────────── private Rigidbody2D _rb; private EnemyMovement _movement; private Vector2 _target; private bool _hasTarget; private bool _arrived; private float _hoverTimer; private float _hoverFlipTimer; private float _hoverDir = 1f; // ── IEnemyNavigator 属性 ──────────────────────────────────────── public bool IsMoving => _hasTarget && !_arrived; public bool HasArrived => _arrived; // 飞行单位直飞,无地形阻挡概念。 public bool IsBlocked => false; // ── Unity 生命周期 ───────────────────────────────────────────── private void Awake() { _rb = GetComponent(); _movement = GetComponent(); _rb.gravityScale = 0f; _rb.constraints = RigidbodyConstraints2D.FreezeRotation; } private void FixedUpdate() { if (_hasTarget && !_arrived) UpdateChase(); else UpdateHover(); } // ── IEnemyNavigator 方法 ──────────────────────────────────────── public void MoveTowards(Vector2 target) { // 幂等:同目标重复调用不重置状态(避免每帧重发目标导致已到达状态被反复清空)。 if (_hasTarget && (target - _target).sqrMagnitude <= 1e-6f) return; _target = target; _hasTarget = true; _arrived = false; } public void Stop() { _hasTarget = false; _arrived = false; _rb.velocity = Vector2.zero; } public void SetSpeed(float speed) => _moveSpeed = speed; public bool TryPickWanderPoint(out Vector2 point) { // 在当前位置随机偏移一个 2D 方向,供上层游荡状态调用;不主动发起移动。 Vector2 offset = Random.insideUnitCircle.normalized * _randomWalkRadius; point = (Vector2)transform.position + offset; return true; } // ── 内部移动逻辑 ─────────────────────────────────────────────── private void UpdateChase() { Vector2 myPos = _rb.position; Vector2 target = _target; float sqrDist = (target - myPos).sqrMagnitude; if (sqrDist <= _stoppingDist * _stoppingDist) { _rb.velocity = Vector2.zero; _arrived = true; return; } Vector2 newPos = Vector2.MoveTowards(myPos, target, _moveSpeed * Time.fixedDeltaTime); _rb.MovePosition(newPos); // 面向移动方向(通过 EnemyMovement 输入信号,保持 _facingDir 与动画系统同步) float dx = target.x - myPos.x; if (Mathf.Abs(dx) > 0.05f) { int dir = dx > 0f ? 1 : -1; if (_movement != null) { _movement.PendingInput.WantFace = true; _movement.PendingInput.FaceDir = dir; } else { // 降级:没有 EnemyMovement 时直接翻转(独立飞行单位) var s = transform.localScale; s.x = Mathf.Abs(s.x) * dir; transform.localScale = s; } } } private void UpdateHover() { _hoverFlipTimer += Time.fixedDeltaTime; if (_hoverFlipTimer >= _hoverFlipInterval) { _hoverFlipTimer = 0f; _hoverDir = -_hoverDir; } float sineY = Mathf.Sin(Time.time * _hoverSineFrequency * Mathf.PI * 2f) * _hoverSineAmplitude; _rb.velocity = new Vector2(_hoverDir * _hoverLateralSpeed, sineY); } } }