어드레서블 씬

Addressables 카탈로그의 additive 씬을 키별 레퍼런스 카운팅과 함께 로드하고 언로드합니다.

현재 레벨 위로 추가 씬을 스트리밍해 넣고 빼세요 — 레퍼런스 카운팅 덕분에 공유 콘텐츠가 일찍 언로드되는 일이 없습니다.

인터페이스IAddressableSceneService
끄기 스위치NullAddressableSceneService
어셈블리CommonGameSystem.AddressableScene (선택 — Addressables 패키지를 제거하면 컴파일에서 빠짐)
시작부팅 시 자동 등록 (23개 서비스 중 하나) — 별도 설정 불필요

하는 일

이 서비스는 이미 열려 있는 씬들 위에 Addressables 카탈로그의 씬을 로드하고(additive 로딩 — 새 씬이 현재 씬들을 대체하지 않고 합류합니다), 다 쓰면 언로드합니다. Addressables는 Unity의 에셋 전송 시스템입니다: 콘텐츠를 카탈로그에 게시하고 런타임에 문자열 키로 로드합니다.

각 씬 키는 레퍼런스 카운팅됩니다: 이미 라이브인 키를 로드하면 기존 씬을 공유하며 카운트가 올라가고, 카운트가 0으로 돌아올 때만 씬이 실제로 언로드됩니다. 진행 중인 로드도 공유됩니다 — 같은 키에 대한 두 개의 겹치는 로드는 하나의 연산을 await합니다.

런타임 실패는 throw하지 않습니다. 없는 키나 실패한 로드는 경고를 로그하고 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()에서 한 번 resolve해 캐시하세요.

API 레퍼런스

IAddressableSceneService

멤버설명
Task<SceneLoadResult> LoadAdditiveAsync(string key, CancellationToken ct = default)카탈로그 씬을 additive로 로드합니다. 이미 라이브인 키를 로드하면 기존 씬을 공유합니다: 카운트가 올라가고 공유된 결과를 받습니다. 다른 연산이 실행 중일 때 로드를 시작하면 메시지가 [AddrScene]으로 시작하는 InvalidOperationException을 throw합니다. 없는 키나 실패한 로드는 절대 throw하지 않습니다 — 경고를 로그하고 Succeeded = false를 반환합니다. 프로그래밍 오류만 throw합니다: null 또는 공백 키, dispose된 서비스, 워커 스레드에서의 호출.
Task UnloadAdditiveAsync(string key, CancellationToken ct = default)로드된 키의 레퍼런스 카운트를 내립니다. 씬은 카운트가 0에 도달할 때만 언로드됩니다. 자동 축출은 없습니다. 로드되지 않은 키를 언로드해도 절대 throw하지 않습니다 — 경고를 로그하고 완료된 태스크를 반환합니다.
IReadOnlyList<string> ActiveSceneList { get; }현재 로드된 서로 다른 씬 키(로드 순서). 읽기 전용 스냅샷이며, 레퍼런스 카운트는 노출되지 않습니다. 서비스를 꺼 두면 비어 있습니다.
bool IsLoading { get; } 서비스에 진행 중인 연산이 있는지. ISceneService.IsLoading과는 별개입니다 — "씬 작업이 하나라도 돌아가고 있나?"를 물으려면 둘 다 확인하세요.

SceneLoadResult

씬 플로우 서비스가 쓰는 것과 같은 결과 타입:

멤버설명
string SceneName결과가 속한 씬 키.
bool Succeeded성공과 로그된 실패를 가르는 분기 기준.
bool Canceled여기서는 쓰이지 않음 — 항상 false.
Exception Error실패의 원인 예외(있다면).
TimeSpan TotalDuration연산에 걸린 시간.

이벤트

이벤트 버스에 발행됩니다 — 짝 맞춤 보장은 아래 함정을 참고하세요:

이벤트발생 시점
AddrSceneLoadStarted(string 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());

이렇게 하면 모든 additive 로드가 억제됩니다. LoadAdditiveAsync는 호출자가 계속 돌아가도록 Succeeded = true인 완료된 결과를 반환하고, UnloadAdditiveAsync는 아무것도 하지 않으며, ActiveSceneList는 비어 있고, 이벤트는 발생하지 않습니다. 헤드리스 테스트나 카탈로그 콘텐츠 없이 출하되는 빌드에 유용합니다. 교체 구현이 생성될 때 경고가 한 번 로그되므로 교체가 조용히 지나가는 일은 없습니다. 프로그래밍 오류는 여전히 throw하고 — null 키는 여전히 ArgumentNullException을 일으킵니다 — 요청된 취소도 여전히 존중됩니다.

흔한 함정

  • 레퍼런스 카운팅은 호출의 짝 맞춤을 뜻합니다. LoadAdditiveAsync("X") 두 번이면 카운트는 2입니다. 첫 UnloadAdditiveAsync("X")에서 씬은 살아남고, 두 번째에서만 언로드됩니다. 모든 로드에 정확히 하나의 언로드를 짝지으세요. 아니면 씬이 살아남습니다.
  • 게이트는 씬 플로우와 독립적입니다. 여기의 IsLoading은 이 서비스 자신의 잠금입니다. 씬 플로우 연산을 막지도, 그것에 막히지도 않습니다. 서비스에서 두 연산이 겹칠 때만 [AddrScene] InvalidOperationException이 throw됩니다.
  • 모든 시작은 정확히 한 번 닫힙니다. 공유된 로드와 거부된 호출은 이벤트를 발행하지 않습니다. 따라서 각 AddrSceneLoadStarted는 정확히 하나의 AddrSceneLoadCompleted 또는 하나의 AddrSceneLoadCanceled와 짝을 이룹니다 — 둘 다인 경우도, 둘 다 아닌 경우도 없습니다.
  • 일반 씬 전환은 additive 씬을 파괴합니다. 게임이 전체(비-additive) 씬 전환을 수행하면 Unity는 모든 additive 씬을 언로드합니다 — 이 서비스가 아직 추적 중인 것들까지 포함해서. 그런 전환 후에는 필요한 것을 다시 로드하세요.

관련 페이지

  • 씬 플로우 — 기본 씬 로더(ISceneService), Unity 빌드 목록에서의 자체 additive 로딩 포함
  • 에셋 프로바이더 — 자매 서비스: 같은 레퍼런스 카운팅 방식의 Addressables 에셋 로딩
  • 이벤트 버스AddrSceneLoad* 이벤트 구독