Save Design 使い方ガイド


📄 Save Design 使用ガイド - 目次

  1. はじめに
    1.1 購入のお礼とスクリプトリファレンスの案内
    1.2 Save Design とは(特徴と仕組みの概要)

  2. 最小限の導入ステップ
    2.1 必要な準備(Unityバージョン/インストール)
    2.2 データ定義クラスの作成

  3. 実用的な使い方例
    3.1 共有データの読み書きについて
    3.2 スロットセーブとオートセーブ
    3.3 SlotMetaData の活用と注意点
    3.4 TempData を使った一時的な状態管理
    3.5 初期化処理の活用方法
    3.6 読み書き時のコールバックの活用方法

  4. よくあるユースケース
    4.1 データ構造の変更にどう対応するか
    4.2 ExceptionPolicy による例外の扱い方
    4.3 一部のデータだけを初期化したい

  5. セキュリティと暗号化(オプション)
    5.1 暗号化を有効にする方法(エディタツール)
    5.2 独自暗号化処理の組み込み方法


1. はじめに

1.1 購入のお礼とスクリプトリファレンスの案内

このたびは Save Design をご購入いただき、誠にありがとうございます。 本アセットが、あなたのプロジェクトにおけるセーブ機能の実装・保守を少しでも快適にすることを願っています。

このドキュメントでは、Save Design の基本的な使い方や導入の流れについて説明します。 各 API の詳細仕様や属性の使い方、インターフェース定義などのより技術的な情報については、 別途ご用意している 「スクリプトリファレンス」 をあわせてご参照ください。

スクリプトリファレンスは、Documentation/save-design-script-reference.pdf に同梱されています。 使用方法とあわせてお読みいただくことで、よりスムーズにご活用いただけます。


1.2 Save Design とは(特徴と仕組みの概要)

Save Design は、Unity 向けに設計されたセーブデータ管理フレームワークです。 最大の特徴は、キー文字列を一切使わずにセーブデータへアクセスできることです。

開発者は [SharedData][SlotData] などの属性を使って、セーブ対象のクラスを明示的に定義します。 それに対して Save Design が 静的 API を自動生成することで、 IDE 補完で型安全にセーブ/ロード/初期化を行えるようになります。


🔧 Save Design の構成要素

要素説明
属性によるデータ定義[SharedData], [SlotData], [TempData] などでセーブ対象を分類
静的 API の自動生成SD.Save.Slot(...)SD.Load.Shared() などの関数が自動的に生成される
補完・リネームに強いすべての処理が型情報に基づいており、キーの書き間違いや更新漏れを防止
導入が簡単属性を付けるだけで導入でき、初期化や設定も最小限
暗号化に対応(オプション)必要に応じて AES + HMAC 暗号化を導入可能

🎯 Save Design の狙い

  • データ構造が複雑になっても 整理されたセーブ設計を維持したい
  • セーブ処理の実装や保守を 型安全に・シンプルに管理したい
  • 複数スロットや一時的な状態など、現実的なセーブ要件を柔軟に対応したい

このようなニーズを持つ開発者にとって、Save Design は「セーブ周りの面倒ごとを消してくれる」頼れるツールです。


2. 最小限の導入ステップ

2.1 必要な準備(Unityバージョン/インストール)

✅ 対応 Unity バージョン

  • Unity 2022.3 LTS 以降 を推奨
  • .NET Standard 2.1 相当の API が使用可能なバージョンである必要があります

📦 パッケージのインポート

Save Design は Unity のパッケージとして配布されており、 インポート後は Assets/Plugins/Save Design/ フォルダ配下に以下の構成が展開されます:

Assets/Plugins/Save Design/
├── Editor/         // エディタ拡張(セットアップ、暗号化設定・コード生成)
├── Runtime/        // 属性やインターフェースなどのコア機能
├── Samples/        // 使用例とサンプルシーン
└── Documentation/  // スクリプトリファレンスと使用ガイド(このファイル)

