Configuration

バリデーションと変更イベントを備えた、型付きで永続化される設定グループ。

変更イベント付きの、型付きで永続化される設定グループ · すべての書き込みで検証 · no-op 差し替え: NullConfiguration

概要

Configuration サービスは、ゲーム設定 — オーディオ音量、グラフィックス品質、入力リバインド、UI 設定 — を、強く型付けされたグループ(設定領域ごとに 1 つの、プレーンなシリアライズ可能クラス)として保存します。すべての Set は新しい値を検証してキャッシュします。変更がいつディスクに保存され、イベントとしてブロードキャストされるかはモード次第です: Immediate モードは Set の中で両方を同期的に行い、Deferred モード(起動時のデフォルト)は変更を保留キューにため込みます — FlushPending<TGroup>()FlushAllPending() を呼ぶまで、何も書き込まれず、何も発行されません。バッチングにより、UI スライダーの高速なドラッグがフレームヒッチを起こさなくなります。その代償は、消費側に反応させたいタイミングで 1 回の明示的なフラッシュ呼び出しが要ることです。

簡単な使用例

現在のオーディオ設定を読み取り、スライダーから更新し、Event Bus に発行される変更に反応するオプションパネルのスクリプト:

using System;
using CommonGameSystem.Core;
using UnityEngine;
using AudioSettings = CommonGameSystem.Core.AudioSettings;  // Unity has its own AudioSettings type

[DefaultExecutionOrder(100)]
public class AudioPanel : MonoBehaviour
{
    private IConfiguration _cfg;
    private IEventBus _bus;
    private IDisposable _subscription;

    private void Awake()
    {
        // Resolve and cache once
        _cfg = ServiceLocator.Resolve<IConfiguration>();
        _bus = ServiceLocator.Resolve<IEventBus>();

        // Pull the current value (no event fires on plain loads by default)
        var audio = _cfg.Get<AudioSettings>();
        UpdateSliders(audio);

        // Subscribe for future changes
        _subscription = _bus.Subscribe<ConfigurationChanged<AudioSettings>>(OnAudioChanged);
    }

    private void OnDestroy()
    {
        _subscription?.Dispose();
    }

    // Called when the user drags the volume slider
    public void OnMasterVolumeSliderChanged(float value)
    {
        var current = _cfg.Get<AudioSettings>();
        current.masterVolume = value;
        _cfg.Set(current);                        // Deferred (default): queues the change only
        _cfg.FlushPending<AudioSettings>();       // save + publish NOW so consumers react live
        // To batch disk writes during a drag instead, move the FlushPending
        // call to the slider's drag-end or Apply-button handler.
    }

    // Event handler (OldValue is always non-null for UserSet changes)
    private void OnAudioChanged(ConfigurationChanged<AudioSettings> evt)
    {
        if (evt.Source == ConfigurationChangeSource.UserSet)
            UpdateAudioMixer(evt.NewValue);
    }

    private void UpdateSliders(AudioSettings audio) { /* ... */ }
    private void UpdateAudioMixer(AudioSettings audio) { /* ... */ }
}

API の全体像

コアアクセサー

  • TGroup Get<TGroup>() where TGroup : class, new() — 現在キャッシュされているグループを取得します。各グループ型の初回呼び出しはストレージからロードし、以降の呼び出しはキャッシュヒットです。まだ何も保存されていない場合(新規インストール、データ削除後)は、グループのデフォルト — new TGroup()、または DefaultsProviders に登録されたファクトリ — が返ります。データがなくても決してスローせず、常に非 null のインスタンスを返します。

  • void Set<TGroup>(TGroup value) where TGroup : class, new() — グループを置き換えて検証します。Immediate モードでは、同じ呼び出しの中で保存と変更イベントの発行も行います。Deferred モード(起動時のデフォルト)では変更をキューに積むだけです — 保存発行はどちらもフラッシュを待ちます。value が null なら ArgumentNullException を、バリデーターや変更イベントハンドラーの内側から同じグループに対して再入的に呼ばれたら InvalidOperationException をスローします(無限の set→publish→set ループへのガード)。

  • void Reset<TGroup>() where TGroup : class, new() — 1 つのグループをファクトリデフォルトに戻し、その保存データを(モードに関係なく)即座に削除し、Source == UserSet の変更イベントを発行します。このグループに対して保留中の deferred な Set は破棄されます。

  • void ResetAll() — すべてのグループをデフォルトに戻し、保存済みの設定データをすべて即座に削除し、グループごとに 1 つの変更イベントを発行します。保留中の deferred エントリはすべて先に破棄されるため、直後の FlushAllPending() は無害な no-op です。

