Unityに関連する記事です

 引き続き、セーブ・ロード機能の実装になります。

 ただし、すでにセーブ・ロードが必要な情報に関しての機能自体は完成していますので、この手順ではさらに2回に分けて、さらにワンランク上の応用処理の実装を目標としています
よって スクリプトを見直してリファクタリングする内容 になります。そのため、難しい処理が多く出てきますので、いますぐに実装を行わなくても問題ありません



発展23 ーセーブ・ロード機能の実装ー

 新しい学習内容は、以下の通りです。

 ・PlayerPrefs クラスと各メソッド◆ HasKey メソッド、DeleteKey メソッド、DeleteAll メソッドー
 ・static クラスと static メソッド
 ・JsonUtility クラス
 ・型引数とジェネリック型



1.設計


 いままでは、クリアポイントはクリアポイント、クリアしたステージの情報はクリアしたステージ情報と、それぞれの値ごとに1つずつセーブを行っていました。
今回の設計では、これらの情報を1つにまとめたクラスを作成し、その内容をセーブするようにします。
ただし、前回の List と同じように、クラスの情報や構造体はそのままではセーブできません。int 型と List<int> 型のように、クラス内には複数の型も混在している状態です。

 こういったケースの場合、クラス内のそれぞれの型を個別にセーブしていては非常に効率が悪く、またロードを行う際にも不便です。
また、クラス内に別のクラスの情報がある場合、セーブが行えません。

 以上のことをふまえて、セーブしたい複数の情報を1つのクラスとしてまとめ、その情報を1つの文字列として作成し、その状態でセーブをします。
string 型であれば、SetString メソッドを活用することでセーブを行えるためです。ただし、前回とは異なり、クラスの情報内はカンマ区切りの文字列には出来ません

 このとき、クラスを復元することも考えて、JsonUtility クラスを利用して、Json 形式と呼ばれる形式に則って string 型を作成します。

 // オブジェクトのデータを Json 形式に変換
  string json = JsonUtility.ToJson(obj);

 // セーブするための準備・セット
 PlayerPrefs.SetString(key, json);

 SetString メソッドの第1引数が Key になります。メソッドの引数で届いている key の情報をそのまま利用しています。

 SetString メソッドの第2引数がセーブされる文字例になります。
ここに Json 形式に変換されたクラスの情報の string 型を指定しています。

 この方法でセーブしておくことで、クラスの情報を1つにパッケージしてセーブしています
この機能を活用することにより、前回までセーブ・ロードをしていなかった List<WeaponData> 型の情報も今回はセーブ・ロードの対象として含めることが出来ます。
今後セーブ・ロードしたい情報が追加・削除されることがあった場合も、このセーブ・ロード用のクラスの情報を修正することで対応が出来ます。

 また今回は様々な部分でセーブとロードを実行することが想定される場合に備えて
PlayerPrefs クラスの機能をさらに自分のゲーム用にカスタマイズして、ゲーム内容に即した1つの新しいクラスを作成しておく設計にします。

 このようにしておくことにより、ゲーム内のセーブ・ロードに関しては、このクラスを利用すればよい状態を作り出すことが出来ます。



 どのような型に変更するかですが、Unity の用意している機能の1つに JsonUtility(ジェイソン・ユーティリティ) クラス があります。
このユーティリティクラスを利用すると、指定した型の情報を Json 形式と呼ばれる種類の string 型の文字に変換することが出来ます。

 また JsonUtility クラスでは、string 型の情報を元のクラスの情報に戻す処理も実行することが出来ます。

 このように、情報を書き換えてあげることで、PlayerPrefs クラスのセーブ・ロードができる string 型になりますので、
セーブを行う際に string 型にして保存し、ロードを行う際にはこの string 型でロードして、その情報を元の型の情報に戻すことで、
PlayerPrefs クラスを利用してセーブとロードが行えるようにします。



 処理の流れをまとめます。

<セーブするとき>
 ・セーブしたい情報(クラス)が PlayerPrefs クラスに対応している型ではないため、そのままではクラス内の情報をまとめてセーブできない。
        ↓
 ・【セーブしたい情報をまとめたクラス】を1つ作成し、JsonUtility クラスの ToJson メソッドを利用して、【セーブしたい情報をまとめたクラス】を string 型(Json 形式)に変換する。
        ↓
 ・この string 型の情報を PlayerPrefs クラスの SetString メソッドと Save メソッドを利用してセーブする。