🔧 Save Design のセットアップをエディタから簡単に行う

Save Design には、初期セットアップを簡略化するための 専用エディタツールが用意されています。 スクリプトの雛形生成から設定アセットの作成まで、数クリックで完了できます。


🔹 スクリプトの自動生成

  1. Unity のメニューから Tools > Save Design > Setup を選択し、Save Design Setup ウィンドウを開きます。

  2. Namespace を入力したら、Generate scripts ボタンをクリックします。

  3. 指定したパスに以下のスクリプトが自動生成されます:

    • SaveDesignRoot 属性が付与された SD クラス(ルートクラス)
    • ISaveDesignConfig を実装した SaveDesignConfig クラス(設定用)

これにより、Save Design を使うための最低限の構成がすぐに整います。


🔹 設定アセットの作成

次に、Unity メニューから Tools > Save Design > Create Save Design Config を選択します。

この操作により、Assets/Resources フォルダ内に SaveDesignConfig アセットが自動生成されます。 このアセットでは以下のような ファイルに関する設定を行うことができます:

  • セーブフォルダ名
  • 共有データ/スロットデータのファイル名
  • 拡張子
  • 初期化処理や読み書き処理中に発生した例外の扱い方

設定が完了すれば、あとは [SharedData][SlotData] を付けたデータクラスを定義するだけで、 Save Design の仕組みをすぐに使い始めることができます。

このセットアップ手順はプロジェクトごとに一度だけ実行すれば十分です。 以降は自動生成された API を通じて、IDE補完つきで安全かつ簡潔にセーブ処理を実装できます。


⚠️ 不要なサンプルを削除してください

Save Design には、導入直後の動作確認や使い方の参考として、簡単な サンプルクラスサンプルシーンが同梱されています。

しかし、これらは実際のゲームには不要であることが多く、 そのままにしておくと最終ビルドにも含まれてしまう可能性があります

Assets/Plugins/Save Design/Samples フォルダを削除し、本番環境に不要なデータを含めないようにしましょう。


🧩 任意で導入できるライブラリ

MessagePack for C#(高速シリアライザ)

  • 使用したい場合は、MessagePack for C# を導入してください
  • ルートクラスに [SaveDesignRoot(SerializerType.MessagePack)] を付けることで対応可能です

Newtonsoft.Json(高機能JSONシリアライザ)

  • 使用したい場合は、Newtonsoft.Json を導入してください
  • ルートクラスに [SaveDesignRoot(SerializerType.NewtonsoftJson)] を付けることで対応可能です
  • Vector3 などの一般的なUnity型を保存する場合は、Newtonsoft.Json-for-Unity.Converters の導入もおすすめします

MemoryPack(高速シリアライザ)

  • 使用したい場合は、MemoryPack を導入してください
  • ルートクラスに [SaveDesignRoot(SerializerType.MemoryPack)] を付けることで対応可能です

これらのライブラリは Save Design に同梱されていません。 必要に応じて各自導入し、ライセンスに従って使用してください。


2.2 データ定義クラスの作成

Save Design では、セーブ対象のデータをクラスとして定義し、 そこに [SharedData][SlotData] などの属性を付けて分類します。

この「データ定義」によって、Save Design は自動的にセーブ用 API を生成できるようになります。


🔹 データの種類と使い分け

属性名用途
[SharedData]全セーブスロット共通のプレイヤー設定や進行状況に
[SlotData]スロットごとに異なるセーブ内容(キャラ情報、所持品など)に
[TempData]保存しない一時的なデータ(セッション内フラグなど)に
[SlotMetaData]スロット表示用のメタ情報(保存日時、プレイ時間など)に

属性の詳細な使い方や構造は、スクリプトリファレンスの「2. 属性」セクションをご参照ください。


🧩 例:スロットごとのプレイヤーデータ