Deferred モードのフラッシュ(起動時デフォルトでは必須)

  • void FlushPending<TGroup>() where TGroup : class, new() — 保留中の 1 グループを保存して発行します。Immediate モードのときや、このグループに保留中のものがないときは no-op です。典型的な呼び出し場所: スライダーのドラッグ終了ハンドラー、オプション画面の Apply ボタン。
  • void FlushAllPending() — 保留中の全グループを 1 パスで保存して発行します。典型的な呼び出し場所: オプション画面のクローズ、シーン遷移。アプリケーション終了時にも自動で呼ばれます — ただしそのパスでは保存のみを行い、イベントは発行しません(挙動とエッジケースを参照)。

イベントペイロード

Event Bus 上で IEventBus.Subscribe<ConfigurationChanged<TGroup>> を通じて購読します:

  • ConfigurationChanged<TGroup>.NewValue — 新しい設定(常に非 null)。
  • ConfigurationChanged<TGroup>.OldValue — 以前の設定。null になるのはディスクからのロードイベントのみ、しかもそれをオプトインした場合だけです(下の PublishOnHydrate を参照)。
  • ConfigurationChanged<TGroup>.Source — 変更の出どころ:
    • UserSet — ユーザー起因の変更(スライダー、キーバインドダイアログ、Apply ボタン)。OldValue は常に非 null。通常どおり処理してください。
    • Hydrate — グループがストレージから初めてロードされたところ。PublishOnHydrate が有効な場合にのみ発行され、OldValue は null。フレームワーク予約 — あなたのコードがこの値を発行してはいけません。
    • ConfigBackedRebindInput サービスのキーリバインド統合のための予約値。入力関連のハンドラーは、フィードバックループを避けるため、このソースでの再適用をスキップすべきです。フレームワーク予約。
    • Unknown — フォールバック。UserSet と同様に扱ってください。

オプション(起動時に設定するチューニング値)

  • PersistCoalesceModeImmediateSet の中で同期的に保存 + 発行)または Deferred(フラッシュまでバッチ)。フレームワークはこのサービスを Deferred モードで起動します。
  • PublishOnHydratetrue の場合、グループがストレージから最初にロードされたときに ConfigurationChanged イベントを発行します(OldValue は null、SourceHydrate)。デフォルトは false: 意図されたパターンは、Awake で現在値を一度プルし、以後の変更を購読することです。
  • LoggerWarningThrottleSeconds — 繰り返される検証警告(クランプやサニタイズされた値)をフィールドごとに N 秒に 1 回へ制限し、スライダードラッグが Console をスパムできないようにします。デフォルトは 1 秒。
  • PlayerPrefsSoftCapBytes — 設定が PlayerPrefs に保存されている場合、ペイロードが PlayerPrefs の文字列上限(約 64 KB)に近づくと警告します。デフォルトは 60000。
  • SaveServiceConfigGroupKeyPrefix — 設定が Save/Load サービス経由で保存されるときに使うセーブスロットのプレフィックス。デフォルトは "config_" で、オーディオグループはスロット config_AudioSettings に保存されます。
  • DefaultsProviders / SetDefaultsProvider<TGroup>(Func<TGroup>) — グループごとのファクトリ関数。デフォルト値が new TGroup() ではなく、デザイナーが作成したアセット(たとえば Inspector で配線した ScriptableObject)から来る場合に使います。

設定の保存先

