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:
2026-05-23 19:10:29 +08:00
parent 81c326af53
commit a1b4e629aa
165 changed files with 7904 additions and 313 deletions

View File

@@ -0,0 +1,89 @@
using System.Collections.Generic;
using UnityEngine;
using Micosmo.SensorToolkit;
namespace BaseGames.Enemies.Perception
{
/// <summary>
/// 敌人感知 Hub架构 07_EnemyModule §9
/// 集中暴露挂载在敌人 Prefab 上的各种 SensorToolkit SensorBD 任务通过
/// 字符串槽位查询,避免在 BD 任务 Inspector 中拖具体 Sensor 引用。
///
/// 典型槽位命名约定:
/// - "aggro" : RangeSensor2D玩家入侵警戒圈
/// - "attack_melee" : RangeSensor2D近战触发距离
/// - "attack_range" : RangeSensor2D远程触发距离
/// - "los" : LOSSensor2D视线
/// - "wall_ahead" : RaySensor2D前方墙体检测
/// - "ledge" : RaySensor2D前方悬崖检测
/// </summary>
[DisallowMultipleComponent]
public sealed class EnemySensorHub : MonoBehaviour, IPerceptionSystem
{
[System.Serializable]
public struct SensorSlot
{
public string slotName;
public Sensor sensor;
}
[SerializeField] private SensorSlot[] _slots;
private Dictionary<string, Sensor> _map;
private void Awake()
{
_map = new Dictionary<string, Sensor>(_slots?.Length ?? 0);
if (_slots == null) return;
for (int i = 0; i < _slots.Length; i++)
{
var s = _slots[i];
if (s.sensor != null && !string.IsNullOrEmpty(s.slotName))
_map[s.slotName] = s.sensor;
}
}
public Sensor Get(string slotName)
{
if (_map == null || string.IsNullOrEmpty(slotName)) return null;
_map.TryGetValue(slotName, out var s);
return s;
}
public bool IsDetecting(string slotName, GameObject target)
{
var s = Get(slotName);
return s != null && target != null && s.IsDetected(target);
}
public bool HasAnyDetection(string slotName)
{
var s = Get(slotName);
if (s == null) return false;
foreach (var _ in s.Detections) return true;
return false;
}
public GameObject GetFirstDetection(string slotName)
{
var s = Get(slotName);
if (s == null) return null;
foreach (var go in s.Detections) return go;
return null;
}
/// <summary>
/// 暂停或恢复所有插槽的 Sensor。
/// 当敌人超出 QuotaManager 活跃范围时调用(关闭),归入活跃范围时恢复(开启)。
/// </summary>
public void SetSuspended(bool suspended)
{
if (_slots == null) return;
for (int i = 0; i < _slots.Length; i++)
{
var sensor = _slots[i].sensor;
if (sensor != null) sensor.enabled = !suspended;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58cb3ac0e49c151429cad39d3e164a3d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
using UnityEngine;
namespace BaseGames.Enemies.Perception
{
/// <summary>
/// 威胁评估器:在原始 LOS 结果上叠加感知反应延迟,使敌人不会瞬间发现玩家。
///
/// 工作原理:
/// <list type="bullet">
/// <item>读取 <see cref="EnemyBase.HasLineOfSight"/>(原始 LOSBatchLOSSystem 写入)。</item>
/// <item>连续感知到玩家超过 <see cref="reactionDelay"/> 秒后,<see cref="IsThreatDetected"/> 才变为 true。</item>
/// <item>一旦丢失 LOS<see cref="IsThreatDetected"/> 立即重置为 false保持对"躲起来"的快速响应)。</item>
/// </list>
///
/// 挂载:添加到与 <see cref="EnemyBase"/> 相同的 GameObject。
/// <see cref="EnemyBase.IsPlayerVisible()"/> 会自动路由至此组件(如果存在)。
/// </summary>
[RequireComponent(typeof(EnemyBase))]
public class EnemyThreatAssessor : MonoBehaviour
{
[Tooltip("持续感知此时长s后判定为威胁模拟敌人的反应时间")]
[SerializeField] [Min(0f)] private float reactionDelay = 0.15f;
private EnemyBase _enemy;
private float _losAccumulator;
/// <summary>是否已将玩家判定为当前威胁(含反应延迟)。</summary>
public bool IsThreatDetected { get; private set; }
private void Awake()
{
_enemy = GetComponent<EnemyBase>();
}
private void Update()
{
if (_enemy == null) return;
bool hasLos = _enemy.HasLineOfSight;
if (hasLos)
{
_losAccumulator += Time.deltaTime;
if (_losAccumulator >= reactionDelay)
IsThreatDetected = true;
}
else
{
_losAccumulator = 0f;
IsThreatDetected = false;
}
}
}
}

View File

@@ -0,0 +1,23 @@
using UnityEngine;
namespace BaseGames.Enemies.Perception
{
/// <summary>
/// 敌人感知系统接口。
/// EnemyBase 通过此接口与感知实现解耦支持运行时替换SensorToolkit / 自定义实现)。
/// </summary>
public interface IPerceptionSystem
{
/// <summary>指定槽位是否检测到任意目标。</summary>
bool HasAnyDetection(string slotName);
/// <summary>指定槽位是否正在检测 target 对象。</summary>
bool IsDetecting(string slotName, GameObject target);
/// <summary>返回指定槽位第一个检测到的对象,无检测则返回 null。</summary>
GameObject GetFirstDetection(string slotName);
/// <summary>暂停或恢复感知系统LOD / 超出活跃范围时调用)。</summary>
void SetSuspended(bool suspended);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8e0f4d9ac2fab74409dfa127cc6a67d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
namespace BaseGames.Enemies.Perception
{
/// <summary>
/// <see cref="EnemySensorHub"/> 槽位名称常量。
///
/// 统一定义字符串键,避免在 BD Task Inspector 和代码中散布魔法字符串。
/// Prefab 上 EnemySensorHub 组件的 slotName 字段必须与此处常量保持一致。
/// </summary>
public static class SensorSlotNames
{
/// <summary>
/// 警戒范围RangeSensor2D玩家进入此圈触发 Alert 阶段。
/// 通常半径大于攻击范围,小于视线检测范围。
/// </summary>
public const string Aggro = "aggro";
/// <summary>
/// 视线检测LOSSensor2D敌我之间无遮挡时持续为 true。
/// 由 BatchLOSSystem 批量计算BD_IsPlayerVisible 读取结果。
/// </summary>
public const string LOS = "los";
/// <summary>
/// 近战攻击范围RangeSensor2D玩家进入时触发近战攻击条件。
/// </summary>
public const string AttackMelee = "attack_melee";
/// <summary>
/// 远程攻击范围RangeSensor2D玩家进入时触发远程攻击条件。
/// </summary>
public const string AttackRange = "attack_range";
/// <summary>
/// 前方墙体RaySensor2D水平方向检测用于巡逻转向。
/// </summary>
public const string WallAhead = "wall_ahead";
/// <summary>
/// 前方悬崖RaySensor2D斜向下检测地面是否存在用于巡逻转向。
/// </summary>
public const string Ledge = "ledge";
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f449ccbbaeb13f468dd74ca202c4937
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: