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.
This commit is contained in:
184
Assets/_Game/Scripts/Enemies/AI/BD_InvestigateLastKnown.cs
Normal file
184
Assets/_Game/Scripts/Enemies/AI/BD_InvestigateLastKnown.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
#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:多步搜查(Navigate → LookAround → RandomWalk × N)。
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Navigate</b>:导航至最后可见位置。</item>
|
||||
/// <item><b>LookAround</b>:到达后原地环顾(播放 Look 动画,持续 LookAroundDuration)。</item>
|
||||
/// <item><b>WalkRandom</b>:向搜查半径内的随机点移动,重复 RandomStepCount 次。</item>
|
||||
/// <item>任意阶段重新发现玩家 → 返回 Failure,BD 树重入追击。</item>
|
||||
/// <item>所有步骤完成仍未发现 → 返回 Success,BD 树归位/恢复巡逻。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TaskName("Investigate Last Known Pos")]
|
||||
[TaskCategory("BaseGames/Enemy/Movement")]
|
||||
[TaskDescription("多步搜查:导航到最后已知位置 → 环顾四周 → 随机游走 N 次")]
|
||||
public sealed class BD_InvestigateLastKnown : Action
|
||||
{
|
||||
[Tooltip("到达搜查点后环顾的时长(s)")]
|
||||
[SerializeField] private float m_LookAroundDuration = 0.8f;
|
||||
|
||||
[Tooltip("随机行走步数(完成环顾后随机游荡的次数)")]
|
||||
[SerializeField] [Min(0)] private int m_RandomStepCount = 2;
|
||||
|
||||
[Tooltip("随机行走半径(m)")]
|
||||
[SerializeField] private float m_SearchRadius = 2.5f;
|
||||
|
||||
[Tooltip("到达目标点的判定半径(m)")]
|
||||
[SerializeField] private float m_ArriveRadius = 0.6f;
|
||||
|
||||
private enum InvestigateSubStep { Navigate, LookAround, WalkRandom }
|
||||
|
||||
private EnemyBase _enemy;
|
||||
private InvestigateSubStep _step;
|
||||
private float _stepTimer;
|
||||
private int _stepsRemaining;
|
||||
private Vector2 _randomTarget;
|
||||
private bool _pathFailed;
|
||||
private bool _subscribed;
|
||||
|
||||
public override void OnAwake() => _enemy = gameObject.GetComponent<EnemyBase>();
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy.SetAiPhase(AiPhase.Investigate);
|
||||
_step = InvestigateSubStep.Navigate;
|
||||
_stepTimer = 0f;
|
||||
_stepsRemaining = m_RandomStepCount;
|
||||
_pathFailed = false;
|
||||
|
||||
var ac = _enemy.AnimConfig;
|
||||
if (_enemy.Animancer != null)
|
||||
{
|
||||
var clip = ac?.Investigate ?? ac?.Walk;
|
||||
if (clip != null) _enemy.Animancer.Play(clip);
|
||||
}
|
||||
|
||||
if (!_subscribed && _enemy.Nav != null)
|
||||
{
|
||||
_enemy.Nav.OnNavPathFailed += HandlePathFailed;
|
||||
_subscribed = true;
|
||||
}
|
||||
|
||||
_enemy.MoveTo(_enemy.LastKnownPlayerPosition);
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
|
||||
if (_enemy.IsPlayerVisible()) return TaskStatus.Failure;
|
||||
|
||||
switch (_step)
|
||||
{
|
||||
case InvestigateSubStep.Navigate: return UpdateNavigate();
|
||||
case InvestigateSubStep.LookAround: return UpdateLookAround();
|
||||
case InvestigateSubStep.WalkRandom: return UpdateWalkRandom();
|
||||
default: return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEnd()
|
||||
{
|
||||
if (_subscribed && _enemy?.Nav != null)
|
||||
{
|
||||
_enemy.Nav.OnNavPathFailed -= HandlePathFailed;
|
||||
_subscribed = false;
|
||||
}
|
||||
_enemy?.StopMovement();
|
||||
}
|
||||
|
||||
// ── 阶段逻辑 ────────────────────────────────────────────────────
|
||||
private TaskStatus UpdateNavigate()
|
||||
{
|
||||
Vector2 self = _enemy.transform.position;
|
||||
bool arrived = _pathFailed ||
|
||||
(self - _enemy.LastKnownPlayerPosition).sqrMagnitude <= m_ArriveRadius * m_ArriveRadius;
|
||||
|
||||
if (!arrived) return TaskStatus.Running;
|
||||
|
||||
_enemy.StopMovement();
|
||||
EnterLookAround();
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
private TaskStatus UpdateLookAround()
|
||||
{
|
||||
_stepTimer += Time.deltaTime;
|
||||
if (_stepTimer < m_LookAroundDuration) return TaskStatus.Running;
|
||||
|
||||
if (_stepsRemaining > 0)
|
||||
EnterRandomWalk();
|
||||
else
|
||||
return TaskStatus.Success;
|
||||
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
private TaskStatus UpdateWalkRandom()
|
||||
{
|
||||
Vector2 self = _enemy.transform.position;
|
||||
bool arrived = _pathFailed ||
|
||||
(self - _randomTarget).sqrMagnitude <= m_ArriveRadius * m_ArriveRadius;
|
||||
|
||||
if (!arrived) return TaskStatus.Running;
|
||||
|
||||
_enemy.StopMovement();
|
||||
_stepsRemaining--;
|
||||
_pathFailed = false;
|
||||
|
||||
if (_stepsRemaining > 0)
|
||||
EnterRandomWalk();
|
||||
else
|
||||
return TaskStatus.Success;
|
||||
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
// ── 辅助方法 ────────────────────────────────────────────────────
|
||||
private void EnterLookAround()
|
||||
{
|
||||
_step = InvestigateSubStep.LookAround;
|
||||
_stepTimer = 0f;
|
||||
|
||||
var ac = _enemy.AnimConfig;
|
||||
if (_enemy.Animancer != null)
|
||||
{
|
||||
var clip = ac?.Investigate ?? ac?.Idle;
|
||||
if (clip != null) _enemy.Animancer.Play(clip);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnterRandomWalk()
|
||||
{
|
||||
_step = InvestigateSubStep.WalkRandom;
|
||||
_pathFailed = false;
|
||||
|
||||
Vector2 origin = _enemy.LastKnownPlayerPosition;
|
||||
// 横板地形中只在水平方向随机偏移,保留 origin.y;
|
||||
// PathBerserker2d 寻路负责处理跨平台的纵向路径规划。
|
||||
float dir = Random.value > 0.5f ? 1f : -1f;
|
||||
float dist = Random.Range(0.5f, m_SearchRadius);
|
||||
_randomTarget = new Vector2(origin.x + dir * dist, origin.y);
|
||||
|
||||
_enemy.MoveTo(_randomTarget);
|
||||
|
||||
var ac = _enemy.AnimConfig;
|
||||
if (_enemy.Animancer != null)
|
||||
{
|
||||
var clip = ac?.Walk;
|
||||
if (clip != null) _enemy.Animancer.Play(clip);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePathFailed() => _pathFailed = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user