PlayerPrefsを用いたセーブ・ロード処理とインターフェースを使ったデバッグシステムの開発

Unity インフレ系クリッカーゲームの作り方

Unityの本格ゲーム制作講座はこちら
【30日間の全額返金保証付き】

前回でコイン獲得時のアニメーション処理を作り、ゲームのメイン処理ができあがりました。

前回の記事↓

羊を購入できるお店スクリプトの作成とGrid Layout Groupを用いたUI画面整形
前回は、SheepButtonを作成して、羊購入処理等を作りました。 前回の記事↓ 今回はそのSheepButtonを複数並べるためのShopオブジェクトを実装していき、一通りクリッカーゲームとして遊べる状態を目指してい...

ですが、インフレ系クリッカーゲームは一度のプレイで完結しないため、セーブシステムが欠かせません。

ここでは、PlayerPrefsやインターフェースを用いてセーブ・ロードシステムを開発し、遊べるゲームとしての完成を目指します。

unityゲームのセーブ・ロードシステムの作り方

クリッカーゲームは根を詰めてプレイするというより、気が向いた時に開いてポチポチやって。というスタイルです。

そのためにはオートセーブ機能オートロード機能が不可欠です(毎回所持金0円、羊0頭から始めたくないですよね)

では、まず要件を詰めましょう。
今回のゲームで、セーブ・ロードをしたいデータはというと

  • ①所持金
  • ②各羊の頭数

だけになると思います。

(ゲームを終了させる前に出ていた羊毛(wool) は? とおっしゃる方もおられるかとは思いますがさすがにそこまでは対応しない事とします(もちろんやればできますが))

要件も決まりましたので、SaveとLoadをするオブジェクトSaveLoadManager」を作りましょう。

Sceneビュー上でCTRL+SHIFT+Nキーを押してオブジェクトを新規作成し、
名前を「SaveLoadManager」に、AddComponent新規スクリプトを追加
こちらも名前を「SaveLoadManager」にしておきます。

では、SaveLoadManagerスクリプトを以下のように修正します。

メンバ変数には、保存対象としている所持金を持っている 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.SetStrig(キー文字列,保存するstringの値);

このようにSet~ で文字列と値をペアにして保存することが出来ます。

保存したデータを逆に取得(ロード)したい場合は

  • PlayerPrefs.GetInt(キー文字列,存在しなかった場合のintの値);
  • PlayerPrefs.GetFloat(キー文字列,存在しなかった場合のfloatの値);
  • PlayerPrefs.GetStrig(キー文字列,存在しなかった場合のstringの値);

このようにGet~ で文字列を指定するとペアになっている値が返却されますし、もしそのキー文字列での保存されたデータが無い場合(初回など)は第二引数で指定した値をデフォルト値として返却するものです。

今回のキー文字列とデータ内容については以下の通りとします。

キー文字列 内容 データ型
MONEY 所持金 string(本当はBigIntegerだが、文字列にして格納)
SHEEP0~7 羊の頭数 int

それでは、上記キー文字列でPlayerPrefsを使ってセーブ・ロード処理を書くとこのようになります。

PlayerPrefsによるセーブ処理の作成

まずセーブ処理ですが、

所持金は 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によるロード処理の作成

次にロード処理ですが、

所持金のロードは、文字列で保存をしているのでPlayerPrefs.GetString("MONEY", "0")で取得した保存された文字列化された所持金データを、BigInteger.Parseメソッドで文字列→BigIntegerに戻して格納しています。

また各種羊の頭数は

でSheepButtonの現在数を保存したデータから戻してますが、加えて大事なことがあります。

というのも、SheepButtonの数だけロードしても、実際の各羊の数が増えるわけではないからです。

そのため、ここで各羊をもともと居た頭数分生成してあげているのが以下の処理になります。

for文で生成する羊の頭数分だけ繰り返して、SheepButtonが持っているsheepGeneratorを使って羊を生成しています。

ではスクリプトを保存して再生をし、ある程度羊を生成して1回停止→再生で停止前の状態が復元できるか確認をしてみましょう。

(※テストの為に初期羊毛の金額を20,000にしてあります。)

 

Tips: PlayerPrefsの初期化の方法

PlayerPrefsを使って実際にセーブやロード処理を何度かテストしていると、もちろんセーブデータが出来ている状態になるので、逆に「セーブデータが保存されていない状態」を作りたくなることがあります(レベルデザインで1からプレイし直したい時など)

そんな場合は、ツールバーのEditClear All PlayerPrefs で初期化をすることが出来ます。