<ロードするとき>
 ・string 型でセーブしてある情報を PlayerPrefs クラスの GetString メソッドを利用してロードをする。
        ↓
 ・ロードした情報(クラス)が string 型のため、そのままではセーブする前のクラスや型の情報として活用できないため、
  JsonUtility クラスの FromJson メソッドを利用して、string 型(Json 形式)をロードしたい情報(クラス・型)に変換して復元する。
  これは、セーブした際の情報の型とロードする際の情報の型が同じもの同士で処理が行える。
        ↓
 ・セーブする際に作成した【セーブしたい情報をまとめたクラス】が復元されるので、この情報を必要な値に代入し直すことでロードを完了し、再度ゲームで利用できるようにする

 簡単な処理ではありませんので、しっかりと処理の流れを把握してください。

 JSON(JavaScript Object Notation)とは「JavaScriptのオブジェクト記法を用いたデータ交換フォーマット」です。
様々な言語でサポートされているため、この情報を活用することにより、他の言語間のデータの受け渡しを簡単にするための機能ですが、
今回の場合は、string 型の情報になり、その後、クラスとして復元できる部分に活用しています。


2.PlayerPrefsHelper スクリプトの内容 <static クラスと static メソッド>


 Unity の PlayerPrefs クラスと JsonUtility クラスを利用して、ゲームの内容に即したクラスを作成します。
このように既存のクラスやメソッドの利便性を高めるように作成するクラスを総称してユーティリティクラス、ヘルパークラスといいます。

 ヘルパークラスの多くは static クラス・ static メソッドを持つ 内容になります。
その際、MonoBehaviour(モノビヘイビア)クラスの継承がないため、ゲームオブジェクトへのアタッチが不要です(正確に言うと出来ません)。
他のインスタンスメンバーと異なり、クラスごとに唯一の実体を持ち、すべてのインスタンスで共有化される情報 になります。

 例を挙げると、エネミーのゲームオブジェクトにはそれぞれ1つずつ EnemyControler クラスがアタッチされています。
そのため、エネミーのゲームオブジェクトがゲーム内に5つあるとするなら、EnemyControler クラスも5つあり、それぞれが異なるインスタンスを持っている状態です。
よって、スクリプト内において EnemyControler クラスの情報を利用したい場合には、対象となる EnemyControler クラスの情報を取得して利用することになります。

 これに対して、static 修飾子のクラスは、複数存在することがありません
そのため、この機能を活用した PlayerPrefsHelper クラスはゲーム内通じて1つしか存在できないようになっています。
よって、対象となるクラスは常に1つだけですので、クラスの情報を取得する必要がありません。

 クラスを人に例えたとして、「EnemyControler (佐藤) さん」とだけ声をかけると5人の EnemyControler (佐藤)さんが振り向いてしまうので、
「このゲームオブジェクトにアタッチされている(出身地とか、下の名前とかのイメージ)」EnemyControler (北海道、〜市在住の佐藤 〜)さんと、個別指定を声をかけるのに対し、
「PlayerPrefsHelper さん」と声をかけると、常に一人しかいないので、その人が必ず振り向いてくれる、というようなイメージです。総理大臣を呼ぶ感じでしょうか。

 static クラスはインスタンスを使って参照できませんので、(クラス)型名を使って参照して処理を行います。
これはどのクラスからでもメソッドの呼び出し命令が実行できます。例えば、【 PlayerPrefsHelper.メソッド名 】と記述すればメソッドを実行できます。
これが他のクラスとの大きな違いになります。

<通常のクラスの参照>
 // クラス(型)と変数の宣言
  EnemyControler enemyControler;

 // 制御したいクラス(型)のインスタンスを取得して代入し、変数を介してインスタンスを参照できる状態にする
  enemyControler = GatComponent<EnemyControler>();

 // 参照できるようになったので、変数を利用して処理を実行する
  enemyControler.<実行したい public 修飾子のメソッド名や変数名>();

<static クラスの参照>
  // インスタンス参照不要のため、クラス(型)をそのまま宣言して参照して処理を実行する
  PlayerPrefsHelper.実行したい public static 修飾子のメソッド名;

 参照して処理を実行していく際の方法が違うことがわかると思います。

参考サイト
MicroSoft
static 修飾子
https://docs.microsoft.com/ja-jp/dotnet/csharp/lan...
未確認飛行 C 様
静的メンバー
https://ufcpp.net/study/csharp/oo_static.html



 今回のヘルパークラスもこれと同様に、static クラスとして作成し、各メソッドも static メソッドになっています。
