Unityゲーム制作奮闘記 - 設計 - セーブシステム

##はじめに

趣味でやっているUnityゲーム制作の理解と備忘の為に記録として残します。（まぁ成果物代わりみたいな。。）
Unityは特に設計関連の記事があまりないので、この機会に書いてみようと思います。

悪戦苦闘しながら作ってるので、内容は色々穴があるかもしれませんが。。
今時点ではこれがベストだと思っていますが、同時にもっと良い方法があるのでは？とも思っています。
素人がなんか頑張ってるな、くらいの温かい面持ちで見て下さい笑
ちなみに「こういう方法もあるよ」などあれば、ぜひ教えて下さい！

##1. セーブシステム概要

今回イメージしているのは、冒険の書型セーブシステム（仮称）です。
プレイの断面を自由に複数保存でき、好きなデータをロードして再スタート出来るイメージ。
ロード画面では、セーブ時のプレイ画面をサムネイル表示させて、どんなデータだったか解るようにします。
なお今回作成しているのはオフラインゲームなので、データはローカルに保持させます。

（ざっくりイメージはこんな感じ。まあ割とありそうなやつです）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401336/00b988bd-22fb-c63f-f15f-503a8216e3be.png)


##2. 検討ポイント

今回のセーブシステムを実装するにあたって、特に検討が必要なのが以下だと思っています。

* データの検討
* セーブシステムのクラス設計
* セーブの検討
* ロードの検討

それぞれを以下で掘り下げて考えたいと思います。

##3. データの検討

まずデータですが、今回のシステムでは大きく2種類必要になると思います。

1. システムデータ（ゲーム全体の設定/パラメタを保持するクラス）
2. プレイデータ（それぞれのプレイデータを保持するクラス）

前述のとおり、プレイデータはあくまでのゲーム断面のパラメータなので、
システム全体のパラメータ（例えば音量設定とか）は別に保存しておくべきと考えます。

またプレイデータは、**ロード画面でロードした際に読み込まれる**イメージなので、
ロード画面に表示する各プレイデータの付随情報（セーブ日時やサムネイル画像）は
別に保存しておく必要があると考えており、これもシステムデータに保持させます。

システムデータは1ファイルのみ。
プレイデータはセーブ数に応じてファイルがどんどん増える。
ただ今回は最大30くらいにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401336/d6f7b173-2986-fdb7-3df9-351fde00aab1.png)


##4. セーブシステムのクラス設計

クラス設計、、圧倒的経験不足により正直よく解りません！笑
色々調べてはみましたが、謎は深まるばかりです←
とりあえずメンテナンス性が良い設計を心がけるのが大事というのは解りました。※出来るとは言ってない
なのでまずはダメ元で思うように作ってみて、今後に活かしたいと思います。

なおシングルトンはあまり使わない方が良いって記事も目にしますが、
今回はゲーム内データを一括管理するので、
シングルトンで構わんのだろう？という心持ちで採用しております。

![SaveSystem.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401336/588f2768-2617-45fd-2845-0c8e87c1c4a1.png)

ちなみにこの設計は白黒_unityさんという方の動画を参考にしています。
めっちゃ解りやすいのでそちらも是非見てみてください！←

SaveSystem/PlayData/Systemdataはモノビヘイビアを継承しません。
これはシーン遷移してもインスタンスを保持するためです。
SaveSystemは自分と各データのインスタンスを保持します。

##5. セーブの検討

セーブの仕組み自体は色々な方が情報を公開してくれており、
インスタンスをJSONに変換して保存するだけで良さそうです。
圧縮や暗号化もかけられるので、容量節約のために圧縮は組み込みたいと思います。
今回は個人ゲームなので暗号化はどっちでも良いかなという感じです。
（チートしたければすればええやん！たぶん楽しくないけどな！という感じ）

注意点を書いておくと、PlayDataとSystemDataのクラスには[System.Serializable]を記載しておく必要があります。
あと、この方法は辞書型のデータはそのまま保存できないっぽいです。（ごにょごにょすれば保存できます）
また多次元配列もそのままでは保存できないらしい。（こっちは試してないので不明ですが、最初から一次元にしといた方が楽そう。。）

セーブ時の一番の考慮事項は、サムネイルに表示する画像かなと思っています。

この仕組みはセーブ画面への移動ボタンがクリックされた際に、
先にスクリーンショットを取得してから、画面遷移という感じで考えています。
⇒メソッド名は忘れましたが、スクリーンショットはUnityの機能で取れます。

スクリーンショットを取った後ですが、まずは解像度を落とします。
そのままでも勿論使えますが、セーブ数分保存すると結構な容量になりそうなので、
ごにょごにょして解像度を下げて保存します。
（解像度を落とすと色が飛んで色々大変だったんですが、その辺りは機会があれば。。）

次に画像をテキスト化します。
ここは画像をそのまま保存でも良いかもしれませんが、テキスト化してそのままシステムデータに保存しておけば
処理もシンプルだし、画像を別で管理する必要がないので楽かしら？という考えです。

このテキスト化した画像はtmpとしてインスタンスのプロパティに入れておいて、
実際のセーブ実行ボタンが押された際に、システムデータの「プレイデータ一覧」的な所に書き込むイメージです。

なおセーブボタンですが、それぞれのボタンにインデックスを振っておき、
押されたボタンに対応するプレイデータとして保存します。（playData001とか）
このインデックス番号で「サムネイルのゲームオブジェクト」「プレイデータ」「システムデータ」を紐づけて管理します。

**【セーブ画面遷移時】**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401336/7ced7f2a-f86c-c504-1dce-76bbf3abb66a.png)

**【データセーブ時】**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401336/c99f8911-fd74-ad4f-c78f-48ca4f88d2be.png)



##6. ロードの検討

ロード自体の処理も簡単で、セーブでやった処理を戻すだけです。
自分の場合は解凍して、テキストをインスタンス化すればOKです。

ロードでの検討項目は、ロード画面にプレイデータの情報をサムネイル表示させる部分。。
まぁゲーム起動時に読み込ませるしかない訳ですが。。（どのくらい時間かかるかな。。）

肝はテキストとして保存されている画像データをスプライトに変換する部分でしょうか。
処理としては変換してサムネイル用のオブジェクトに登録するだけですが、、
今回はプレイデータの最大数が30なので、多分そんなに掛からない筈。。
（遅くなるのを恐れて最大数を減らしたと言っても過言ではない。。）

あ！あと明記してませんでしたが、セーブ/ロード画面は同じ画面を使います。
Flagを立てて、ロードorセーブで処理などを変えるイメージ。
（なのでロード画面にサムネイルを描画すると、必然的にセーブ画面も描画されます）

**【起動時内部処理】**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401336/494b9978-c714-2f1b-9d23-5f9bf7a4ac7e.png)

**【データロード時】**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401336/220edb32-70de-18bc-b70a-1e3fc4c2e66e.png)



## おわりに

セーブシステムの検討は以上です。
スマートなやり方なのかは一旦置いておくとして、
これでイメージするセーブシステムは作成出来ます。

でも、もっといいやり方が有る気するなぁ。。
結構メジャーな方式な気はするけれど、他の方々は一体どんな風に設計/実装しているのだろう。。


以上