- 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.
141 lines
5.8 KiB
C#
141 lines
5.8 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:追击玩家(三阶段视线丢失模型)。
|
||
///
|
||
/// <list type="bullet">
|
||
/// <item><b>Tracking</b>:持有视线,以奔跑速度追击并更新 LastKnownPlayerPosition。</item>
|
||
/// <item><b>Searching</b>:视线丢失超过 LostSightBuffer(~0.3s)后进入。速度降低,向最后可见位置行进;若恢复视线即回到 Tracking。</item>
|
||
/// <item><b>Lost</b>:Searching 持续超过 LoseLinkTimeout → 返回 Failure,让 BD 树切入 BD_InvestigateLastKnown。</item>
|
||
/// </list>
|
||
///
|
||
/// 短暂遮挡(如绕过柱子)在 LostSightBuffer 内不会改变速度,避免追击手感割裂。
|
||
/// </summary>
|
||
[TaskName("Chase Player")]
|
||
[TaskCategory("BaseGames/Enemy/Movement")]
|
||
[TaskDescription("三段式追击(Tracking→Searching→Lost);丢失视线后进入缓冲期")]
|
||
public sealed class BD_ChasePlayer : Action
|
||
{
|
||
[Header("距离限制")]
|
||
[Tooltip("追击距离上限(m);0 = 使用 EnemyStatsSO.MaxChaseDistance")]
|
||
[SerializeField] [Min(0f)] private float m_MaxChaseDistance = 0f;
|
||
|
||
[Header("视线丢失")]
|
||
[Tooltip("视线丢失判定超时(s);0 = 使用 EnemyStatsSO.LoseLinkTimeout")]
|
||
[SerializeField] [Min(0f)] private float m_LoseLinkTimeout = 0f;
|
||
|
||
[Tooltip("视线刚丢失后的缓冲期(s):此期间保持追击速度,短暂遮挡不触发搜索模式")]
|
||
[SerializeField] [Min(0f)] private float m_LostSightBuffer = 0.3f;
|
||
|
||
[Header("路径规划")]
|
||
[Tooltip("路径重规划阈值(m):玩家移动超过此距离后才重新规划,避免每帧请求")]
|
||
[SerializeField] [Min(0.1f)] private float m_ReplanThreshold = 1.5f;
|
||
|
||
[Header("搜索阶段")]
|
||
[Tooltip("Searching 阶段速度倍率(相对于行走速度;<1 = 减速搜索)")]
|
||
[SerializeField] [UnityEngine.Range(0.3f, 1.5f)] private float m_SearchSpeedMultiplier = 0.7f;
|
||
|
||
private enum ChaseSubState { Tracking, Searching }
|
||
|
||
private EnemyBase _enemy;
|
||
private float _losTimer;
|
||
private Vector2 _lastReplanPos;
|
||
private ChaseSubState _subState;
|
||
|
||
public override void OnAwake() => _enemy = gameObject.GetComponent<EnemyBase>();
|
||
|
||
public override void OnStart()
|
||
{
|
||
_enemy.SetAiPhase(AiPhase.Chase);
|
||
_losTimer = 0f;
|
||
_lastReplanPos = Vector2.positiveInfinity;
|
||
_subState = ChaseSubState.Tracking;
|
||
|
||
float runSpeed = _enemy.Stats?.RunSpeed ?? 4f;
|
||
_enemy.Nav?.SetSpeed(runSpeed);
|
||
|
||
var ac = _enemy.AnimConfig;
|
||
if (_enemy.Animancer != null && ac?.Run != null)
|
||
_enemy.Animancer.Play(ac.Run);
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
if (_enemy == null || _enemy.PlayerTransform == null)
|
||
return TaskStatus.Failure;
|
||
|
||
float maxDist = m_MaxChaseDistance > 0f ? m_MaxChaseDistance : (_enemy.Stats?.MaxChaseDistance ?? 15f);
|
||
float loseTime = m_LoseLinkTimeout > 0f ? m_LoseLinkTimeout : (_enemy.Stats?.LoseLinkTimeout ?? 2f);
|
||
|
||
// 超出最大追击距离 → 放弃追击
|
||
if (_enemy.Stats != null && _enemy.Stats.SqrDistanceToPlayer > maxDist * maxDist)
|
||
return TaskStatus.Failure;
|
||
|
||
Vector2 playerPos = _enemy.PlayerTransform.position;
|
||
|
||
if (_enemy.IsPlayerVisible())
|
||
{
|
||
// 视线恢复:Searching → Tracking,恢复奔跑速度
|
||
if (_subState == ChaseSubState.Searching)
|
||
{
|
||
_subState = ChaseSubState.Tracking;
|
||
_enemy.Nav?.SetSpeed(_enemy.Stats?.RunSpeed ?? 4f);
|
||
}
|
||
_losTimer = 0f;
|
||
_enemy.LastKnownPlayerPosition = playerPos;
|
||
}
|
||
else
|
||
{
|
||
_losTimer += Time.deltaTime;
|
||
|
||
if (_subState == ChaseSubState.Tracking)
|
||
{
|
||
// 缓冲期结束 → 切入 Searching:减速并向最后可见位置行进
|
||
if (_losTimer >= m_LostSightBuffer)
|
||
{
|
||
_subState = ChaseSubState.Searching;
|
||
float searchSpeed = (_enemy.Stats?.WalkSpeed ?? 2f) * m_SearchSpeedMultiplier;
|
||
_enemy.Nav?.SetSpeed(searchSpeed);
|
||
_enemy.MoveTo(_enemy.LastKnownPlayerPosition);
|
||
_lastReplanPos = _enemy.LastKnownPlayerPosition;
|
||
}
|
||
}
|
||
else // Searching
|
||
{
|
||
if (_losTimer >= loseTime)
|
||
return TaskStatus.Failure; // 搜索超时 → 进入 BD_InvestigateLastKnown
|
||
}
|
||
}
|
||
|
||
// Tracking 阶段按阈值重规划路径
|
||
if (_subState == ChaseSubState.Tracking)
|
||
{
|
||
float sqrReplan = m_ReplanThreshold * m_ReplanThreshold;
|
||
if ((playerPos - _lastReplanPos).sqrMagnitude > sqrReplan)
|
||
{
|
||
_enemy.MoveTo(playerPos);
|
||
_lastReplanPos = playerPos;
|
||
}
|
||
}
|
||
|
||
_enemy.FacePlayer();
|
||
return TaskStatus.Running;
|
||
}
|
||
|
||
public override void OnEnd()
|
||
{
|
||
float walkSpeed = _enemy?.Stats?.WalkSpeed ?? 2f;
|
||
_enemy?.Nav?.SetSpeed(walkSpeed);
|
||
_enemy?.StopMovement();
|
||
_subState = ChaseSubState.Tracking; // 重置子状态,防止下次激活时以错误状态重入
|
||
}
|
||
}
|
||
}
|
||
#endif
|