158 lines
6.3 KiB
C#
158 lines
6.3 KiB
C#
using UnityEngine;
|
||
using BaseGames.Enemies;
|
||
|
||
namespace BaseGames.Enemies.Navigation
|
||
{
|
||
/// <summary>
|
||
/// 飞行单位直线导航代理,实现 <see cref="IEnemyNavigator"/>。
|
||
/// 不依赖寻路网格,直接通过 Rigidbody2D.MovePosition 向目标点直飞。
|
||
///
|
||
/// 特性:
|
||
/// <list type="bullet">
|
||
/// <item>空闲时施加正弦波形悬停偏移,产生飘浮感。</item>
|
||
/// <item>飞行单位直飞,无地形阻挡概念,<see cref="IsBlocked"/> 恒为 false。</item>
|
||
/// </list>
|
||
///
|
||
/// 使用方式:挂载到飞行怪 Prefab 根节点,替代 EnemyNavAgent;
|
||
/// <see cref="EnemyBase.Awake"/> 的 GetComponent<IEnemyNavigator>() 自动发现此组件。
|
||
/// </summary>
|
||
[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<Rigidbody2D>();
|
||
_movement = GetComponent<EnemyMovement>();
|
||
_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);
|
||
}
|
||
}
|
||
}
|