Scene Flow

ローディング画面・キャンセル・加算マルチシーン対応を備えた非同期シーンロード。

シーン遷移のための 1 つの awaitable API · 自動ローディング画面、進捗イベント、協調的キャンセル · 加算マルチシーンレイヤリングを標準装備 · no-op 差し替え: NullSceneService

CGS は ISceneService を通じてゲームをシーン間で移動させます — メニューからゲームプレイへ、そしてまたメニューへ、あらゆる遷移のための 1 つの awaitable API です。await _scene.LoadAsync("Gameplay") を呼べば、残りはサービスが調整します: ローディング画面を自動で表示し、1 フレームのフラッシュを避けるのに十分な時間だけ画面に保持し、プログレスバーのための進捗イベントを発行し、協調的なキャンセル(プレイヤーが Esc を押したらロードは停止)をサポートします。

同じサービスが加算(additive)シーン — 現在のシーンを置き換えるのではなく、その上にロードされるシーン — も扱います。UI シーンを重ねたり、隣接エリアをストリーミングしたりする方法がこれです。シングルモードと加算ロードは 1 つのサービスの 2 つの表面であり、追加のセットアップはありません。

概要

  • 遷移ごとに 1 つの非同期呼び出し。 LoadAsyncawait できる 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 FrameworkIPanel)。
MinDisplayDurationローディング画面を表示し続ける最小秒数。ロードが数フレームで終わったときの不快なフラッシュを防ぎます。
LoadTimeoutSeconds呼び出しごとのタイムアウト上書き。0(デフォルト)はサービス全体の設定を使います。満了するとロードは、内側に TimeoutException を持つ OperationCanceledException をスローします。

結果(SceneLoadResult

プロパティ意味
Succeededシーンが正常にアクティブ化されたら true
ErrorSucceededfalse のときに捕捉された例外(たとえばシーンが 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 を返します。

知っておくべき挙動:

  • 加算ロードはローディング画面をスキップします。 加算操作では LoadingScreenPanelMinDisplayDuration は無視されます(設定していればログメッセージが出ます)— 加算ロードは軽量でシームレスであることを意図しています。
  • 同じイベントが発火します。 加算のロードとアンロードは、シングルモードのロードと同じ SceneLoadStarted / SceneLoadProgress / SceneLoadCompleted イベントを発行します。加算操作の間は ActiveSceneName が変わらないので、両者は見分けられます。
  • シングルモードのロードは加算リストをクリアします。 LoadAsync が完了した時点で、Unity は以前のすべてのシーンをアンロード済みです — サービスは ActiveSceneList を新しいベースシーンだけにリセットします。
  • それでも操作は一度に 1 つ。 加算のロード・アンロードは、シングルモードのロードと同じ進行中ゲートを共有します: IsLoadingtrue の間、別の操作の開始は拒否されます。

完全な例

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 つ。 IsLoadingtrue の間は別のロードを開始できません — 最初のロードを await するか、キャンセルしてください。これはシングルモードのロード、加算ロード、加算アンロードにまたがって適用されます。

  • キャンセルは協調的です。 新しいシーンの Awake 呼び出しが始まると(LoadPhase.Activating)、キャンセルは無視され、ロードは完了します — Unity はアクティブ化の途中でシーンを安全に中断できません。キャンセルは LoadingMinDisplayHold のフェーズ中に尊重されます。

  • 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 クロック