Files
zeling_v2/Assets/_Game/Scripts/World/Streaming/RoomHandle.cs
Joywayer 520f84999b Add enemy respawner and related components for room lifecycle management
- 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.
2026-05-23 21:23:09 +08:00

302 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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、暂停 IRoomLifecycleAI、音效等</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);
// 分帧激活 IRoomLifecycleAI、特效等
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}]";
}
}