- 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.
186 lines
7.4 KiB
C#
186 lines
7.4 KiB
C#
#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_Waypoints(Transform[])</b>:拖入场景中的路点对象;适合动态路点(可在运行时移动)。</item>
|
||
/// <item><b>m_InlineWaypoints(Vector2[])</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("内联路点坐标(世界空间 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<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
|
||
|