[SlotData]            // セーブスロットごとに分けるデータは SlotData 属性
[System.Serializable] // JsonUtility でシリアライズするために必要
public class Player
{
    public int level;
    public float hp;
    public Vector3 position;
}

上記のようなクラスを定義するだけで、自動生成された API 経由で下記のようにこのデータを保存・読み込みできるようになります。

SD.Load.Slot(slotIndex);          // 読み込み

var level = SD.Slot.Player.level; // 値の取得

SD.Slot.Player.level++;           // 値の変更

SD.Save.Slot(slotIndex);          // 保存

💡 注意:フィールドの保存対象は使用するシリアライザーに依存します

Save Design では、データクラスの内容を実際に保存・復元する処理は、JsonUtilityMessagePack for C# などの 外部シリアライザーによって行われます。 そのため、「どのフィールドが保存されるか」は選択したシリアライザーの仕様に従います。

たとえば UnityEngine.JsonUtility を使用する場合:

  • public フィールドは保存されます
  • private フィールドでも [SerializeField] が付いていれば保存されます
  • プロパティや [NonSerialized] 属性が付いたフィールドは保存されません

一方、MessagePack for C# を使用する場合は [MessagePackObject][Key(n)] 指定が必要です。

意図したデータが正しく保存・復元されるように、各シリアライザーの仕様を確認のうえ設計してください。


3. 実用的な使い方例

3.1 共有データの読み書きについて

[SharedData] を付与したデータは、すべてのセーブスロットに共通してアクセスされる「共有データ」として扱われます。

ユーザー設定やゲーム全体の進行状況など、スロットに依存しない情報の保存に適しています。


✅ 読み込みの推奨タイミングと方法

共有データは、ゲーム起動時に一度だけ読み込んでおき、以降は常駐データとして扱うのが一般的です。

この読み込み処理は、ルートクラスに以下のような静的初期化関数を実装する形で行うことを推奨します:

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void InitSaveDesignConfig()
{
    // ファイル関連の設定を事前に行う
    config = Resources.Load<SaveDesignConfig>("SaveDesignConfig");

    // 共有データの読み込みを行い、データが無ければ初期化する
    if (!Load.Shared()) Initialize.Shared();
}

この処理により、どのシーンよりも早い段階で共有データが準備されるため、 後続の処理が安心して SD.Shared.xxx にアクセスできる状態になります。


💾 保存のタイミングと設計方針

共有データの保存タイミングについては、変更が発生した直後に保存する方式と、ゲーム終了時にまとめて保存する方式 の2種類があります。

どちらも使用可能ですが、信頼性と意図の明確さの観点から、基本的には前者(即時保存)を推奨します。


✅ 方式①:値が変更された直後に保存する(推奨)

SD.Shared.settings.volume = newVolume;
SD.Save.Shared();

この方法は最も安全で、アプリケーションが予期せず終了した場合にも、直前の変更が確実に保存されている状態を保てます

とくに設定や進行状況など、後で復元が必要になる情報にはこの方式を使うことを推奨します。


⚠️ 方式②:アプリケーション終了時にまとめて保存する(補助的)

[RuntimeInitializeOnLoadMethod]
private static void RegisterSaveOnExit()
{
    Application.quitting += () => SD.Save.Shared();
}

この方法は記述が簡単で漏れがなく、軽量なプロジェクトや頻繁に変更されない設定項目に向いています。

ただし、Application.quitting はあくまで Unity が「正常終了」と判断した場合にしか呼び出されないため、 タスクマネージャーでの強制終了や、モバイルOSによるプロセス終了などでは実行されない可能性があります


3.2 スロットセーブとオートセーブ

Save Design では、典型的な「スロット1〜3」のように番号で分けられたスロットごとのセーブデータを柔軟に扱うことができます。

そして、番号だけではなく識別子(文字列)による保存にも対応しています。