デフォルトでは、このサービスは Save/Load サービスを通じて、グループごとに 1 つのセーブスロット(config_AudioSettingsconfig_GraphicsSettings、…)として永続化します。バックエンドは起動時に一度だけ選ばれます: その時点で Save/Load サービスがアクティブなら設定はそこを経由し、不在または既に NullSaveService へ差し替え済みなら、Configuration は Unity の PlayerPrefs にフォールバックします。Play セッションの後からセーブサービスを差し替えても、すでにルーティングされた設定は移動しません — この選択は起動時の決定です。

組み込みグループ

フレームワークは、自身のサービスが使う 3 つの既製グループを同梱しています: AudioSettingsGraphicsSettingsInputSettings です。独自のグループも追加できます — 挙動とエッジケースにある制約を満たすクラスなら何でも、登録不要で直ちに Get/Set と一緒に機能します。

オフにする

// In a test setup, or anywhere before consumers resolve it:
ServiceLocator.Replace<IConfiguration>(new NullConfiguration());

NullConfiguration では、すべての Get が新品のデフォルトを返し、すべての Set/Reset は no-op です — イベントの発行も、ディスクへの書き込みもありません。ヘッドレス/CI ビルドや、プレイヤーのカスタマイズを永続化すべきでないキオスク環境に便利です。消費側は変更なしで動き続けます。単にデフォルト値だけを見て、変更イベントを受け取らなくなります。

挙動とエッジケース

  • メインスレッド専用。 すべてのメソッドは、メインスレッドでの実行をアサートします(このチェックは Release ビルドでは除去されます)。ワーカースレッドから呼ばないでください。必要なら、まずメインスレッドへマーシャリングします。

  • Deferred モードがデフォルトです。 Set 単体では保存も発行もしません。FlushPending<TGroup>()FlushAllPending() を呼ばなければ、消費側(たとえば Audio サービスのミキサー)は変更を決して見ることがありません。アプリケーション終了時のセーフティネットが保留中の値を保存しますが、イベントは発行しません — つまりそれに頼ると、プレイ中にライブな消費側が反応することはありません。ライブなフィードバックには変更ごとにフラッシュし、書き込みをまとめたければドラッグ終了や「Apply」でフラッシュしてください。

  • グループ型はプレーンなシリアライズ可能クラスでなければなりません。 [Serializable] を付け、public フィールドだけを使います: 自動プロパティ不可、UnityEngine.Object 参照不可、そしてパラメーターなしコンストラクターが必要です。下層のシリアライザーである Unity の JsonUtility は自動プロパティを黙って無視します — 値が静かに永続化されなくなります。

  • Unity の型との名前衝突。 組み込みグループ CommonGameSystem.Core.AudioSettingsUnityEngine.AudioSettings と同名です。ファイルが両方の名前空間をインポートする場合は、using エイリアスを追加してください(上の例で示しています)。

  • OldValue の null チェック。 デフォルト設定では、イベントは常に非 null の OldValue を運びます。PublishOnHydrate を有効にした場合にのみ、(ディスクからのロードイベントで)OldValue が null になり得ます — その場合は使用前にチェックしてください。

  • 再入的な Set はスローします。 あるグループのバリデーターや変更イベントハンドラーの内側から、そのグループの Set<TGroup> を呼ぶと InvalidOperationException がスローされます。変更には反応してください。同じグループを自身の通知から書き戻してはいけません。

  • 独自のグループ型と IL2CPP。 フレームワークは 3 つの組み込みグループを IL2CPP のコードストリッピングから保護しています。IL2CPP でビルドする場合は、独自のグループ型に [Preserve] 属性を付け、プロジェクトの link.xml にエントリを追加してください。

関連ページ

  • Event BusConfigurationChanged<TGroup> イベントの購読
  • Save/Load — 設定のデフォルトのストレージバックエンド
  • AudioAudioSettings の消費者
  • InputInputSettings を通じて保存されるキーリバインド
  • Bootstrap — 起動順序と永続化バックエンドの登録