씬 플로우
로딩 화면, 취소, 애디티브 멀티 씬을 지원하는 비동기 씬 로딩.
씬 전환을 위한 하나의 awaitable API · 자동 로딩 화면, 진행률 이벤트, 협조적 취소 · 애디티브 멀티 씬 레이어링 내장 · no-op 대체물:
NullSceneService
CGS는 ISceneService — 모든 메뉴→게임플레이→메뉴 전환을 위한 하나의 awaitable API — 를 통해 게임을 씬 사이로 이동시킵니다. await _scene.LoadAsync("Gameplay")를 호출하면 나머지는 서비스가 조율합니다: 로딩 화면을 자동으로 보여 주고, 한 프레임 번쩍임을 피할 만큼 충분히 화면에 유지하고, 진행률 바를 위한 진행 이벤트를 발행하며, 협조적 취소(플레이어가 Esc를 누르면 로드가 멈춤)를 지원합니다.
같은 서비스가 애디티브 씬 — 현재 씬을 대체하는 대신 그 위에 로드되는 씬 — 도 처리합니다. UI 씬을 겹치거나 이웃 지역을 스트리밍해 들일 때 쓰는 방식입니다. 싱글 모드와 애디티브 로딩은 한 서비스의 두 표면입니다; 추가 설정은 없습니다.
무엇을 하는가
- 전환당 하나의 비동기 호출.
LoadAsync는await할 수 있는Task<SceneLoadResult>를 반환합니다. 성공과 실패는 결과 객체로 돌아오고, 취소와 타임아웃은OperationCanceledException을 던집니다. - 자동 로딩 화면. 옵션에 패널을 넘기면 서비스가 로드 전에 push하고 끝난 뒤 pop하며, 최소 표시 시간을 구성할 수 있습니다.
- 진행률 이벤트. 서비스는 로드 시작/진행/완료/취소 이벤트를 이벤트 버스에 발행합니다 — 음악을 페이드 아웃했다가 다시 페이드 인하기에 자연스러운 지점입니다.
- 애디티브 레이어링. 베이스 씬 위에 추가 씬을 로드/언로드하고, 로드된 씬의 전체 목록을 조사하고, Unity가 어느 것을 활성으로 취급할지 선택합니다.
서비스 가져오기
한 번 resolve해 참조를 캐시하십시오(Update 안에서는 절대 금지):
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)]
public class MenuController : MonoBehaviour
{
private ISceneService _scene;
private void Awake()
{
_scene = ServiceLocator.Resolve<ISceneService>();
}
}
씬 로드하기 (싱글 모드)
싱글 모드 로딩은 현재 씬을 대체합니다 — 표준적인 메인 메뉴→게임플레이 전환입니다.
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 | 로드가 시작될 때 push되고 끝나면 pop되는 선택적 패널(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 씬, 스트리밍되는 이웃 지역, 공유 "매니저" 씬에 사용하십시오.
Task<SceneLoadResult> LoadAdditiveAsync(string sceneName,
SceneLoadOptions opts = default,
CancellationToken ct = default)
베이스 씬 위에 씬을 애디티브로 로드합니다. 활성 씬은 바뀌지 않습니다 — ActiveSceneName은 여전히 베이스 씬을 보고합니다. 이미 애디티브로 로드된 씬을 다시 로드하는 것은 안전한 no-op입니다: 서비스는 경고를 로그하고 완료된 태스크를 반환합니다(참조 카운팅은 없습니다 — 각 씬은 최대 한 번만 로드됩니다).
Task UnloadAdditiveAsync(string sceneName, CancellationToken ct = default)
애디티브로 로드된 씬 하나를 언로드합니다. 베이스 씬의 이름이나 로드되지 않은 이름을 넘기는 것은 경고와 함께 안전한 no-op입니다 — 베이스 씬은 이 방법으로 결코 제거될 수 없습니다(대체하려면 LoadAsync를 사용하십시오).
IReadOnlyList<string> ActiveSceneList { get; }
로드된 모든 씬: 베이스 씬이 먼저, 이어서 로드된 순서대로 애디티브 씬들. 읽을 때마다 자유롭게 보관하거나 수정할 수 있는 새 스냅샷을 반환합니다.
bool SetActiveScene(string sceneName)
로드된 씬 중 어느 것이 "활성"인지 — 새로 인스턴스화되는 오브젝트를 받고 라이팅 설정을 주도하는 씬 — Unity에 알립니다. 그 씬은 이미 ActiveSceneList에 있어야 합니다; 로드되어 있지 않으면 (경고와 함께, 예외 없이) false를 반환합니다.
알아 둘 만한 동작:
- 애디티브 로드는 로딩 화면을 건너뜁니다. 애디티브 작업에서는
LoadingScreenPanel과MinDisplayDuration이 무시됩니다(설정했다면 로그 메시지와 함께) — 애디티브 로드는 가볍고 이음새 없이 이루어지도록 의도된 것입니다. - 같은 이벤트가 발행됩니다. 애디티브 로드와 언로드는 싱글 모드 로드와 동일한
SceneLoadStarted/SceneLoadProgress/SceneLoadCompleted이벤트를 발행합니다. 애디티브 작업 중에는ActiveSceneName이 바뀌지 않는다는 점으로 구별할 수 있습니다. - 싱글 모드 로드는 애디티브 목록을 비웁니다.
LoadAsync가 완료되면 Unity가 이전 씬을 전부 언로드한 상태입니다 — 서비스는ActiveSceneList를 새 베이스 씬 하나로 리셋합니다. - 여전히 한 번에 한 작업. 애디티브 로드와 언로드는 싱글 모드 로드와 동일한 in-flight 게이트를 공유합니다:
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 구현에서는 모든 로드 호출이 이미 완료된 성공 태스크를 반환합니다 — 실제 로딩도, 이벤트도, 로딩 패널도 없습니다. 헤드리스 테스트나 게임플레이 플로우 모킹에 유용합니다. 취소 토큰은 여전히 존중합니다(이미 취소된 토큰은 취소된 태스크를 만듭니다).
흔한 함정
-
메인 스레드 전용. 로드 메서드 호출과 프로퍼티 읽기는 메인 스레드에서만 하십시오. 디버그 빌드에서 서비스가 이를 검사합니다.
-
씬 작업은 한 번에 하나.
IsLoading이true인 동안 다른 로드를 시작할 수 없습니다 — 먼저 것을await하거나 취소하십시오. 이는 싱글 모드 로드, 애디티브 로드, 애디티브 언로드 전체에 적용됩니다. -
취소는 협조적입니다. 새 씬의
Awake호출이 시작되면(LoadPhase.Activating) 취소는 무시되고 로드가 완료됩니다 — Unity는 활성화 도중의 씬을 안전하게 중단할 수 없습니다. 취소는Loading과MinDisplayHold단계에서 존중됩니다. -
MinDisplayDuration은 UI 클럭으로 흐릅니다. 최소 표시 대기는 Time 서비스의 UI 클럭을 사용하며, 이 클럭은 게임플레이가 일시정지된 동안에도 계속 흐릅니다. 로드 중에 UI 클럭을 일시정지하면 대기도 함께 멈춥니다. -
씬은 Build Settings에 있어야 합니다. 등록되지 않은 이름은
Error가 문제를 설명하는SceneLoadResult로 실패합니다 — 성공을 가정하지 말고 결과를 확인하십시오. -
IL2CPP + 코드 스트리핑. 프로젝트가 IL2CPP로 빌드되고 로딩 패널이나 이벤트 구독자가 스트리핑된다면, 프로젝트의
link.xml에 다음을 추가하십시오:<type fullname="CommonGameSystem.Core.SceneLoadResult" preserve="all"/> <type fullname="System.Threading.Tasks.Task`1[[CommonGameSystem.Core.SceneLoadResult]]" preserve="all"/>