たとえば、タイトル画面でプレイヤーがスロットを選択した場合には番号を使ってデータを読み込み、 ゲームプレイ中のチェックポイントなど自動保存用途では "autosave""checkpoint-3" のような文字列を使うと便利です。

ただし、Save Design は自動的にデータを保存するわけではありません。

これは意図的な設計であり、開発者が意図しないタイミングでデータが書き換わるといった 予期せぬトラブルやデバッグ困難な状態を防ぐためです。

Save Design は、「保存したいときに、明示的に保存する」シンプルなルールに従って動作します。

裏で勝手に動作したり、開発者が気づかないタイミングで I/O が発生することはありません。

そのため、Save Design の仕組みを意識せずにプロジェクト全体を安全に保つことができます

この設計方針により、開発者は「今、どのデータが保存されているのか」「いつ保存されたのか」を常に把握でき、 セーブ処理をコントロールしやすく、バグの発生原因も明確になります。


3.3 SlotMetaData の活用と注意点

SlotMetaData は、セーブスロットの一覧画面や選択画面などに表示するための、スロット情報のメタデータを保持するための仕組みです。

プレイヤー名やプレイ時間、保存日時など、ゲーム内容とは直接関係しないが UI 表示に必要な情報を格納するのに適しています。

セーブスロットを使わないようなゲームでは SlotMetaData をあえて定義しないことも可能です。


✅ SlotMetaData の用途例

  • セーブスロット選択画面での「プレイヤー名」「現在の章」「プレイ時間」の表示
  • 「前回保存日時」の記録
  • スロットが空かどうかを判断するためのフラグ

SlotMetaData のデータは SD.Save.Slot(...) でスロットデータを保存するときに一緒に保存されます。

SD.Load.SlotMeta(...) で読み込んで表示や分岐に使用します。


⚠️ 注意:SlotMetaData は常に新規インスタンスで保存される

SlotMetaData は他のデータ(SharedData や SlotData)とは異なり、 既存のデータに変更を加えて保存するという使い方ができません

毎回新しいインスタンスを生成し、すべてのフィールドを書き込み前に明示的にセットする必要があります

たとえば、前回読み込んだ SlotMetaData を取得して一部のフィールドだけ変更して保存しようとしても反映されません。

if (SD.Load.SlotMeta(slotIndex, out var meta))
{
    meta.value += 100; // ❌ メタ情報の値を書き換えても保存されない
}

この設計は、メタ情報は他のセーブデータの値から自動的に生成されるべきという考えに基づいています。

SlotMetaData は IBeforeSaveCallback を使って下記のように実装することを推奨します。

[SlotMetaData, System.Serializable]
public class SlotMetaData : IBeforeSaveCallback
{
    public string playerName;
    public int level;
    public int money;
    
    void IBeforeSaveCallback.OnBeforeSave()
    {
        // 他のデータの値を使ってフィールドを初期化する
        var player = SD.Slot.Player;
        playerName = player.name;
        level = player.level;
        money = player.money;
    }
}

3.4 TempData を使った一時的な状態管理

TempData は、セーブファイルとして保存されない 一時的な状態を管理するための仕組みです。

一度ゲームを終了したり、スロットを切り替えたりすると自動的に破棄されるため、 「このセッション中だけ覚えておきたい値」「ロードし直したらリセットしたいフラグ」のような場面に最適です。

たとえば、以下のような用途に TempData は活用できます:

  • 一時的なアイテム取得状態やダイアログ進行状況
  • バトル中の一時的なリザルト、スコア履歴
  • ロード直後にだけ発火する一度きりの処理フラグ

TempData を定義するには、通常のデータクラスに [TempData] 属性を付けるだけです。

さらに TempDataResetTiming を指定することで、どのタイミングでリセットされるかを細かく制御できます。

たとえば、OnGameStart を指定すればゲームの起動時に、 OnSlotDataLoad を指定すればスロットを切り替えたときにリセットされます。

