Addressable 场景
从 Addressables 目录加载与卸载叠加场景,按 key 进行引用计数。
在当前关卡之上把额外场景流式载入与载出——带引用计数,共享内容绝不会被提前卸载。
| 接口 | IAddressableSceneService |
| 关闭开关 | NullAddressableSceneService |
| 程序集 | CommonGameSystem.AddressableScene(可选——移除 Addressables 包后自动退出编译) |
| 启动 | 启动时自动注册(23 个服务之一)——无需任何设置 |
它做什么
这个服务在你已打开的场景之上,从 Addressables 目录加载场景(叠加加载——新场景加入当前场景而不是替换它们),用完后再卸载。Addressables 是 Unity 的资源分发系统:内容发布到一个目录(catalog),运行时按字符串 key 加载。
每个场景 key 都有引用计数:加载一个已经存活的 key 会共享现有场景并把计数加一,只有当计数归零时场景才真正卸载。进行中的加载同样共享——同一 key 的两次重叠加载会等待同一个操作。
运行时失败不会抛异常。key 不存在或加载失败会记录一条警告并返回 Succeeded = false 的结果,你的游戏继续运行。
该服务一次只运行一个操作,其 IsLoading 门与场景流程服务(ISceneService)相互独立——两者永远不会互相阻塞。
它随可选的 CommonGameSystem.AddressableScene 程序集发布。移除 Addressables 包后,该程序集会自行关闭,框架其余部分照常编译。
快速上手
using CommonGameSystem.Core;
using UnityEngine;
public class MySceneStreamer : MonoBehaviour
{
private IAddressableSceneService _scenes;
private void Awake() => _scenes = ServiceLocator.Resolve<IAddressableSceneService>();
public async void EnterArea()
{
SceneLoadResult result = await _scenes.LoadAdditiveAsync("DlcArea_01");
if (result.Succeeded) { /* scene is live */ }
}
// later, when you're done with it:
public async void LeaveArea()
{
await _scenes.UnloadAdditiveAsync("DlcArea_01"); // count goes down; unloads at 0
}
}
Bootstrap 会在启动时自动注册该服务。如上所示,在 Awake() 中解析并缓存一次。
API 参考
IAddressableSceneService
| 成员 | 说明 |
|---|---|
Task<SceneLoadResult> LoadAdditiveAsync(string key, CancellationToken ct = default) | 从目录叠加加载一个场景。加载已存活的 key 会共享现有场景:计数上升,你收到共享的结果。在另一个操作运行期间发起加载会抛出消息以 [AddrScene] 开头的 InvalidOperationException。key 不存在或加载失败绝不抛异常——记录一条警告并返回 Succeeded = false。只有编程错误才抛异常:null 或全空白的 key、已释放的服务、或来自工作线程的调用。 |
Task UnloadAdditiveAsync(string key, CancellationToken ct = default) | 降低已加载 key 的引用计数。只有计数归零时场景才卸载;没有自动淘汰。卸载一个未加载的 key 绝不抛异常——记录一条警告并返回已完成的任务。 |
IReadOnlyList<string> ActiveSceneList { get; } | 当前已加载的不同场景 key,按加载顺序排列。只读快照;不暴露引用计数。服务被关闭时为空。 |
bool IsLoading { get; } | 本服务是否有操作在进行中。它与 ISceneService.IsLoading 相互独立——要问"是否有任何场景工作在运行?",两者都要检查。 |
SceneLoadResult
与场景流程服务使用同一个结果类型:
| 成员 | 说明 |
|---|---|
string SceneName | 该结果所属的场景 key。 |
bool Succeeded | 据此分支以区分成功与已记录的失败。 |
bool Canceled | 此处未使用——恒为 false。 |
Exception Error | 底层失败原因(如有)。 |
TimeSpan TotalDuration | 操作耗时。 |
事件
发布在事件总线上——配对保证见下方陷阱一节:
| 事件 | 触发时机 |
|---|---|
AddrSceneLoadStarted(string Key) | 一次真正的加载开始(该 key 从未加载变为加载中)。 |
AddrSceneLoadCompleted(string Key, SceneLoadResult Result) | 加载在未被取消的情况下结束(成功或已记录的失败)。 |
AddrSceneLoadCanceled(string Key, Exception Cause) | 一次进行中的加载通过其 CancellationToken 被取消。 |
AddressableSceneOptions — 构造设置
| 成员 | 说明 |
|---|---|
bool WarnOnMissingKey / bool WarnOnUnloadOfUnloaded | 警告的详尽程度。Default 预设会告警(更易诊断);Release 预设保持沉默。 |
int InitialKeyCapacity | 内部场景表的初始容量,钳制到 0–1024。 |
示例
using CommonGameSystem.Core;
using UnityEngine;
public class DlcLoader : MonoBehaviour
{
private IAddressableSceneService _scenes;
private const string DlcKey = "DlcArea_01";
private void Awake() => _scenes = ServiceLocator.Resolve<IAddressableSceneService>();
public async void EnterDlc()
{
// Loading twice is safe: a second call while live just raises the count.
SceneLoadResult result = await _scenes.LoadAdditiveAsync(DlcKey);
if (!result.Succeeded)
{
Debug.LogWarning($"DLC scene didn't load: {result.Error}");
return;
}
Debug.Log($"Loaded in {result.TotalDuration.TotalMilliseconds:F0} ms. " +
$"Active: {string.Join(", ", _scenes.ActiveSceneList)}");
}
public async void LeaveDlc() => await _scenes.UnloadAdditiveAsync(DlcKey); // unloads at count 0
}
关闭它
ServiceLocator.Replace<IAddressableSceneService>(new NullAddressableSceneService());
这会抑制所有叠加加载。LoadAdditiveAsync 返回 Succeeded = true 的已完成结果以便调用方继续运行,UnloadAdditiveAsync 不做任何事,ActiveSceneList 为空,且不触发任何事件。适用于 headless 测试,或不带目录内容发布的构建。替换实现构造时记录一条警告,因此替换绝不会悄无声息。编程错误仍会抛异常——null key 仍会引发 ArgumentNullException——请求的取消也仍会被尊重。
常见陷阱
- 引用计数意味着成对调用。 两次
LoadAdditiveAsync("X")把计数推到 2。场景会挺过第一次UnloadAdditiveAsync("X"),只在第二次时卸载。每次加载都要与恰好一次卸载配对,否则会留下一个一直活着的场景。 - 这道门与场景流程相互独立。 这里的
IsLoading是本服务自己的锁。它不阻塞、也不被场景流程的操作阻塞。只有本服务上的两个重叠操作才会抛出[AddrScene]的InvalidOperationException。 - 每次开始都恰好收尾一次。 共享的加载与被拒绝的调用不发布任何事件。因此每个
AddrSceneLoadStarted都对应恰好一个AddrSceneLoadCompleted或恰好一个AddrSceneLoadCanceled——绝不会两个都有,也绝不会两个都没有。 - 普通的场景切换会摧毁叠加场景。 如果你的游戏执行一次完整的(非叠加)场景切换,Unity 会卸载所有叠加场景——包括本服务仍在追踪的那些。在这样的切换之后,请重新加载你需要的内容。