- Implemented EnemyRespawner to manage enemy spawning and respawning within rooms. - Added IRoomLifecycle interface for room activation and dormancy handling. - Created supporting classes and metadata for enemy perception and threat assessment. - Established streaming system components for room state management and transitions. - Added necessary metadata files for new scripts to ensure proper integration with Unity.
302 lines
13 KiB
C#
302 lines
13 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.AddressableAssets;
|
||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||
using UnityEngine.ResourceManagement.ResourceProviders;
|
||
using UnityEngine.SceneManagement;
|
||
|
||
namespace BaseGames.World.Streaming
|
||
{
|
||
/// <summary>
|
||
/// 单个房间的流式加载状态包装器。
|
||
/// <para>
|
||
/// 由 <see cref="RoomStreamingManager"/> 创建和管理,封装以下职责:
|
||
/// <list type="bullet">
|
||
/// <item>Addressables 加载 / 卸载句柄</item>
|
||
/// <item>Dormantize:批量关闭 Renderer、暂停 IRoomLifecycle(AI、音效等)</item>
|
||
/// <item>Activate:批量恢复渲染,分帧唤醒 IRoomLifecycle</item>
|
||
/// <item>Deactivate:玩家离开后关闭本房间内容</item>
|
||
/// </list>
|
||
/// </para>
|
||
/// </summary>
|
||
public class RoomHandle
|
||
{
|
||
// ── 公开属性 ──────────────────────────────────────────────────────────────
|
||
|
||
public string RoomId { get; }
|
||
public RoomState State { get; private set; } = RoomState.Unloaded;
|
||
public int EstimatedMemKB { get; set; }
|
||
|
||
/// <summary>上次处于 Active 状态的时间(Time.time)。用于 LRU 卸载优先级计算。</summary>
|
||
public float LastActiveTime { get; private set; }
|
||
|
||
/// <summary>加载完成后由 RoomController.Start() 通过 IRoomStreamingManager.RegisterRoomController 注入。</summary>
|
||
public RoomController RoomController { get; set; }
|
||
|
||
// ── 私有字段 ──────────────────────────────────────────────────────────────
|
||
|
||
private AsyncOperationHandle<SceneInstance> _sceneHandle;
|
||
|
||
// 缓存的场景组件集合,Dormantize 后可快速 Activate
|
||
private Renderer[] _renderers;
|
||
private IRoomLifecycle[] _lifecycles;
|
||
private AudioSource[] _audioSources;
|
||
private Light[] _lights;
|
||
private ParticleSystem[] _particleSystems;
|
||
|
||
// 分帧激活的协程宿主(由 RoomStreamingManager 提供)
|
||
private MonoBehaviour _coroutineRunner;
|
||
private int _lifecycleActivatePerFrame;
|
||
|
||
// ── 构造 ──────────────────────────────────────────────────────────────────
|
||
|
||
public RoomHandle(string roomId, MonoBehaviour coroutineRunner, int lifecycleActivatePerFrame)
|
||
{
|
||
RoomId = roomId;
|
||
_coroutineRunner = coroutineRunner;
|
||
_lifecycleActivatePerFrame = lifecycleActivatePerFrame;
|
||
}
|
||
|
||
// ── 加载 / 卸载 ───────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 开始后台异步加载(Additive)。
|
||
/// 加载完成后自动调用 <see cref="Dormantize"/> 将房间置于休眠状态。
|
||
/// </summary>
|
||
public IEnumerator LoadAsync(string addressableKey)
|
||
{
|
||
if (State != RoomState.Unloaded)
|
||
{
|
||
Debug.LogWarning($"[RoomHandle] {RoomId} 非 Unloaded 状态,忽略重复加载请求。");
|
||
yield break;
|
||
}
|
||
|
||
State = RoomState.Loading;
|
||
|
||
_sceneHandle = Addressables.LoadSceneAsync(addressableKey, LoadSceneMode.Additive);
|
||
|
||
while (!_sceneHandle.IsDone)
|
||
yield return null;
|
||
|
||
if (_sceneHandle.Status != AsyncOperationStatus.Succeeded)
|
||
{
|
||
Debug.LogError($"[RoomHandle] {RoomId} 加载失败:{addressableKey}");
|
||
State = RoomState.Unloaded;
|
||
yield break;
|
||
}
|
||
|
||
// 收集场景内组件引用,然后置于休眠
|
||
CollectSceneComponents();
|
||
Dormantize();
|
||
}
|
||
|
||
/// <summary>异步卸载本房间。完成后 State → Unloaded。</summary>
|
||
public IEnumerator UnloadAsync()
|
||
{
|
||
if (State == RoomState.Unloaded || State == RoomState.Unloading || !_sceneHandle.IsValid()) yield break;
|
||
|
||
// 若正处于加载中,等待加载完成后再卸载(Addressables 不支持对未完成的句柄直接卸载)
|
||
if (State == RoomState.Loading)
|
||
{
|
||
while (!_sceneHandle.IsDone)
|
||
yield return null;
|
||
}
|
||
|
||
// Active 状态先走 Deactivate,再设置 Unloading
|
||
bool needsDeactivate = State == RoomState.Active ||
|
||
State == RoomState.Activating ||
|
||
State == RoomState.Cooling;
|
||
if (needsDeactivate)
|
||
Deactivate();
|
||
|
||
State = RoomState.Unloading;
|
||
|
||
var op = Addressables.UnloadSceneAsync(_sceneHandle);
|
||
yield return op;
|
||
|
||
_renderers = null;
|
||
_lifecycles = null;
|
||
_audioSources = null;
|
||
_lights = null;
|
||
_particleSystems = null;
|
||
RoomController = null;
|
||
State = RoomState.Unloaded;
|
||
}
|
||
|
||
// ── Dormantize ────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 将已加载的房间置于休眠状态。
|
||
/// 关闭所有 Renderer 和 AudioSource,通知所有 IRoomLifecycle 组件进入休眠。
|
||
/// 场景内 GameObject 保持激活(便于快速恢复),但不消耗渲染和 AI 开销。
|
||
/// </summary>
|
||
public void Dormantize()
|
||
{
|
||
// 允许从 Loading(初次加载完成后)、Active、Activating、Cooling 进入
|
||
if (State != RoomState.Loading &&
|
||
State != RoomState.Active &&
|
||
State != RoomState.Activating &&
|
||
State != RoomState.Cooling)
|
||
{
|
||
Debug.LogWarning($"[RoomHandle] {RoomId} 在 {State} 状态下调用 Dormantize,忽略。");
|
||
return;
|
||
}
|
||
|
||
if (_renderers != null)
|
||
foreach (var r in _renderers) if (r != null) r.enabled = false;
|
||
|
||
if (_audioSources != null)
|
||
foreach (var a in _audioSources) if (a != null) { a.Stop(); a.enabled = false; }
|
||
|
||
if (_lights != null)
|
||
foreach (var l in _lights) if (l != null) l.enabled = false;
|
||
|
||
if (_particleSystems != null)
|
||
foreach (var ps in _particleSystems) if (ps != null) ps.Pause();
|
||
|
||
if (_lifecycles != null)
|
||
foreach (var l in _lifecycles) l?.OnRoomDormant();
|
||
|
||
State = RoomState.Dormant;
|
||
}
|
||
|
||
// ── Activate ──────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 激活此房间(Dormant → Active)。
|
||
/// 恢复 Renderer、AudioSource,分帧唤醒 IRoomLifecycle,并设置玩家出生点。
|
||
/// 目标房间必须已处于 <see cref="RoomState.Dormant"/> 状态。
|
||
/// </summary>
|
||
public IEnumerator Activate(SpawnContext context)
|
||
{
|
||
if (State != RoomState.Dormant)
|
||
{
|
||
Debug.LogError($"[RoomHandle] {RoomId} 不是 Dormant 状态(当前:{State}),无法激活。");
|
||
yield break;
|
||
}
|
||
|
||
State = RoomState.Activating;
|
||
|
||
// 先恢复渲染(让玩家立刻能看到新房间)
|
||
if (_renderers != null)
|
||
foreach (var r in _renderers) if (r != null) r.enabled = true;
|
||
|
||
if (_audioSources != null)
|
||
foreach (var a in _audioSources) if (a != null) a.enabled = true;
|
||
|
||
if (_lights != null)
|
||
foreach (var l in _lights) if (l != null) l.enabled = true;
|
||
|
||
if (_particleSystems != null)
|
||
foreach (var ps in _particleSystems) if (ps != null) ps.Play();
|
||
|
||
// 相机 + 出生点(由 RoomController 处理)
|
||
RoomController?.OnRoomActivate(context);
|
||
|
||
// 分帧激活 IRoomLifecycle(AI、特效等)
|
||
yield return _coroutineRunner.StartCoroutine(ActivateLifecyclesGradually(context));
|
||
|
||
State = RoomState.Active;
|
||
LastActiveTime = Time.time;
|
||
}
|
||
|
||
/// <summary>将 IRoomLifecycle 组件分帧激活,避免单帧 CPU 峰值。</summary>
|
||
private IEnumerator ActivateLifecyclesGradually(SpawnContext context)
|
||
{
|
||
if (_lifecycles == null) yield break;
|
||
|
||
int activatedThisFrame = 0;
|
||
foreach (var lc in _lifecycles)
|
||
{
|
||
if (lc == null) continue;
|
||
lc.OnRoomActivate(context);
|
||
activatedThisFrame++;
|
||
if (activatedThisFrame >= _lifecycleActivatePerFrame)
|
||
{
|
||
activatedThisFrame = 0;
|
||
yield return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── Deactivate ────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 玩家离开后关闭此房间(Active / Activating → Dormant)。
|
||
/// 与 <see cref="Dormantize"/> 相同,但语义上表示从 Active 退出。
|
||
/// </summary>
|
||
public void Deactivate()
|
||
{
|
||
if (State != RoomState.Active && State != RoomState.Activating && State != RoomState.Cooling)
|
||
{
|
||
Debug.LogWarning($"[RoomHandle] {RoomId} 在 {State} 状态下调用 Deactivate,忽略。");
|
||
return;
|
||
}
|
||
|
||
Dormantize();
|
||
}
|
||
|
||
// ── 冷却 ──────────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 标记为冷却中,同时休眠渲染和 AI 开销。
|
||
/// 玩家离开后不立即卸载,等待冷却计时结束后再卸载。
|
||
/// </summary>
|
||
public void BeginCooling()
|
||
{
|
||
if (State != RoomState.Active && State != RoomState.Activating) return;
|
||
// 先休眠渲染和 AI(与 Dormantize 相同,但最终 State 设为 Cooling 而非 Dormant)
|
||
Dormantize();
|
||
State = RoomState.Cooling;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 冷却期间玩家返回,将状态从 Cooling 重置为 Dormant。
|
||
/// 渲染已在 BeginCooling 时关闭,无需重复操作。
|
||
/// </summary>
|
||
public void ResetToDormant()
|
||
{
|
||
if (State == RoomState.Cooling)
|
||
State = RoomState.Dormant;
|
||
}
|
||
|
||
// ── 内部工具 ──────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 收集加载完成的场景中所有需要被管理的组件引用。
|
||
/// 在 LoadAsync 完成后、Dormantize 之前调用一次。
|
||
/// </summary>
|
||
private void CollectSceneComponents()
|
||
{
|
||
if (!_sceneHandle.IsValid()) return;
|
||
|
||
Scene scene = _sceneHandle.Result.Scene;
|
||
GameObject[] roots = scene.GetRootGameObjects();
|
||
|
||
var rendererList = new List<Renderer>();
|
||
var lifecycleList = new List<IRoomLifecycle>();
|
||
var audioSourceList = new List<AudioSource>();
|
||
var lightList = new List<Light>();
|
||
var particleList = new List<ParticleSystem>();
|
||
|
||
foreach (var root in roots)
|
||
{
|
||
rendererList.AddRange(root.GetComponentsInChildren<Renderer>(true));
|
||
lifecycleList.AddRange(root.GetComponentsInChildren<IRoomLifecycle>(true));
|
||
audioSourceList.AddRange(root.GetComponentsInChildren<AudioSource>(true));
|
||
lightList.AddRange(root.GetComponentsInChildren<Light>(true));
|
||
particleList.AddRange(root.GetComponentsInChildren<ParticleSystem>(true));
|
||
}
|
||
|
||
_renderers = rendererList.ToArray();
|
||
_lifecycles = lifecycleList.ToArray();
|
||
_audioSources = audioSourceList.ToArray();
|
||
_lights = lightList.ToArray();
|
||
_particleSystems = particleList.ToArray();
|
||
}
|
||
|
||
public override string ToString() => $"RoomHandle[{RoomId}, {State}]";
|
||
}
|
||
}
|