この仕組みにより、「初期化し忘れて前回の状態が残ってしまう」といったありがちなバグを防止でき、 一時的な状態管理をより安全かつ簡潔に実装できます。

なお、デフォルトのリセットタイミングは TempDataResetTiming.OnSharedDataLoad です。


3.5 初期化処理の活用方法

Save Design では、新規ゲーム開始時やセーブデータが存在しない状態での開始処理として、 Initialize を使った 明示的な初期化が可能です。

初期化には SD.Initialize.Shared()SD.Initialize.Slot() の2種類があり、 それぞれ共有データ/スロットデータに対して、空のインスタンスを生成します。

主に以下のようなタイミングで使用します:

  • 「はじめから」や「ニューゲーム」 など、完全に新しいセーブデータを作成する場面
  • 新規スロットを作るとき
  • テスト時にデータを一度リセットしたいとき

この処理を呼ぶと、現在メモリに読み込んでいるセーブデータ(該当スロットや共有データ)のインスタンスは破棄されます。(セーブデータファイルが削除されるわけではありません)

また、初期化時に特定の処理を走らせたい場合は、対象のデータクラスに IAfterInitializeCallback インターフェースを実装することで、 インスタンス生成直後に一度だけ OnAfterInitialize() が呼ばれます。

このコールバックは、たとえば以下のような用途に向いています:

  • フィールドの初期値を設定する(セーブ対象外の値も含めて)
  • 他のデータ構造を参照して初期状態を構築する

これにより、新規開始時だけに発生する特殊な初期処理を安全に分離でき、 再ロード時には実行されないという保証が得られます。

ゲームの状態を「明示的に初期化」できるということは、 セーブフローにおけるバグを大幅に減らし、テストもしやすくなるという大きな利点につながります。

IAfterInitializeCallback[SharedData][SlotData] のどちらかを付与したクラスでのみ処理されます。

詳細な仕様は、スクリプトリファレンスの「1.1 IAfterInitializeCallback」をご参照ください。


3.6 読み書き時のコールバックの活用方法

Save Design では、セーブ直前やロード直後に処理を挟みたい場合に、 IBeforeSaveCallback および IAfterLoadCallback という2つのインターフェースが利用できます。

これにより、セーブ対象のデータクラスごとに、任意の前後処理を明確に分離して記述できます。


IAfterLoadCallback の活用例

このインターフェースを実装したデータクラスでは、 セーブデータをロードした直後に OnAfterLoad() が自動的に呼び出されます。

この仕組みは以下のような処理に便利です:

  • ロード後に初期化が必要なランタイム専用フィールドの再構築
  • キャッシュや辞書の再構成(例:ID → オブジェクト辞書)
  • サウンド/UIの反映など、見た目に関わるデータの更新トリガー

IBeforeSaveCallback の活用例

セーブ直前に OnBeforeSave() が呼ばれることで、保存内容を意図的に整える処理が記述できます。

たとえば:

  • 使用中のオブジェクトの状態を保存用構造に変換
  • 逆参照や不要なデータを除外
  • 最新のタイムスタンプやプレイ時間を更新

このように、データ整形やログ記録などを事前に実行することで、より正確で安全なセーブ内容を保証できます。


💡 なぜコールバックが便利なのか?

コールバックを使えば、「セーブ処理を呼ぶ場所」や「ロード結果を渡す処理」に直接処理を書かなくて済みます。 その結果、セーブ処理の呼び出し元は常にシンプルな1行のままに保たれ、 細かな処理ロジックはそれぞれのデータクラスに閉じ込めておけるというメリットがあります。


🔔 他のデータに依存する場合の注意

Save Design では、複数のデータ(SharedData, SlotData, TempData など)を別々のクラスとして定義できますが、 データの初期化や読み書きの順序は、特別な指定をしない限り保証されません。

