Unity開発メモ #9 セーブスロットシステムの実装
EasySave3によるセーブスロットシステムの実装
EasySave3というアセットでヘホゲのセーブシステムを構築しているという情報を得たのでヌケルさんもこちらのアセットを使用してシステムを構築しています
とても簡単にセーブ & ロードが実装できてやさしいアセットだと思います!
ただし値段は円安でやさしくない
変数のセーブとロード
EasySave3はオートセーブやゲーム内のオブジェクトの位置情報を保存するなど高度な機能があるが、今回のゲームでは変数さえ保存できればいいのでとても単純です。
各セーブスロットに変数をセーブする
ES3.Save関数を使います
https://docs.moodkie.com/easy-save-3/es3-api/es3-methods/es3-save/
public static void Save<T>(string key, T value, string filePath, ES3Settings settings)
<>にはデータ型、第1引数でセーブデータのキーを設定し、第2引数にはキーに格納する具体的なデータを入れ、第3引数はオプショナルで設定した場合は書き込み先のファイル名となります
下はインスペクターで指定した数字のスロットにゲームの進捗状況を管理する変数を格納しているProgressDataクラスの変数をセーブするコードです
ついでにセーブ時間も記録しています
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class SaveGame : MonoBehaviour
{
public int saveSlotNumber; // インスペクターで指定した1~3の数字のスロットにゲームの変数(ProgressData)をセーブする
// セーブボタンをクリックしたときに呼び出す
public void Save()
{
string saveFileName = "save" + saveSlotNumber + ".es3"; // 例 saveSlotが1のときはsave1.es3にデータを保存する
DateTime now = DateTime.Now; // 現在時刻の取得
ES3.Save<int>("NowDay", ProgressData._NowDay, saveFileName);
ES3.Save<int>("NowCharacter", ProgressData._NowCharacter, saveFileName);
ES3.Save<bool>("Night", ProgressData._Night, saveFileName);
中略
ES3.Save<int>("SavedMonth", now.Month, saveFileName);
ES3.Save<int>("SavedDay", now.Day, saveFileName);
ES3.Save<int>("SavedHour", now.Hour, saveFileName);
ES3.Save<int>("SavedMinute", now.Minute, saveFileName);
ES3.Save<int>("SavedSecond", now.Second, saveFileName);
}
}
各セーブスロットにセーブされた変数を出力する
セーブファイルからデータを取り出すときはES3.Loadを使います
https://docs.moodkie.com/easy-save-3/es3-api/es3-methods/es3-load/
public static T Load<T>(string key, string filePath, T defaultValue, ES3Settings settings)
<>には同じくデータ型を、第1引数でロードするデータのキーを設定し、第2引数では読込先のセーブファイルの名称を指定、defaultValueでは何も保存されていない場合に読み込むデフォルト値を設定します
ES3.Loadで読み取ったデータをゲームオブジェクト上のTextに反映させたりするようなコードを書けば、ゲーム画面上にどのようなセーブデータが保存されているかを表示することができるので、セーブスロットシステムの外観が整います
アイコン・日付表示・テキスト・進捗バー・セーブ時間・登録者数・やみ度の表示を各セーブスロットに保存されたデータをもとに表示しています
ロードしたデータをゲームに反映する
各セーブスロットのロードボタンを押したときに下記の処理を実行すれば実現できます
・ロードSceneに飛ばす
・ES3.Loadを使って必要な変数をゲーム内の進捗管理変数に代入する
・再開すべきSceneに戻す
コードにすると下のような感じです
ロードときはセーブファイルが存在するか確認してなければ処理を止める処理を入れておいた方がよさそうです
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.SceneManagement;
public class LoadGame : MonoBehaviour
{
public int saveSlotNumber; // インスペクターで指定した1~3の数字のスロットからゲームの変数(ProgressData)をロードする
// ロードボタンをクリックしたときに呼び出す
public void Load()
{
string saveFileName = "save" + saveSlotNumber + ".es3"; // 例 saveSlotが1のときはsave1.es3のデータをロードする
if(!ES3.FileExists(saveFileName)) // ファイルが存在していない場合
{
return;
}
SceneManager.LoadScene("Loading"); // ロード画面に飛ばす
// データをロードしProgressDataに格納していく
ProgressData._NowDay = ES3.Load<int>("NowDay", saveFileName, defaultValue: 0);
ProgressData._NowCharacter = ES3.Load<int>("NowCharacter", saveFileName, defaultValue: 2);
後略
ファイルの保存場所について
LocationをFile、DirectoryをPersistent Data pathに指定すると、C:/Users/Owner/AppDataに保存ファイル(es3ファイル)が作成される。
多くのUnity製アプリがここを保存場所にしており、安全と思われる。 (Unity標準ログ出力もここ)
LocationをDataPathにした場合はゲームフォルダ内の「ゲーム名_Data」に保存される。こちらの方が場所はわかりやすいか
Persistent Data pathの方が面倒ゴトが少なく安全そうなので、そちらで実装すると思います
他にもセーブファイルの暗号化機能とかもあるのですが、オンラインゲームではないのでセーブデータを改造して遊んでもらって全くかまわないので、あえてしないと思います
PlayerPrefsによるコンフィグ設定の保存
PlayerPrefはUnityにデフォルトで備わったデータ保存機能ですが、
種種の理由からセーブファイルの保存にはあまり推奨されていなようです
ただ言語設定やマスター音量等のコンフィグ設定であれば使ってもよさそう説も見かけたので、それぞれ保存するようにしてみました
以下はスライダーバーUIにアタッチするだけで、マスター音量の変更に加えて、変更した値を保存してゲーム再開時にその音量でプレイできるように作成したコードです
これで前回音量を下げたのにゲームを再開したら爆音でBGMがなるという事故が防げます、たぶん
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MasterVolumeControl : MonoBehaviour
{
public Slider masterVolumeSlider;
private const string masterVolumeKey = "MasterVolume";
private void Start()
{
// PlayerPrefsから保存されたマスター音量の値を読み込む
float savedVolume = PlayerPrefs.GetFloat(masterVolumeKey, 1f);
masterVolumeSlider.value = savedVolume;
SetMasterVolume(savedVolume);
// スライダーの値が変更されたときに呼び出されるイベントを登録
masterVolumeSlider.onValueChanged.AddListener(OnMasterVolumeChanged);
}
private void OnMasterVolumeChanged(float volume)
{
// スライダーの値が変更されたときに呼び出される関数
SetMasterVolume(volume);
}
private void SetMasterVolume(float volume)
{
// マスター音量を設定する
AudioListener.volume = volume;
// PlayerPrefsにマスター音量の値を保存する
PlayerPrefs.SetFloat(masterVolumeKey, volume);
PlayerPrefs.Save();
}
}
実績やアーカイブ(いわゆる回想)に関してはPlayerPrefsではなく、セーブスロットとは別のグローバルセーブデータをEasySave3で作成して管理しようと思います