场景流程
异步场景加载,支持加载画面、取消与叠加多场景。
一个可等待的场景转换 API · 自动加载画面、进度事件、协作式取消 · 内建叠加多场景分层 · 无操作替代:
NullSceneService
CGS 通过 ISceneService 在场景之间移动你的游戏——每一次"菜单到玩法再回到菜单"的转换都用这一个可等待的 API。你调用 await _scene.LoadAsync("Gameplay"),其余由服务协调:自动显示你的加载画面、把它在屏幕上保持足够长的时间以避免一帧闪烁、为你的进度条发布进度事件,并支持协作式取消(玩家按下 Esc,加载停止)。
同一个服务还处理叠加场景(additive scene)——加载到当前场景之上而不是替换它的场景,这正是叠一层 UI 场景或流式载入相邻区域的方式。Single 模式与叠加加载是同一个服务的两个表面;没有额外的设置。
功能概述
- 每次转换一个异步调用。
LoadAsync返回一个可await的Task<SceneLoadResult>。成功与失败以结果对象返回;取消与超时抛出OperationCanceledException。 - 自动加载画面。 在选项中传入一个面板,服务会在加载前压入、加载后弹出,并带可配置的最短显示时间。
- 进度事件。 服务在事件总线上发布加载开始/进度/完成/取消事件——正是淡出、淡入音乐的合适位置。
- 叠加分层。 在基础场景之上加载和卸载额外场景,检视已加载场景的完整列表,并选择 Unity 把哪一个当作活动场景。
获取服务
解析一次并缓存引用(绝不要在 Update 里):
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)]
public class MenuController : MonoBehaviour
{
private ISceneService _scene;
private void Awake()
{
_scene = ServiceLocator.Resolve<ISceneService>();
}
}
加载场景(Single 模式)
Single 模式加载会替换当前场景——标准的主菜单到玩法的转换。
Task<SceneLoadResult> LoadAsync(string sceneName,
SceneLoadOptions opts = default,
CancellationToken ct = default)
按名称加载场景。场景必须已在 Build Settings 中注册(File > Build Settings > Scenes in Build)。成功或出错时返回 SceneLoadResult;取消或超时抛出 OperationCanceledException。
Task<SceneLoadResult> ReloadCurrentAsync(SceneLoadOptions opts = default,
CancellationToken ct = default)
重新加载当前活动场景——方便检查点重生和调试重载。
选项(SceneLoadOptions)
| 字段 | 作用 |
|---|---|
LoadingScreenPanel | 可选面板(UI 框架的 IPanel),服务在加载开始时压入、结束时弹出。 |
MinDisplayDuration | 加载画面保持可见的最短秒数。防止加载在几帧内就完成时的突兀闪烁。 |
LoadTimeoutSeconds | 单次调用的超时覆盖。0(默认)使用服务级设置。到期时加载抛出内含 TimeoutException 的 OperationCanceledException。 |
结果(SceneLoadResult)
| 属性 | 含义 |
|---|---|
Succeeded | 场景正常激活时为 true。 |
Error | Succeeded 为 false 时捕获到的异常(例如,场景不在 Build Settings 中)。成功时为 null。 |
SceneName | 本次加载的目标场景。 |
TotalDuration | 端到端的加载耗时,TimeSpan 类型。 |
加载阶段与查询
一次加载会经过一系列你随时可以观察的阶段:
string ActiveSceneName { get; } // Active scene name; empty before the first load completes
bool IsLoading { get; } // true while any scene operation is in flight
LoadPhase CurrentPhase { get; } // Idle, Unloading, Loading, MinDisplayHold, Activating, Active
发布的事件
通过事件总线订阅:
| 事件 | 时机 | 典型用途 |
|---|---|---|
SceneLoadStarted(FromSceneName, ToSceneName) | 加载开始 | 淡出旧音乐 |
SceneLoadProgress(SceneName, Progress, Phase) | 进度更新,Progress 取值 0..1 | 驱动进度条 |
SceneLoadCompleted(FromSceneName, ToSceneName, TotalDuration) | 加载完成 | 淡入新音乐 |
SceneLoadCanceled(FromSceneName, AttemptedSceneName, PhaseAtCancel, ElapsedBeforeCancel, Cause) | 加载被取消或超时 | 干净地返回菜单 |
叠加场景
叠加加载把一个场景加入到已加载的场景中,而不是替换它们。用于玩法之上的常驻 UI 场景、流式载入的相邻区域,或一个共享的"managers"场景。
Task<SceneLoadResult> LoadAdditiveAsync(string sceneName,
SceneLoadOptions opts = default,
CancellationToken ct = default)
在基础场景之上叠加加载一个场景。活动场景不会改变——ActiveSceneName 仍然报告基础场景。加载一个已经叠加加载的场景是安全的无操作:服务记录一条警告并返回一个已完成的任务(没有引用计数——每个场景最多加载一次)。
Task UnloadAdditiveAsync(string sceneName, CancellationToken ct = default)
卸载一个叠加加载的场景。传入基础场景的名字,或一个未加载的名字,都是带警告的安全无操作——基础场景永远无法用这种方式移除(要替换它请用 LoadAsync)。
IReadOnlyList<string> ActiveSceneList { get; }
所有已加载的场景:基础场景在前,然后是按加载顺序排列的叠加场景。每次读取都返回一份新的快照,你可以随意保留或修改。
bool SetActiveScene(string sceneName)
告诉 Unity 哪个已加载的场景是"活动"场景——接收新实例化对象并决定光照设置的那个场景。该场景必须已出现在 ActiveSceneList 中;未加载时返回 false(带警告,不抛异常)。
值得了解的行为:
- 叠加加载跳过加载画面。 对叠加操作,
LoadingScreenPanel和MinDisplayDuration会被忽略(若你设置了它们会有一条日志消息)——叠加加载本应轻量、无缝。 - 同样的事件照发。 叠加加载和卸载发布与 Single 模式加载相同的
SceneLoadStarted/SceneLoadProgress/SceneLoadCompleted事件。区分方法:叠加操作期间ActiveSceneName不变。 - Single 模式加载会清空叠加列表。
LoadAsync完成时,Unity 已卸载之前的所有场景——服务把ActiveSceneList重置为只含新的基础场景。 - 仍然是同一时间一个操作。 叠加加载和卸载与 Single 模式加载共享同一个进行中门闩:
IsLoading为true时,启动另一个操作会被拒绝。
完整示例
using System;
using System.Threading;
using System.Threading.Tasks;
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)]
public class GameFlow : MonoBehaviour
{
private ISceneService _scene;
private IPanel _loadingScreen;
private CancellationTokenSource _menuCts;
private void Awake()
{
_scene = ServiceLocator.Resolve<ISceneService>();
_loadingScreen = GetComponent<IPanel>(); // Your loading screen panel
}
// --- Single-mode: menu -> gameplay with a loading screen ---
public async void OnPlayButtonClicked()
{
_menuCts = new CancellationTokenSource();
try
{
var opts = new SceneLoadOptions
{
LoadingScreenPanel = _loadingScreen,
MinDisplayDuration = 1.0f // At least 1 second on screen
};
var result = await _scene.LoadAsync("Gameplay", opts, _menuCts.Token);
if (!result.Succeeded)
Debug.LogError($"Scene load failed: {result.Error.Message}");
}
catch (OperationCanceledException)
{
Debug.Log("Scene load was canceled (player hit Esc)");
}
}
public void OnEscapePressed()
{
_menuCts?.Cancel(); // Cancel the in-flight load
}
// --- Additive: layer a HUD scene over gameplay ---
public async Task ShowHudAsync()
{
await _scene.LoadAdditiveAsync("HudOverlay");
Debug.Log(_scene.ActiveSceneName); // "Gameplay" — unchanged
Debug.Log(string.Join(", ", _scene.ActiveSceneList)); // "Gameplay, HudOverlay"
}
public async Task HideHudAsync()
{
await _scene.UnloadAdditiveAsync("HudOverlay");
}
}
关闭此服务
ServiceLocator.Replace<ISceneService>(new NullSceneService());
换成 null 实现后,每个加载调用都返回一个已完成的成功任务——没有实际加载、没有事件、没有加载面板。适用于无头测试或对玩法流程做 mock。它仍然尊重取消令牌(一个已取消的令牌产生一个已取消的任务)。
常见陷阱
-
仅限主线程。 只在主线程上调用加载方法和读取属性。Debug 构建中服务会检查这一点。
-
同一时间一个场景操作。
IsLoading为true时不能启动另一次加载——先await第一次,或取消它。这对 Single 模式加载、叠加加载与叠加卸载一体适用。 -
取消是协作式的。 一旦新场景的
Awake调用开始(LoadPhase.Activating),取消就会被忽略,加载会完成——Unity 无法在场景激活中途安全中止。取消在Loading与MinDisplayHold阶段会被兑现。 -
MinDisplayDuration走 UI 时钟。 最短显示等待使用时间服务的 UI 时钟,它在玩法暂停时仍继续流动。如果你在加载期间暂停 UI 时钟,这段等待也会随之暂停。 -
场景必须在 Build Settings 中。 未注册的名字会以
SceneLoadResult失败,其Error解释了问题所在——检查结果,不要假定成功。 -
IL2CPP + 代码裁剪。 如果你的项目用 IL2CPP 构建,并且你的加载面板或事件订阅者被裁剪掉了,请把这些加入项目的
link.xml:<type fullname="CommonGameSystem.Core.SceneLoadResult" preserve="all"/> <type fullname="System.Threading.Tasks.Task`1[[CommonGameSystem.Core.SceneLoadResult]]" preserve="all"/>