そのため、コールバック(IAfterLoadCallback, IBeforeSaveCallback, IAfterInitializeCallback など)の中で 他のデータの値を参照したい場合には注意が必要です


❌ 順序が保証されていない場合に起きる問題の例

たとえば、GameSettings(SharedData)内で、Profile(SharedData)の情報を参照して初期設定を行いたいとします:

[SharedData, Serializable]
public class GameSettings : IAfterLoadCallback
{
    public int volume;

    void IAfterLoadCallback.OnAfterLoad()
    {
        volume = SD.Shared.Profile.defaultVolume; // ← ここでエラーになる可能性
    }
}

このとき、Shared.Profile がまだ読み込まれていなければ NullReferenceException が発生するか、 意図しない初期値が使用されてしまう恐れがあります。


✅ Save Design の解決策:依存関係の明示

Save Design では、データ定義属性に Type を渡すことで 依存関係を明示的に宣言できます。

[SharedData(typeof(Profile)), Serializable]
public class GameSettings : IAfterLoadCallback
{
    ...
}

このように記述すると、Save Design は GameSettings の読み込みや初期化を 必ず Profile よりも後に実行するよう処理順を調整します。

✅ 依存先のクラスも同じデータ種別である必要があります(例:SharedData → SharedData)
[TempData] の場合は TempDataResetTiming も一致させる必要があります

この仕組みにより、コールバック内で他のデータに安全にアクセスできるようになり、 複雑な依存関係を持つプロジェクトでも安心してデータ設計が行えます。


ロールバックについて

Save Design では、初期化・読み込み・保存の各処理中に例外が発生した場合、データと副作用を可能な限り元の状態に戻す * ロールバック機能* を提供しています。

これにより、処理が失敗してもセーブデータの整合性を維持しやすくなります。

ロールバックの呼び出し順序

ロールバックは コールバックが実行された順の逆順(LIFO) で呼び出されます。

リソース確保や外部処理の片付けを安全に行うため、最後に行った操作から順に巻き戻されます。

各処理における挙動

  • 初期化・読み込み処理

    1. まず、該当するロールバックインターフェースが実装されていれば、コールバックによる副作用を逆順でロールバック します。
      (例: UI の更新を戻す、ファイルハンドルを閉じる、イベント購読を解除する など)
    2. その後、各データクラスのインスタンスを古いものに差し戻し、既知の良好な状態に復帰させます。
      新しく生成されたインスタンスは破棄され、直前まで保持していた安定したデータが再利用されます。
  • 保存処理
    保存では新しいインスタンスを生成せず既存データを直接操作するため、データ自体はそのままです。
    例外が発生した場合は、外部リソースや UI などの 副作用のみロールバックされます。

専用インターフェース

ロールバック処理を実装する場合は、以下の専用インターフェースを利用します。

  • IAfterInitializeRollback.OnAfterInitializeRollback
  • IAfterLoadRollback.OnAfterLoadRollback
  • IBeforeSaveRollback.OnBeforeSaveRollback

これらは既存のコールバックインターフェース(IAfterInitializeCallback / IAfterLoadCallback / IBeforeSaveCallback )で副作用を持つ処理を行っている場合にのみ実装してください。未実装でも従来通り動作します。

注意点

  • ロールバックは ベストエフォート で実行されます。すべての副作用を完全に元に戻せる保証はありません。
  • 特に外部リソースとのやり取りは状況によって正しく復帰できない場合があります。
  • 推奨: コールバックやロールバック関数内では、例外を発生させない堅牢な実装を行ってください。

4. よくあるユースケース

4.1 データ構造の変更にどう対応するか

ゲームの開発が進むにつれて、セーブ対象となるデータクラスに フィールドを追加したり削除したり することは珍しくありません。

リリース前であればどのような変更でも問題は起きませんが、すでにリリースしているゲームの場合は注意が必要です。

JsonUtilityMessagePack を使う場合、読み込まれるセーブデータに存在しないフィールドはデフォルト値で補完されます。

