Scene Flow
ローディング画面・キャンセル・加算マルチシーン対応を備えた非同期シーンロード。
シーン遷移のための 1 つの awaitable API · 自動ローディング画面、進捗イベント、協調的キャンセル · 加算マルチシーンレイヤリングを標準装備 · no-op 差し替え:
NullSceneService
CGS は ISceneService を通じてゲームをシーン間で移動させます — メニューからゲームプレイへ、そしてまたメニューへ、あらゆる遷移のための 1 つの awaitable API です。await _scene.LoadAsync("Gameplay") を呼べば、残りはサービスが調整します: ローディング画面を自動で表示し、1 フレームのフラッシュを避けるのに十分な時間だけ画面に保持し、プログレスバーのための進捗イベントを発行し、協調的なキャンセル(プレイヤーが Esc を押したらロードは停止)をサポートします。
同じサービスが加算(additive)シーン — 現在のシーンを置き換えるのではなく、その上にロードされるシーン — も扱います。UI シーンを重ねたり、隣接エリアをストリーミングしたりする方法がこれです。シングルモードと加算ロードは 1 つのサービスの 2 つの表面であり、追加のセットアップはありません。
概要
- 遷移ごとに 1 つの非同期呼び出し。
LoadAsyncはawaitできるTask<SceneLoadResult>を返します。成功と失敗は結果オブジェクトとして返り、キャンセルとタイムアウトはOperationCanceledExceptionをスローします。 - 自動ローディング画面。 オプションでパネルを渡すと、サービスがロード前に push し、終了後に pop します。最小表示時間も設定できます。
- 進捗イベント。 サービスはロードの開始/進捗/完了/キャンセルのイベントを Event Bus に発行します — 音楽をフェードアウト・フェードインするのに自然な場所です。
- 加算レイヤリング。 ベースシーンの上に追加のシーンをロード・アンロードし、ロード済みシーンの全リストを調べ、どのシーンを Unity がアクティブとして扱うかを選べます。
サービスの取得
一度解決して参照をキャッシュします(決して 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 Framework の 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
発行されるイベント
Event Bus を通じて購読します:
| イベント | タイミング | 典型的な用途 |
|---|---|---|
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)
加算ロード済みのシーンを 1 つアンロードします。ベースシーンの名前や、ロードされていない名前を渡すのは、警告付きの安全な no-op です — この方法でベースシーンが取り除かれることは決してありません(置き換えるには LoadAsync を使います)。
IReadOnlyList<string> ActiveSceneList { get; }
ロード済みのすべてのシーン: 先頭にベースシーン、続いて加算シーンがロードされた順に並びます。読み取るたびに新しいスナップショットが返り、自由に保持・変更できます。
bool SetActiveScene(string sceneName)
ロード済みシーンのどれを Unity が「アクティブ」として扱うか — 新規インスタンス化されたオブジェクトを受け取り、ライティング設定を駆動するシーン — を指定します。そのシーンはすでに ActiveSceneList に現れていなければなりません。ロードされていなければ(警告付きで、例外なしに)false を返します。
知っておくべき挙動:
- 加算ロードはローディング画面をスキップします。 加算操作では
LoadingScreenPanelとMinDisplayDurationは無視されます(設定していればログメッセージが出ます)— 加算ロードは軽量でシームレスであることを意図しています。 - 同じイベントが発火します。 加算のロードとアンロードは、シングルモードのロードと同じ
SceneLoadStarted/SceneLoadProgress/SceneLoadCompletedイベントを発行します。加算操作の間はActiveSceneNameが変わらないので、両者は見分けられます。 - シングルモードのロードは加算リストをクリアします。
LoadAsyncが完了した時点で、Unity は以前のすべてのシーンをアンロード済みです — サービスはActiveSceneListを新しいベースシーンだけにリセットします。 - それでも操作は一度に 1 つ。 加算のロード・アンロードは、シングルモードのロードと同じ進行中ゲートを共有します:
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 実装では、すべてのロード呼び出しが完了済みの成功タスクを返します — 実際のロードなし、イベントなし、ローディングパネルなし。ヘッドレステストやゲームプレイフローのモックに便利です。キャンセルトークンは引き続き尊重します(すでにキャンセル済みのトークンはキャンセル済みタスクを生みます)。
よくある落とし穴
-
メインスレッド専用。 ロードメソッドの呼び出しとプロパティの読み取りは、メインスレッドでのみ行ってください。Debug ビルドでサービスがチェックします。
-
シーン操作は一度に 1 つ。
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"/>
関連ページ
- Bootstrap — サービスが自動的に起動する仕組み
- UI Framework — ローディング画面に使うパネル型
- Event Bus — 4 つのシーンイベントの購読
- Time — 最小表示ホールドの時間を計る UI クロック