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 会卸载所有叠加场景——包括本服务仍在追踪的那些。在这样的切换之后,请重新加载你需要的内容。

相关页面

  • 场景流程 — 基础场景加载器(ISceneService),包括它自己的、从 Unity 构建列表进行的叠加加载
  • 资源提供器 — 姊妹服务:采用同一套引用计数思路的 Addressables 资源加载
  • 事件总线 — 订阅 AddrSceneLoad* 事件