Files
zeling_v2/Assets/_Game/Scripts/Enemies/AI/BD_PatrolWaypoints.cs
Joywayer a1b4e629aa feat: Implement Room Streaming System
- Add RoomStreamingManager to manage room loading and unloading based on player proximity.
- Create StreamingBudgetConfigSO for memory and performance budgeting of the streaming system.
- Introduce TransitionDirector to handle seamless and atmospheric fade transitions between rooms.
- Develop WorldGraph to represent room connectivity and facilitate neighbor queries and distance calculations.
- Implement RoomNode and RoomEdge classes to structure room data and connections.
2026-05-23 19:10:29 +08:00

186 lines
7.4 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.
#if GRAPH_DESIGNER
using UnityEngine;
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
using BaseGames.Enemies;
namespace BaseGames.Enemies.AI
{
/// <summary>
/// BD Action按预设路点顺序巡逻支持 Transform 引用或内联 Vector2 坐标两种模式)。
///
/// <list type="bullet">
/// <item><b>m_WaypointsTransform[]</b>:拖入场景中的路点对象;适合动态路点(可在运行时移动)。</item>
/// <item><b>m_InlineWaypointsVector2[]</b>:直接填写世界坐标;无需在场景中放置对象,编辑器中以 Gizmo 可视化路径。</item>
/// <item>两者同时设置时 m_Waypoints 优先。</item>
/// </list>
///
/// 到达最后一个路点后可循环Loop或往返PingPong
/// 与 PathBerserker2d 集成:通过 IPathAgent.RequestMoveTo 导航,支持跨平台跳跃 NavLink。
/// </summary>
[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("内联路点坐标(世界空间 Vector2m_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<EnemyBase>();
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