そのため、上記の使用例をみていただいてもわかる通り、static 修飾子のクラスとメソッドにして作成しておくことによって
いずれのクラスからでも自由に(変数への代入不要で)、呼び出し命令を行うことが出来るようになっています。

 このような設計になっていることによって、ヘルパークラスは使いやすさ・利便性を担保しています

 メインとなっているのは、クラスを string 型としてセーブを行い、ロードして元のクラスに復元する機能です。これをメソッド化して用意しています。
他にも便利な機能して、セーブデータが存在するのかを確認するメソッドや、整数の情報をセーブ・ロードする機能、
デバッグを簡単にするためにセーブデータを削除する機能もメソッド化しています。

 今後も PlayerPrefs クラスを活用したい場合には新しくメソッドを作成して、機能ごとに用意をしておくとよいでしょう。


3.PlayerPrefsHelper スクリプトを作成する


 先ほどの説明を踏まえた上で、PlayerPrefsHelper スクリプトを作成します。


PlayerPrefsHelper.cs

 <= クリックすると開きます


 スクリプトを作成したらセーブします。

 このスクリプトはゲームオブジェクトにアタッチすることが出来ませんが、
static クラスとして作成しておくことで、ゲームを実行した時点で自動的にインスタンス化されますので、ゲーム内で常に自由に利用できる状態になります。


4.<PlayerPrefs クラスと各メソッド◆ HasKey メソッド、DeleteKey メソッド、DeleteAll メソッドー>


 今回新しく実装している PlayerPrefs クラスの各メソッドを説明します。
1.HasKey メソッド

 引数に指定した文字列が PlayerPrefs に保存されている Key の情報としてあるかを調べて、Key が存在している場合には true、存在していない場合には false を戻します。

  public static bool ExistsData(string key) {

    // 指定したキーのデータが存在しているか確認して、存在している場合は true 、存在していない場合には false を戻す
        return PlayerPrefs.HasKey(key);
    }

 今回のケースでは、このメソッドを別のメソッド内に記述して、その結果を戻り値として戻す設計になっています。
引数で届いている Key の確認を行い、その存在がない場合(false)と存在する場合(true)とで異なる値を戻すようになっています。

参考サイト
Unity公式スクリプトリファレンス
PlayerPrefs.HasKey
https://docs.unity3d.com/ja/current/ScriptReferenc...



 最後に削除用のメソッドを2つ説明します。

2.DeleteKey メソッド

 PlayerPrefs 内にセーブされている情報に Key (引数で指定した文字列)が存在している場合、その情報を削除するメソッドです。

 // 指定されたキーのデータを削除
  PlayerPrefs.DeleteKey(key);

 今回のケースでは、メソッドの引数に届いている Key の情報を削除する命令を実行しています。


参考サイト
Unity公式スクリプトリファレンス
PlayerPrefs.DeleteKey
https://docs.unity3d.com/ja/current/ScriptReferenc...


3.DeleteAll メソッド

 PlayerPrefs 内にセーブされているすべての情報をまとめて削除するメソッドです。
この操作は Unity エディターから実行することも可能です。主にデータをリセットする際に利用しますので、
ゲームのデータを初期化してデバッグを行う際にも利用できます。

 // すべてのセーブデータを削除
  PlayerPrefs.DeleteAll();

参考サイト
Unity公式スクリプトリファレンス
PlayerPrefs.DeleteAll
https://docs.unity3d.com/ja/current/ScriptReferenc...


 DeleteKey メソッドと DeleteAll メソッドは実装はしているものの呼び出し命令は記述していません。
そのため、例えば、デバッグ用に任意のボタンにこれらのメソッドの呼び出し命令を実行するようにしておくことで、セーブデータのリセット用の機能を作成することが出来ます。

 自由に作成して、処理がどのように動くかを確認してみてください。


5.<JsonUtility クラス>


 Unity が用意している Json 形式のデータを扱うためのクラスです。
メソッドは全部で3つ用意されています。

JsonUtility クラス
https://docs.unity3d.com/ja/current/ScriptReferenc...



 今回は ToJson メソッドと FromJson メソッドを利用しています。

 ToJson メソッドを実行することで、指定したクラス(型)の情報を string 型の Json 形式の文字列に変換します。