本当に初期化(削除)してよいかダイアログが出てくるので、良い場合は「YES」を選びましょう。

「この操作は元に戻せないけど良いですか?」

インタフェースの使い方とデバッグシステムの換装

※ここからは余裕のある方、プログラムを専門としない方などは飛ばしてしまっても構いません

さて、PlayerPrefsを使ってセーブとロードが出来るようになりました。

これにて完成! と言っても良いのですが、実はPlayerPrefsを使ったセーブ・ロードはあまり性能が良くなく、人によっては拒否反応を示すことがあります(個人的には全然使えるので使えばいいと思っていますが)
そうじゃなくても保存をローカルではなく、ネットワーク上のサーバーに保持する事だって考えられます。

ここで重要なのはSaveLoadManagerオブジェクトが欲しいのはあくまでも所持金と羊頭数のセーブとロードという「機能・出来る事」であって、その先の方法・保存先がPlayerPrefsなのかファイルなのかネットワーク上のクラウドなのかは実は知る必要がありません。

こういう場合に便利なのが「インタフェース(interface)」になります。

早速使ってみましょう。

Assets/Scripts で右クリックメニューで Create→C# Script を選択してスクリプトを追加します。

ファイル名はISaveDataにしておきます。

この手順で作られるISaveDataスクリプトは最初MonoBehaviour継承のclassになっていますが、classはinterface(インタフェース)に変更し、継承は消してしまいます。

そしてStartメソッドとUpdateメソッドも消して、「所持金と羊頭数のセーブ・ロード機能」だけを宣言します(中身は書きません。あくまでも「宣言」だけです)

では、このISaveDataインタフェースを実装したPlayerPrefsSaveDataクラスを作成します。

Assets/Scripts で右クリックメニューで Create→C# Script を選択してスクリプトを追加し、ファイル名はPlayerPrefsSaveDataにしておきます。

ではスクリプトを修正していきます。
前述しましたが、新規作成されたスクリプトは自動でMonoBehaviour継承になっていますが、ISaveDataインタフェースに変更し、ISaveDataインタフェースで「定義だけ」されたメソッドの中身をPlayerPrefsを使って書いていきます。(これを実装と呼びます)

これで、ISaveDataインタフェースPlayerPrefsによって実装したPlayerPrefsSaveDataクラスが完成しました。

SaveLoadManagerスクリプト修正

SaveLoadManagerスクリプトではまだ直にPlayerPrefsを使っていますので、先ほど作成したISaveDataインタフェースPlayerPrefsによって実装したPlayerPrefsSaveDataクラスを使う様に修正します。

まず、メンバ変数に ISaveData saveData を追加します。(ここがPlayerPrefsSaveDataじゃないところが肝です)

次にAwakeメソッドを追加し、メンバ変数 saveData に PlayerPrefsSaveDataを生成(new)して格納してあげます。

後は直接、PlayerPrefsを使ってセーブやロードをしていた処理をISaveDataインタフェースである saveDataのメソッドを使用するように変更するだけです。

以下、修正後のSaveLoadManagerスクリプト全体になります。(直接PlayerPrefsを使う箇所が無くなっているのが分かると思います)

これは、ISaveDataインタフェースを通じてその奥にある、PlayerPrefsSaveDataクラスのメソッドを呼んであげている。という状態です。

今回追加・修正したスクリプトの保存を全て行ったら、UnityEditor上で確認してみましょう。変わらずセーブ・ロードができているのがわかると思います。

デバッグ用のISaveData実装

このままでは特にメリットは感じない方も多いでしょう。(何も挙動は変わってませんからね・・・)

インタフェースを使うメリットは「実装の差し替えがしやすい」ことにあります。

試しにデバッグ用のISaveData実装のDebugSaveDataクラスを作ってみましょう。

Assets/Scripts で右クリックメニューで Create→C# Script を選択してスクリプトを追加し、ファイル名はDebugSaveDataにしておきます。

ではスクリプトを修正していきます。

PlayerPrefsSaveDataを作った時同様、DebugSaveDataクラス親クラスMonoBehaviourからISaveDataインタフェースに変更し、ISaveDataインタフェースで「定義だけ」されたメソッドの中身をデバッグ用に書いてしまいます。

Save処理はいらないので、中身は空です。

Load処理はデバッグ用に所持金大金(10,000,000,000)を、羊頭数5を返すようにしておきます。

これで、ISaveDataインタフェース実装したデバッグ用のクラスDebugSaveDataクラスが完成しました。

では、SaveLoadManagerスクリプトを作ったDebugSaveDataクラスを使うように修正してみましょう。

