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。フレームワーク予約 — あなたのコードがこの値を発行してはいけません。ConfigBackedRebind— Input サービスのキーリバインド統合のための予約値。入力関連のハンドラーは、フィードバックループを避けるため、このソースでの再適用をスキップすべきです。フレームワーク予約。Unknown— フォールバック。UserSetと同様に扱ってください。
オプション(起動時に設定するチューニング値)
PersistCoalesceMode—Immediate(Setの中で同期的に保存 + 発行)またはDeferred(フラッシュまでバッチ)。フレームワークはこのサービスをDeferredモードで起動します。PublishOnHydrate—trueの場合、グループがストレージから最初にロードされたときにConfigurationChangedイベントを発行します(OldValueは null、SourceはHydrate)。デフォルトは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_AudioSettings、config_GraphicsSettings、…)として永続化します。バックエンドは起動時に一度だけ選ばれます: その時点で Save/Load サービスがアクティブなら設定はそこを経由し、不在または既に NullSaveService へ差し替え済みなら、Configuration は Unity の PlayerPrefs にフォールバックします。Play セッションの後からセーブサービスを差し替えても、すでにルーティングされた設定は移動しません — この選択は起動時の決定です。
組み込みグループ
フレームワークは、自身のサービスが使う 3 つの既製グループを同梱しています: AudioSettings、GraphicsSettings、InputSettings です。独自のグループも追加できます — 挙動とエッジケースにある制約を満たすクラスなら何でも、登録不要で直ちに 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.AudioSettingsはUnityEngine.AudioSettingsと同名です。ファイルが両方の名前空間をインポートする場合は、usingエイリアスを追加してください(上の例で示しています)。 -
OldValueの null チェック。 デフォルト設定では、イベントは常に非 null のOldValueを運びます。PublishOnHydrateを有効にした場合にのみ、(ディスクからのロードイベントで)OldValueが null になり得ます — その場合は使用前にチェックしてください。 -
再入的な
Setはスローします。 あるグループのバリデーターや変更イベントハンドラーの内側から、そのグループのSet<TGroup>を呼ぶとInvalidOperationExceptionがスローされます。変更には反応してください。同じグループを自身の通知から書き戻してはいけません。 -
独自のグループ型と IL2CPP。 フレームワークは 3 つの組み込みグループを IL2CPP のコードストリッピングから保護しています。IL2CPP でビルドする場合は、独自のグループ型に
[Preserve]属性を付け、プロジェクトのlink.xmlにエントリを追加してください。