一方で、過去のバージョンのセーブデータに不要なフィールドが残っていたとしても、それを無視して読み込むことができます。

より複雑な変更(フィールド名の変更、型の変更など)が発生する場合は、以下のような対応が推奨されます:

  • データに Version フィールドを追加し、読み込み後にバージョンによって処理を分岐する
  • IAfterLoadCallback を使って、古い構造を新しい構造に変換する
  • 古いデータを一度破棄し、Initialize で新しい形式のデータを再生成する

こうしたカスタム処理は Save Design が提供する自由な設計の中で実装可能です。

プロジェクトの成長にあわせて、セーブ構造も安全に進化させられる仕組みを整えておくことが重要です。

以下は、一部のフィールドの型を変更する例です。

[SlotData, Serializable]
public class ExampleData : IAfterLoadCallback
{
    public int version;    // セーブデータのバージョン
    public int oldField;   // 古いフィールドをリネームしたり削除してしまうとデータが読み込めないため、そのまま残す
    public float newField; // 型を変更した新しいフィールド

    void IAfterLoadCallback.OnAfterLoad()
    {
        // 古いデータを読み込んだときのマイグレーション処理
        if (version == 0)
        {
            newField = (float)oldField;
            version = 1;
        }
        
        // さらにバージョンが変わった場合
        if (version == 1)
        {
            ...
        }
    }
}

4.2 ExceptionPolicy による例外の扱い方

Save Design では、初期化処理や読み書き処理中に例外が発生した場合の扱いExceptionPolicy 列挙体で制御できます。

これにより、利用者はアプリケーションの要件に応じて挙動を柔軟に切り替えることが可能です。

選択できるポリシー

  • Throw
    発生した例外をそのままスローします。呼び出し元で try-catch を行い、例外処理を細かく制御したい場合に利用します。

  • LogAndSuppress
    発生した例外を UnityEngine.Debug.LogException でログ出力した上で握りつぶします。
    以前の OnGameDataError に近い挙動を再現でき、既存プロジェクトからの移行に適しています。

  • Suppress
    発生した例外を握りつぶします。ログも出力されないため、ユーザーにエラーを見せたくない場合や、失敗を静かに無視したい場合に利用します。

デフォルトの挙動

初期化関数や読み書き関数は、デフォルトでは ISaveDesignConfig から取得した ExceptionPolicy に従って例外を扱います。

グローバルに共通のポリシーを設定することで、通常は統一された例外処理ポリシーを適用できます。

特殊ケースへの対応

各関数には ExceptionPolicy を引数に受け取る オーバーロード が用意されています。

これを利用すれば、普段とは異なるポリシーで例外を処理したい特殊なケース(例: 一部の保存処理だけは必ず Throw したい)にも柔軟に対応できます。

共通の動作

  • どのポリシーを選択しても、ベストエフォートでロールバック処理が実行されます。
  • 初期化・読み込みでは副作用のロールバック後にデータが既知の良状態に復帰し、保存では副作用のみがロールバックされます。
  • ロールバックが完全に成功する保証はありません。特に外部リソースとのやり取りがある場合は注意してください。

移行ガイド

以前は partial void OnGameError(Exception ex) に例外が渡されていましたが、本バージョンからは削除されました。

既存コードを移行する場合は、ExceptionPolicy.LogAndSuppress を設定することで従来に近い挙動を再現できます。


4.3 一部のデータだけを初期化したい

Initialize は指定したデータの種類であればすべて初期化してしまいます。

一部のデータだけを初期化したい場合は次のような方法があります。

[SharedData("Settings"), Serializable]
public class Audio : IAfterInitializeCallback
{
    public event System.Action<float> OnVolumeChanged;
    [SerializedField] float volume;

    public void SetVolume(float volume)
    {
        this.volume = volume;
        OnVolumeChanged?.Invoke(volume);
    }

    public void Initialize()
    {
        SetVolume(1f);
    }

    void IAfterInitializeCallback.OnAfterInitialize()
    {
        Initialize();
    }
}

SD.Shared.Settings.Audio.Initialize();

5. セキュリティと暗号化(オプション)

5.1 暗号化を有効にする方法(エディタツール)

Save Design には、Unity エディタ上で簡単に暗号化設定を行える専用ツールが用意されています。 次の手順で暗号化を有効にすることができます:

  1. Unity のメニューバーから 「Tools > Save Design > Encrypt Settings」 を開きます。

  2. 表示されたウィンドウに、AES鍵(32文字)と HMAC鍵(32文字)を入力します。 ※ どちらもセキュアなランダム文字列を推奨します。

  3. [Generate Encryptor.cs] ボタンを押すと、Encryptor.cs という暗号化スクリプトが自動生成されます。

このスクリプトには Save Design のランタイム処理にフックされる暗号化ロジックが含まれており、 その後に行われるすべてのセーブは自動的に暗号化され、ロード時には HMAC による改ざん検知が行われるようになります。


5.2 独自暗号化処理の組み込み方法

より細かい制御や特殊な暗号方式を使いたい場合、Save Design では暗号化ロジックを自前で差し替えることも可能です。

Encryptor 属性を付与したクラスに Encrypt 関数と Decrypt 関数を実装するだけで独自処理を組み込めます。

using SaveDesign.Runtime;

[Encryptor]
public static class CustomEncryptor
{
    public static void Encrypt(ref byte[] data)
    {
        ...
    }

    public static void Decrypt(ref byte[] data)
    {
        ...
    }
}

これらのメソッドは、それぞれ 保存前/読み込み後に呼び出されるフックポイントです。

ここで data に対して任意の暗号化・復号処理を施すことで、独自のセキュリティポリシーに対応できます。

たとえば:

  • 業務用タイトルで内部形式に基づいた特殊な暗号ロジックを導入する
  • セーブごとにランダムなIVやSaltを用いた複雑な暗号スキームを構築する
  • 複数ファイルをまたいだ HMAC チェックを導入する

といったことも可能です。

このように、Save Design の暗号化機能は 簡単に導入できる標準ツールと、高度な組み込みが可能な拡張性の両方を兼ね備えています。

セーブデータの信頼性を高めたい場合は、ぜひ導入を検討してください。


⚠️ 注意:暗号化処理を組み込む場合はゲームのリリース前に済ませてください

平文のデータは暗号化処理を組み込んだ状態では読み込めません。同じく暗号化されたデータは暗号化処理が組み込まれていない場合は読み込めません。

ゲームをリリースした後に暗号化処理を組み込んだり、逆に暗号化処理を削除してしまうとデータが読み込めなくなります。


📄 ライセンスとサードパーティ表記

Save Design は、下記のサードパーティ製ライブラリを利用可能な構成になっています。 これらはすべて MIT ライセンスの下で配布されています。


■ サードパーティライブラリ(オプション)

ライブラリ名ライセンス用途
MessagePack for C#MIT高速バイナリシリアライザー(任意使用)
Newtonsoft.JsonMIT高機能な JSON シリアライザー(任意使用)
MemoryPackMIT高速バイナリシリアライザー(任意使用)

これらのライブラリは Save Design に同梱されておらず、オプション機能として利用者が導入・管理する必要があります。 そのため、ビルドに含めるかどうかは開発者の判断に委ねられます

また、これらをプロジェクトに組み込む場合は、ライセンス条件に従い、必要に応じてエンドユーザー向けにライセンスの表記を行ってください。


■ Save Design のライセンスについて

本アセット「Save Design」自体のコード・構成物はすべて商用利用可能な Unity Asset Store EULA に基づいて提供されています。 詳細は Unity Asset Store エンドユーザーライセンス をご参照ください。