Files
zeling_v2/Assets/_Game/Scripts/World/RoomController.cs
Joywayer a1b4e629aa 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.
2026-05-23 19:10:29 +08:00

136 lines
5.8 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 BaseGames.Camera;
using BaseGames.Core;
using UnityEngine;
namespace BaseGames.World
{
/// <summary>
/// 房间控制器。挂在每个房间场景的 [RoomRoot] 下。
/// <para>
/// 在流式加载模式下(<see cref="IRoomStreamingManager"/> 已注册Start() 将自身注册到
/// 流式管理器,由管理器控制相机初始化时机,避免 Dormant 房间抢占相机。
/// 非流式模式(无管理器注册)则退回原有行为,在 Start() 中立即初始化相机。
/// </para>
/// 支持房间内存在多个 CameraArea 的情况:动态检测玩家位于哪个触发区域,
/// 无匹配时回退到场景内第一个 CameraArea。
/// </summary>
public class RoomController : MonoBehaviour, IRoomLifecycle
{
[SerializeField] private string _roomId;
[SerializeField] private PlayerSpawnPoint[] _spawnPoints;
[Tooltip("(可选)显式指定此房间的基线相机区域。\n" +
"设置后将优先使用此区域作为房间基准,而非动态检测玩家所在触发区域。\n" +
"通常由 SceneObjectPlacerTool 自动赋值,也可手动拖入。")]
[SerializeField] private CameraArea _cameraArea;
public string RoomId => _roomId;
private void Start()
{
// 流式模式:注册到管理器,由管理器控制相机初始化时机(避免 Dormant 房间抢占相机)
var streaming = ServiceLocator.GetOrDefault<IRoomStreamingManager>();
if (streaming != null)
{
streaming.RegisterRoomController(this);
return;
}
// 非流式模式(管理器未注册):立即初始化相机,保持原有行为
SetupCamera();
}
// ── IRoomLifecycle ────────────────────────────────────────────────────────
/// <summary>
/// 房间进入休眠时由 <see cref="Streaming.RoomHandle"/> 调用。
/// 相机无需额外操作,视觉隐藏由 RoomHandle 批量关闭 Renderer 完成。
/// </summary>
public void OnRoomDormant() { }
/// <summary>
/// 房间被激活时由 <see cref="Streaming.RoomHandle"/> 调用,重新初始化相机区域。
/// </summary>
public void OnRoomActivate(SpawnContext context)
{
SetupCamera();
}
// ── 相机初始化 ────────────────────────────────────────────────────────────
/// <summary>初始化相机区域。可由 Start() 或 OnRoomActivate() 调用。</summary>
public void SetupCamera()
{
// 显式覆盖优先:直接使用编辑器/工具指定的基线区域
CameraArea area = _cameraArea != null ? _cameraArea : FindAreaForPlayer();
if (area != null)
// instantCut = true房间入口传送后相机硬切无混合拖影
ServiceLocator.GetOrDefault<ICameraService>()?.SwitchArea(area, 0, instantCut: true);
else
Debug.LogError($"[RoomController] {name}:未找到任何 CameraArea相机不会切换。");
}
/// <summary>
/// 查找玩家当前所在的 CameraArea。
/// 优先检测玩家位置与哪个 CameraTriggerZone 重叠,选取其中优先级最高者;
/// 无重叠时回退到场景内第一个 CameraArea。
/// </summary>
private CameraArea FindAreaForPlayer()
{
GameObject player = GameObject.FindWithTag("Player");
if (player != null)
{
Vector2 playerPos = player.transform.position;
// 使用 CameraTriggerZone 静态注册表,避免 FindObjectsOfType 全场景扫描
var zones = CameraTriggerZone.AllZones;
// 选取优先级最高的匹配区域(避免多区域重叠时选错基线)
CameraArea bestArea = null;
int bestPriority = int.MinValue;
foreach (var zone in zones)
{
if (zone == null) continue;
if (zone.ContainsPoint(playerPos))
{
var area = zone.GetComponentInParent<CameraArea>();
if (area != null && zone.Priority > bestPriority)
{
bestPriority = zone.Priority;
bestArea = area;
}
}
}
if (bestArea != null) return bestArea;
}
// 回退:返回场景中第一个 CameraArea
#if UNITY_6000_0_OR_NEWER
var fallback = Object.FindFirstObjectByType<CameraArea>();
#else
var fallback = Object.FindObjectOfType<CameraArea>();
#endif
if (fallback != null)
Debug.LogWarning($"[RoomController] {name}:玩家不在任何触发区域内,回退到 {fallback.name}。建议确认玩家出生位置与 CameraTriggerZone 的重叠关系。");
return fallback;
}
/// <summary>通过 transitionId 查找对应的出生点。</summary>
public PlayerSpawnPoint GetSpawnPoint(string transitionId)
{
if (_spawnPoints == null) return null;
foreach (var sp in _spawnPoints)
{
if (sp != null && sp.TransitionId == transitionId)
return sp;
}
// Fallback返回第一个出生点
return _spawnPoints.Length > 0 ? _spawnPoints[0] : null;
}
}
}