Unity2021.3.39f1
概要
- ゲーム制作初心者が手探りで考えたセーブ・ロード仕様のまとめ
- 便利なアセットを使わず1から実装したい人向け
- ベストプラクティスではないと思うので記載情報は鵜呑みにしないでください
(もっとこうしたほうが良いよ!みたいなコメントほしいです…!)
ここに記載した仕様で現在開発中の3Dアクションエロゲ「にゃうびぃの大冒険」が動いています。
要件
- 任意のタイミングでセーブ(特定のファイルに文字列書き込み)したい
- タイトルメニューのシーンが開かれたタイミングだけロードしたい
- 進行中のデータはデータクラスのようなもので管理されメモリ上に一時保存したい
- シーン切替時はデータクラスのようなものからデータを読み取って進行情報を反映させたい
要件の補足をすると、
1.任意のタイミングでセーブ(特定のファイルに文字列書き込み)したい
セーブデータをどこで保存するか?を考えた時に出てきた候補として、Windowsのレジストリに直接書き込む、実行ファイル内に埋め込む、外部ファイルとして保存するの3パターンが浮かんだ。
私の作ってるゲームは都度更新したゲーム本体の実行ファイルを配布して遊んでもらいたいと考えていたため、取り回しの悪い実行ファイル内に埋め込む案は却下。
Windowsのレジストリに直接書き込む方法はセーブデータの移行の手間は無いが、仮にセーブデータが壊れてしまった際の復旧が難しいだろうと思って却下。(あとなんかレジストリいじるの怖い)
消去法で外部ファイルとして保存する方法に決定。
2.タイトルメニューのシーンが開かれたタイミングだけロードしたい
ロードは最初の1回だけで、あとはオンメモリ上の情報でよろしく処理したかったのでこのような要件に設定。
3.進行中のデータはデータクラスのようなもので管理されメモリ上に一時保存したい
セーブデータはリアルタイム保存はされず、ゲームオーバーやゲーム終了したらセーブ地点からやり直しとなるようにしたかったため。
4.シーン切替時はデータクラスのようなものからデータを読み取って進行情報を反映させたい
進行情報はメモリ上に一時保存されるためロードはせず、シーンを跨いでデータを保持するデータクラスのようなものからデータを読み取って各コンポーネントに値を渡すことで進行情報を反映するようにしたい。
シーケンス図で表すと以下のようになる。
仕様
データクラス
仕様を決めるにあたって考慮したかったのが、シーン間の依存性をなるべく排除したいことだった。
シーンを跨いだデータのやり取りをする場合、よく見かける方法としてはPlayerPrefs、DontDestroyOnLoad、ScriptableObjectがあった。
PlayerPrefsはレジストリを書き換えるので却下。
DontDestroyOnLoadはクラスをシングルトンにして破棄されないように初期化する処理を何処かで行う必要があるが、ゲーム開発中に任意のシーンを実行する時に問題が出た。「初期化用のシーンを読み込んで、そこからゲームシーンを読み込んで…」みたいな手続きをする必要が出て管理が面倒になってしまったので利用を断念。
最後に残ったScriptableObjectはシーンを跨いでもデータが保持される代わりにそのままではセーブの仕組みとしては使えない。(開発中は良いが、ビルドするとゲーム起動時に毎回値がリセットされてしまう?)
しかし、ScriptableObjectを一時保存先として利用して永続保存は外部ファイルにしてしまえば問題ないと判断したためデータクラスとしてScriptableObjectを採用した。
実際に作ったScriptableObjectはこんな感じ。
簡単に補足すると
- 初めてゲームを起動したときにInitValueの値でScriptableObjectの値を上書きしてセーブデータ作成
(意図しない値が混入しないよう対策。補足で解説) - 既にセーブデータがある状態でゲームを起動したときはセーブデータをロード
- つづきからを選択した場合はGameDataのシーンを読み込んでそのままスタート
- はじめにを選択した場合はGameDataとEventDataの値だけ初期化してスタート
(ボリュームとかキーコンフィグの値はそのまま維持したいため)
EventDataはDictionaryのように見えるがそのままだとInspectorに表示されないので、List<EventData>という型を作って振る舞いをDictionaryに似せ、EventData型の中でNameとFlagを定義することで実現している。
セーブデータ
ScriptableObjectはクラスなので、JsonUtility.ToJson()でクラスのプロパティをそのままJSON文字列にシリアライズしてファイルに書き込んでセーブしている。
ロードはJsonUtility.FromJsonOverwrite()するだけ。
補足
- ScriptableObjectはUnity Editorで入れてた値がビルドした実行ファイルにも引き継がれるっぽいのでゲーム起動タイミングとかで初期化する必要がありそう
- ゲーム本編のセーブデータとオプション設定のセーブデータは分けたほうが良いかも
私の場合は一緒にしてしまったので、はじめからを選んだときにオプション設定は初期化しないような処理を入れてます