<ToJson メソッド>
    /// <summary>
    /// 指定されたオブジェクトのデータをセーブ
    /// </summary>
    /// <param name="key">データを識別するためのキー</param>
    /// <param name="isSave"></param>
    public static void SaveSetObjectData<T>(T obj, string key) {

        // オブジェクトのデータを Json 形式に変換
        string json = JsonUtility.ToJson(obj);   // <= この処理

        // セット
        PlayerPrefs.SetString(key, json);

        // セットした Key と json をセーブ
        PlayerPrefs.Save();
    }

ToJson メソッド
https://docs.unity3d.com/ja/current/ScriptReferenc...



 FromJson メソッドを実行することで、Json 形式の string 型の情報をオブジェクトとして作成し、指定したクラス(型)の情報に変換(復元)します。
戻り値のあるメソッドにしてあるため、この情報を別のクラスに提供する(ゲッターメソッドとして利用する)ことが出来ます。

<FromJson メソッド>
    /// <summary>
    /// 指定されたオブジェクトのデータをロード
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <returns></returns>
    public static T LoadGetObjectData<T>(string key) {

        // セーブされているデータをロード
        string json = PlayerPrefs.GetString(key);

        // 読み込む型を指定して変換して取得
        return JsonUtility.FromJson<T>(json);   // <= この処理
    }

FromJson メソッド
https://docs.unity3d.com/ja/current/ScriptReferenc...


6.<型引数とジェネリック型>


 C# にはジェネリックという機能があります。
これは事前に型を指定するのではなく、メソッドを実行する際に任意の型を指定して実行できるようにしている機能です。

 日頃利用しているものとしては GetComponent メソッドや、List などがあります。

GetComponent メソッド
https://docs.unity3d.com/ja/current/ScriptReferenc...



 ジェネリックの型を示す用語として T が利用されます。ここにはどのような型でも指定していいが、実行時には型を指定する、という意味合いです。

    /// 指定されたオブジェクトのデータをセーブ
    /// </summary>
    /// <param name="key">データを識別するためのキー</param>
    /// <param name="isSave"></param>
    public static void SaveSetObjectData<T>(T obj, string key) {

        // オブジェクトのデータを Json 形式に変換
        string json = JsonUtility.ToJson(obj);

    }

 メソッド名と引数の間に <> があります。この部分は【型引数】と呼ばれる部分です。
GetComponent メソッドの <> と同じで、ここに、型の情報を指定します。GetComponent<Rigidbody>() のように、毎回型を指定して書いていると思いますが、それと同じです。

 このメソッドがジェネリックとして設計されている理由は、セーブを行う際に、どのようなクラスであっても Json 形式に変換できるようにするためです。
例えば、型を指定している場合、その型でしかメソッドを実行することが出来ません

<型を指定している場合>
  public static void SaveSetObjectData(EnemyController obj, string key) {

 上記の例であれば、EnemyController クラスを常に引数として指定していますので、このクラスであれば引数として受け入れることが出来ますが、
他のクラスには対応できないことになります。つまり、汎用性がない、とも言えます。

 今回はクラスの情報を Json 形式の string 型に変換してセーブ・ロードを行うことが目的です。
このとき、変換できる型の指定があるかないかによって、メソッドの利便性が大きく変化します。
固定されて指定された型しか変換できないメソッドと、どのような型でも変換できるメソッド、の違いとも言えます。
また、いずれのクラスからでも実行できるように static なメソッドとクラスになっています。

 運用を行う観点から考えると、EnemyController クラスを固定してメソッドを作成してしまっている場合、
このクラスを変換してセーブすることはできますが、他のクラスには対応できません。
そうなると、各クラスごとに引数だけを変えたメソッドを複数個用意しておく必要が生まれます

 これは非常に不便であり、メソッドの多態性を考えた場合にも理にかなっているとは言えない設計です。

 そのため今回は、セーブとロードを行うメソッドはジェネリック型とし、処理を実行する際に型を指定してもらうことで
その型に対応して柔軟に処理を実行できるような設計になっています。


参考サイト
Ararami Studio様
C#のジェネリックを使おう
https://araramistudio.jimdo.com/2017/12/26/c-%E3%8...
超初心者向けプログラミング入門 様
ジェネリック
https://programming.pc-note.net/csharp/generic.htm...



 以上でこの手順は終了です。次の手順でもセーブ・ロード機能のリファクタリングを行います。

 次は 発展24 ーセーブ・ロード機能の実装ぁ です。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます