#if GRAPH_DESIGNER using UnityEngine; using Opsive.BehaviorDesigner.Runtime.Tasks; using Opsive.BehaviorDesigner.Runtime.Tasks.Actions; using BaseGames.Enemies; namespace BaseGames.Enemies.AI { /// /// BD Action:按预设路点顺序巡逻(支持 Transform 引用或内联 Vector2 坐标两种模式)。 /// /// /// m_Waypoints(Transform[]):拖入场景中的路点对象;适合动态路点(可在运行时移动)。 /// m_InlineWaypoints(Vector2[]):直接填写世界坐标;无需在场景中放置对象,编辑器中以 Gizmo 可视化路径。 /// 两者同时设置时 m_Waypoints 优先。 /// /// /// 到达最后一个路点后可循环(Loop)或往返(PingPong)。 /// 与 PathBerserker2d 集成:通过 IPathAgent.RequestMoveTo 导航,支持跨平台跳跃 NavLink。 /// [TaskName("Patrol (Waypoints)")] [TaskCategory("BaseGames/Enemy/Movement")] [TaskDescription("按预设路点顺序巡逻;支持 Transform 引用或内联 Vector2 坐标,可循环或折返")] public sealed class BD_PatrolWaypoints : Action { [Tooltip("路点列表(世界空间 Transform);与 m_InlineWaypoints 同时设置时此项优先")] [SerializeField] private Transform[] m_Waypoints; [Tooltip("内联路点坐标(世界空间 Vector2);m_Waypoints 为空时使用;在 Scene 视图中以绿色 Gizmo 可视化")] [SerializeField] private Vector2[] m_InlineWaypoints; [Tooltip("到达路点的判定半径(m)")] [SerializeField] private float m_ArriveRadius = 0.3f; [Tooltip("true = 往返; false = 循环")] [SerializeField] private bool m_PingPong = false; [Tooltip("每个路点到达后等待时长(s)")] [SerializeField] private float m_WaitAtWaypoint = 0f; private EnemyBase _enemy; private int _index = 0; private int _dir = 1; private float _waitTimer = 0f; private bool _waiting = false; // ── 统一路点访问 ──────────────────────────────────────────────────── private int WaypointCount => m_Waypoints != null && m_Waypoints.Length > 0 ? m_Waypoints.Length : m_InlineWaypoints?.Length ?? 0; private Vector2 GetWaypoint(int index) { if (m_Waypoints != null && m_Waypoints.Length > 0) { var t = m_Waypoints[index]; return t != null ? (Vector2)t.position : (Vector2)transform.position; } return m_InlineWaypoints != null && index < m_InlineWaypoints.Length ? m_InlineWaypoints[index] : (Vector2)transform.position; } // ── BD 生命周期 ───────────────────────────────────────────────────── public override void OnAwake() => _enemy = gameObject.GetComponent(); public override void OnStart() { if (WaypointCount == 0) return; _waiting = false; _waitTimer = 0f; _enemy?.SetAiPhase(AiPhase.Patrol); RequestCurrent(); } public override TaskStatus OnUpdate() { if (_enemy == null || WaypointCount == 0) return TaskStatus.Failure; if (_waiting) { _waitTimer -= Time.deltaTime; if (_waitTimer > 0f) return TaskStatus.Running; _waiting = false; Advance(); RequestCurrent(); } else { Vector2 wp = GetWaypoint(_index); float sqrDist = ((Vector2)_enemy.transform.position - wp).sqrMagnitude; if (sqrDist <= m_ArriveRadius * m_ArriveRadius) { if (m_WaitAtWaypoint > 0f) { _waiting = true; _waitTimer = m_WaitAtWaypoint; _enemy.StopMovement(); } else { Advance(); RequestCurrent(); } } else { _enemy.MoveTo(wp); _enemy.Movement?.FaceTarget(wp); } } return TaskStatus.Running; } public override void OnEnd() => _enemy?.StopMovement(); // ── 内部辅助 ──────────────────────────────────────────────────────── private void RequestCurrent() { if (WaypointCount == 0) return; _enemy.MoveTo(GetWaypoint(_index)); } private void Advance() { if (WaypointCount <= 1) return; if (m_PingPong) { _index += _dir; if (_index >= WaypointCount) { _index = WaypointCount - 2; _dir = -1; } else if (_index < 0) { _index = 1; _dir = 1; } } else { _index = (_index + 1) % WaypointCount; } } // ── Gizmo 可视化(仅 m_InlineWaypoints 模式)──────────────────────── #if UNITY_EDITOR private new void OnDrawGizmos() { if (m_InlineWaypoints == null || m_InlineWaypoints.Length < 1) return; // m_Waypoints 存在时不绘制,避免与场景路点重叠 if (m_Waypoints != null && m_Waypoints.Length > 0) return; Gizmos.color = new Color(0.2f, 0.85f, 0.2f, 0.8f); for (int i = 0; i < m_InlineWaypoints.Length; i++) { Gizmos.DrawWireSphere(m_InlineWaypoints[i], m_ArriveRadius); if (i < m_InlineWaypoints.Length - 1) Gizmos.DrawLine(m_InlineWaypoints[i], m_InlineWaypoints[i + 1]); } // PingPong 时也画返回线;Loop 时画首尾连接线 if (m_InlineWaypoints.Length >= 2) { if (m_PingPong) { // 往返:虚线效果(半透明线) Gizmos.color = new Color(0.2f, 0.85f, 0.2f, 0.35f); Gizmos.DrawLine(m_InlineWaypoints[m_InlineWaypoints.Length - 1], m_InlineWaypoints[0]); } else { // 循环:画首尾连接 Gizmos.color = new Color(0.2f, 0.85f, 0.2f, 0.5f); Gizmos.DrawLine(m_InlineWaypoints[m_InlineWaypoints.Length - 1], m_InlineWaypoints[0]); } } // 编辑器标签(路点序号) UnityEditor.Handles.color = new Color(0.2f, 0.85f, 0.2f, 1f); for (int i = 0; i < m_InlineWaypoints.Length; i++) UnityEditor.Handles.Label(m_InlineWaypoints[i] + Vector2.up * 0.25f, $"WP{i}"); } #endif } } #endif