前回でコイン獲得時のアニメーション処理を作り、ゲームのメイン処理ができあがりました。
前回の記事↓
ですが、インフレ系クリッカーゲームは一度のプレイで完結しないため、セーブシステムが欠かせません。
ここでは、PlayerPrefsやインターフェースを用いてセーブ・ロードシステムを開発し、遊べるゲームとしての完成を目指します。
unityゲームのセーブ・ロードシステムの作り方
クリッカーゲームは根を詰めてプレイするというより、気が向いた時に開いてポチポチやって。というスタイルです。
そのためにはオートセーブ機能、オートロード機能が不可欠です(毎回所持金0円、羊0頭から始めたくないですよね)
では、まず要件を詰めましょう。
今回のゲームで、セーブ・ロードをしたいデータはというと
- ①所持金
- ②各羊の頭数
だけになると思います。
(ゲームを終了させる前に出ていた羊毛(wool) は? とおっしゃる方もおられるかとは思いますがさすがにそこまでは対応しない事とします(もちろんやればできますが))
要件も決まりましたので、SaveとLoadをするオブジェクト「SaveLoadManager」を作りましょう。
Sceneビュー上でCTRL+SHIFT+Nキーを押してオブジェクトを新規作成し、
名前を「SaveLoadManager」に、AddComponentで新規スクリプトを追加。
こちらも名前を「SaveLoadManager」にしておきます。
では、SaveLoadManagerスクリプトを以下のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SaveLoadManager : MonoBehaviour { //保存対象①所持金用 [SerializeField] private Wallet wallet; //保存対象②羊の頭数用 [SerializeField] private Shop shop; private void OnApplicationQuit() { Debug.Log("セーブ"); } void Start() { Debug.Log("ロード"); } } |
メンバ変数には、保存対象としている所持金を持っている Walletオブジェクトと、羊の頭数を持っているSheepButtonを複数管理するShopオブジェクトが必要です。
まず、ゲームを終了する時にオートセーブさせたいのですが、そういうときには
OnApplicationQuit というメソッドを使います。
これは、StartメソッドやUpdateメソッドと同じ、Unity側から適宜呼ばれるメソッドの一つで、アプリケーション終了時に呼ばれるメソッドになります。
そのため、一字一句、大文字小文字含め完全にこの名前でないと呼ばれませんので、注意してください。
この中にセーブ処理を書きたい、のですがとりあえず今は”セーブ”とログ出力だけにしています。
次に、オートロードにしたいので、Startメソッドでロード処理を行いたい、のですがとりあえず今は”ロード”とログ出力だけにしています。
ではスクリプトを保存し、UnityEditorのInspectorでWalletオブジェクトとShopオブジェクトをセットします。
なお、HierarchyからWalletオブジェクトやShopオブジェクトを見つけるのが大変な時は、Inspectorの◎をクリックすると検索ウインドウが開き、そこからセットすることもできます。(Shopスクリプトを探す時は、自動的にShopスクリプトが割り当てられたScene上のオブジェクトが絞り込まれて表示されます)
適宜使いやすい方法を選びましょう。
Consoleウインドウに実行時に「ロード」、終了時に「セーブ」と出てきましたね。
ログが正しく出力されない(特に「ロード」)場合は、スクリプトの修正が誤っている可能性が高いです。見直してみましょう。
PlayerPrefsによるセーブ・ロード処理の実装
さて、今回欲しいのは「セーブ機能」と「ロード機能」です。
一番簡単な方法をとると PlayerPrefsを使うのが簡単です。これは、Unityが用意している簡易なセーブロード機能で使い方は
PlayerPrefs.SetInt
(キー文字列,保存するintの値);PlayerPrefs.SetFloat
(キー文字列,保存するfloatの値);PlayerPrefs.SetString
(キー文字列,保存するstringの値);
このようにSet~ で文字列と値をペアにして保存することが出来ます。
保存したデータを逆に取得(ロード)したい場合は
PlayerPrefs.GetInt
(キー文字列,存在しなかった場合のintの値);PlayerPrefs.GetFloat
(キー文字列,存在しなかった場合のfloatの値);PlayerPrefs.GetString
(キー文字列,存在しなかった場合のstringの値);
このようにGet~ で文字列を指定するとペアになっている値が返却されますし、もしそのキー文字列での保存されたデータが無い場合(初回など)は第二引数で指定した値をデフォルト値として返却するものです。
今回のキー文字列とデータ内容については以下の通りとします。
キー文字列 | 内容 | データ型 |
MONEY | 所持金 | string(本当はBigIntegerだが、文字列にして格納) |
SHEEP0~7 | 羊の頭数 | int |
それでは、上記キー文字列でPlayerPrefsを使ってセーブ・ロード処理を書くとこのようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using System.Collections; using System.Collections.Generic; using System.Numerics; using UnityEngine; public class SaveLoadManager : MonoBehaviour { //保存対象①所持金用 [SerializeField] private Wallet wallet; //保存対象②羊の頭数用 [SerializeField] private Shop shop; private void OnApplicationQuit() { Debug.Log("セーブ"); //所持金を保存 PlayerPrefs.SetString("MONEY", wallet.money.ToString()); //全ての羊の頭数を保存しておく for (var index = 0; index < shop.sheepButtonList.Count; index++) { var sheepButton = shop.sheepButtonList[index]; PlayerPrefs.SetInt($"SHEEP{index}", sheepButton.currentCnt); } } void Start() { Debug.Log("ロード"); //所持金をロード wallet.money = BigInteger.Parse(PlayerPrefs.GetString("MONEY", "0")); //全ての羊の頭数をロードする for (var index = 0; index < shop.sheepButtonList.Count; index++) { var sheepButton = shop.sheepButtonList[index]; var sheepCnt = PlayerPrefs.GetInt($"SHEEP{index}", 0); sheepButton.currentCnt = sheepCnt; for (var i = 0; i < sheepCnt; i++) { sheepButton.sheepGenerator.CreateSheep(sheepButton.sheepData); } } } } |
PlayerPrefsによるセーブ処理の作成
まずセーブ処理ですが、
1 2 3 4 5 6 7 8 |
//所持金を保存 PlayerPrefs.SetString("MONEY", wallet.money.ToString()); //全ての羊の頭数を保存しておく for (var index = 0; index < shop.sheepButtonList.Count; index++) { var sheepButton = shop.sheepButtonList[index]; PlayerPrefs.SetInt($"SHEEP{index}", sheepButton.currentCnt); } |
所持金は PlayerPrefs.SetString("MONEY", wallet.money.ToString());
と、wallet.moneyをToStringメソッドで文字列にしてから保存しています。これはPlayerPrefsはintとfloatとstringしか対応していないからです。
そして各種類の羊の頭数はShopオブジェクトが持っているsheepButtonList内のSheepButtonが所持しているので、for文で全ての羊購入ボタン(SheepButton)を取得してPlayerPrefs.SetInt($"SHEEP{index}", sheepButton.currentCnt);
で保存をしています。
これにより
SheepButton配列0番目 は Sheep0 という名前で保存
SheepButton配列1番目 は Sheep1 という名前で保存
…
SheepButton配列7番目 は Sheep7 という名前で保存
としているわけです
PlayerPrefsによるロード処理の作成
次にロード処理ですが、
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//所持金をロード wallet.money = BigInteger.Parse(PlayerPrefs.GetString("MONEY", "0")); //全ての羊の頭数をロードする for (var index = 0; index < shop.sheepButtonList.Count; index++) { var sheepButton = shop.sheepButtonList[index]; var sheepCnt = PlayerPrefs.GetInt($"SHEEP{index}", 0); sheepButton.currentCnt = sheepCnt; for (var i = 0; i < sheepCnt; i++) { sheepButton.sheepGenerator.CreateSheep(sheepButton.sheepData); } } |
所持金のロードは、文字列で保存をしているのでPlayerPrefs.GetString("MONEY", "0")
で取得した保存された文字列化された所持金データを、BigInteger.Parse
メソッドで文字列→BigInteger
に戻して格納しています。
また各種羊の頭数は
1 2 |
var sheepCnt = PlayerPrefs.GetInt($"SHEEP{index}", 0); sheepButton.currentCnt = sheepCnt; |
でSheepButtonの現在数を保存したデータから戻してますが、加えて大事なことがあります。
というのも、SheepButtonの数だけロードしても、実際の各羊の数が増えるわけではないからです。
そのため、ここで各羊をもともと居た頭数分生成してあげているのが以下の処理になります。
1 2 3 4 |
for (var i = 0; i < sheepCnt; i++) { sheepButton.sheepGenerator.CreateSheep(sheepButton.sheepData); } |
for文で生成する羊の頭数分だけ繰り返して、SheepButtonが持っているsheepGeneratorを使って羊を生成しています。
ではスクリプトを保存して再生をし、ある程度羊を生成して1回停止→再生で停止前の状態が復元できるか確認をしてみましょう。
(※テストの為に初期羊毛の金額を20,000にしてあります。)
Tips: PlayerPrefsの初期化の方法
PlayerPrefsを使って実際にセーブやロード処理を何度かテストしていると、もちろんセーブデータが出来ている状態になるので、逆に「セーブデータが保存されていない状態」を作りたくなることがあります(レベルデザインで1からプレイし直したい時など)
そんな場合は、ツールバーのEdit→Clear All PlayerPrefs で初期化をすることが出来ます。
本当に初期化(削除)してよいかダイアログが出てくるので、良い場合は「YES」を選びましょう。
インタフェースの使い方とデバッグシステムの換装
※ここからは余裕のある方、プログラムを専門としない方などは飛ばしてしまっても構いません
さて、PlayerPrefsを使ってセーブとロードが出来るようになりました。
これにて完成! と言っても良いのですが、実はPlayerPrefsを使ったセーブ・ロードはあまり性能が良くなく、人によっては拒否反応を示すことがあります(個人的には全然使えるので使えばいいと思っていますが)
そうじゃなくても保存をローカルではなく、ネットワーク上のサーバーに保持する事だって考えられます。
ここで重要なのはSaveLoadManagerオブジェクト
が欲しいのはあくまでも所持金と羊頭数のセーブとロードという「機能・出来る事」であって、その先の方法・保存先がPlayerPrefsなのかファイルなのかネットワーク上のクラウドなのかは実は知る必要がありません。
こういう場合に便利なのが「インタフェース(interface)」になります。
早速使ってみましょう。
おさらいと次回予告
今回はセーブ・ロード処理とデバッグシステムの作成を行い一旦のゲーム完成となりました。
次回はサウンドの追加をしていきます。
サウンドの追加にはサウンドマネージャーを使用します。
第1回~10回まで通しで読んでいただいたあなたには申し訳ないのですが、一度サウンドマネージャー作成の方を先に見ていただき、改めて第11回講座の方を読んでいくとスムーズかと思われます。
サウンドマネージャーの作り方はこちら↓
第11回目講座はこちら↓
コメント
【講座最後の自動セーブ処理の実装について】
まず、OnApplicationQuitメソッドの中身をpublic型にし、saveメソッドを作ります。
{
Debug.Log(“セーブ”);
//所持金を保存
saveData.SaveMoney(wallet.money);
//全ての羊の頭数を保存しておく
for (var index = 0; index < shop.sheepButtonList.Count; index++){ var sheepButton = shop.sheepButtonList[index]; saveData.SaveSheepCnt(index, sheepButton.currentCnt); } }
OnApplicationQuitメソッドの中身をSave();に変更しましょう。
そして、SheepGenerator.csを開き、
[SerializeField] private SaveLoadManager saveLoadManager; //羊購入時のデータ保存処理
を追加します。
さらに、CreateSheepメソッドの最後に
saveLoadManager.Save();
の命令を追加します。
最後にインスペクター画面でSheepGeneratorオブジェクトにSaveLoadManagerをアタッチすれば完成です。
これで、羊の購入ボタンを押した際に自動でセーブ処理が行われます。
毎回コインを獲得する度にセーブ処理を行ってもよいのですが呼び出し回数が増大してしまうため、このような形で簡易セーブシステムを実装しました。