といっても変更するのは1行だけです。

Awakeメソッドで、メンバ変数 saveData に PlayerPrefsSaveDataを生成(new)して格納していますが、一旦コメントアウト(行頭に//を入れて、処理として一旦外すことをコメントアウトと呼びます)させて、DebugSaveDataを生成(new)して格納するよう修正します。

では追加・修正したスクリプトを保存してUnityEditor上で確認してみましょう。

実行した瞬間、所持金が10,000,000,000 全羊が5頭ずつになっているのがわかると思います。(そして、セーブ処理は空実装なのでセーブされません)

このように、たった1行変更するだけで、セーブ・ロードの処理を切り替える事ができました。
というのも、SaveLoadManagerスクリプトは実際の処理には依存せず、ISaveDataインタフェースを通じて、機能にだけ依存している状態だからです。

この考え方で、PlayerPrefsを使わないセーブ・ロード機能のクラスを作成して切り替えることも出来ますね。

なお、今回SaveLoadManagerスクリプトで修正しましたが、もちろんデバッグ用の処理なので最後には元の処理に戻しておきましょう。


ゲームクリエイター専門学校の資料請求はこちら
【大学・会社に通いながら学べる】

おまけ webGLでPlayerPrefsを用いてセーブしたいとき

ここで、少し脱線しますが、unityで作った個人ゲームはwebGL形式にすることでunityroomなどでフリーゲーム公開が可能です。

しかし、webGLではブラウザタブを閉じる場合など、今回の講座で使ったOnApplicationQuit メソッドが作動しません。

そこで、何かしらゲームの途中イベント発生時にOnApplicationQuit メソッドの中身を実行する必要があります。

さて、そんなときどのようにスクリプトを変更すればよいでしょうか?

これが唯一の正解!というものはありませんが、ゲームの自動セーブ処理を作る場合などに同じ問題にぶち当たることでしょう。

ぜひ一度考えて自分で改造してみてくださいね(この後に簡単に解説を書いておきますね)。

回答例 自動セーブ処理の実装について

まず、OnApplicationQuitメソッドの中身をpublic型にし、saveメソッドを作ります。

OnApplicationQuitメソッドの中身をSave();に変更しましょう。

そして、SheepGenerator.csを開き、

を追加します。

その後、Startメソッドで変数のsaveLoadManagerにゲームオブジェクトのSaveLoadManagerのコンポーネントを紐づけます。

さらに、CreateSheepメソッドの最後に

の命令を追加します。
最後にインスペクター画面でSheepGeneratorオブジェクトにSaveLoadManagerをアタッチすれば完成です。

これで、羊の購入ボタンを押した際に自動でセーブ処理が行われます。
毎回コインを獲得する度にセーブ処理を行ってもよいのですが呼び出し回数が増大してしまうため、このような形で簡易セーブシステムを実装しました。

おさらいと次回予告

今回はセーブ・ロード処理とデバッグシステムの作成を行い一旦のゲーム完成となりました。

次回は最終回、サウンドの追加をしていきます。

サウンドの追加にはサウンドマネージャーを使用します。第1回~9回まで通しで読んでいただいたあなたには申し訳ございませんが、一度サウンドマネージャー作成の方を先に見ていただき、改めて第10回講座の方を読んでいただくとスムーズかと思われます。

そして、そのサウンドマネージャー講座は今まさに書いているところです!しばしお待ちくださいませ!

放置インフレ系クリッカーゲームの作り方講座に戻る↓

Unity インフレ系クリッカーゲームの作り方
今回のunityゲーム開発講座では2DUnityを用いたインフレ系クリッカーゲームシステムの制作を行っていきます! 講座は現在全部で10回に分かれており、初めてunityを使ってゲームを作る人でもサクサク進められる講座になっています。 「Unityでクリッカーゲームを作ってみたい!」 「Unityでインフレ系タップゲームを作ってみたい!」 そんなあなたのためにインフレ系クリッカーゲームの作り方講座です!

ゲームクリエイター専門学校の資料請求はこちら
【大学・会社に通いながら学べる】

コメント

  1. ばこ より:

    【講座最後の自動セーブ処理の実装について】
    まず、OnApplicationQuitメソッドの中身をpublic型にし、saveメソッドを作ります。

    public void 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をアタッチすれば完成です。 これで、羊の購入ボタンを押した際に自動でセーブ処理が行われます。 毎回コインを獲得する度にセーブ処理を行ってもよいのですが呼び出し回数が増大してしまうため、このような形で簡易セーブシステムを実装しました。

タイトルとURLをコピーしました