Unityに関連する記事です

 GameData クラスに現在所持しているアイテムの情報がありますので、このデータをセーブし、
次回のゲーム起動時にロードをして、ゲームをやめる前に所持していたアイテムの情報の状態を復元する制御を実装します。


<実装画像 セーブ>
動画ファイルへのリンク


<実装画像 ロード>
動画ファイルへのリンク


手順22 −所持しているアイテムのセーブ・ロード機能の追加−
38.GameData スクリプトを修正して所持しているアイテムの情報のセーブ機能を追加する
39.GameData スクリプトを修正して所持しているアイテムの情報のロード機能を追加し、以前と同じ所持順で並べる


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

 ・PlayerPrefs クラス ーSetStringメソッド、Save メソッド、HasKey メソッド、GetString メソッドー
 ・String.Split メソッド
 ・コンストラクタ ーインスタンスしたクラスにコンストラクタを利用して値を代入する方法ー
 ・Linqの機能の実装例◆ OrderBy メソッド〜
 ・Enum.Parse メソッド
 ・int.Parse メソッド

 非常に多くの機能を利用して実装を行っています。
 セーブ機能よりもロード機能の方が複雑ですので、セーブ機能が難しいと感じた場合には実装を中断して、充分な知識と技術を養ってから再度挑戦してください。



38.GameData スクリプトを修正して所持しているアイテムの情報のセーブ機能を追加する

1.設計


 所持しているアイテムの情報をセーブする機能を実装します。

 実装にあたっては Unity の用意している PlayerPrefs クラスを利用します。

 所持しているアイテムの情報は GameData クラスで管理を行っていますので、
セーブ機能もおなじ GameData クラス内に追記して実装するような設計にします。

 これはアイテムの情報を管理しているクラスであるからだけではなく、
GameData クラスがシングルトンクラスであるため、このクラス内にセーブ用のメソッドを実装しておくことにより、
外部のクラスから任意のタイミングでセーブの処理の呼び出し命令を行いやすくするためでもあります。
例えば、フィールドやダンジョンなど、どこでもセーブできるような状態を想定した場合には、自由にセーブメソッドが実行できる設計の方が利便性が高いと言えます。


2.GameData スクリプトを修正する


 セーブの処理を行うメソッドを作成し、所持しているアイテムの情報を元にデータのセーブを行います。

 デバッグ用に Update メソッドを作成し、キーボード入力によってセーブが行えるようにも処理を追加します。
この処理でデバッグを行い、無事にセーブができることを確認できれば、あとは、任意のタイミングでセーブの処理を書けばよい状態になります。


GameData.cs

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


 スクリプトを修正したらセーブします。


3.<PlayerPrefs クラス  SetString メソッド、Save メソッド>


 Unity ではデータのセーブ・ロードを行うための PlayerPrefs クラスが用意されています。
PlayerPrefs クラスにはセーブ・ロード用のメソッドが用意されていますので、こちらを実装することで
指定したデータをセーブしたり、ロードしたりすることが可能になっています。

 データの保存先は、Unity Editor や PC ゲームの場合には PC 本体のハードディスク、Web の場合はブラウザ内、スマホ端末の場合にはアプリ内のデータの場所内です。


<PlayerPrefs クラス>
https://docs.unity3d.com/ja/current/ScriptReferenc...


 2つのメソッドを利用してセーブを行っていますので、順番に説明します。


1.SetString メソッド

 Key という文字列を保存する際の識別子として指定し、その名前を用いて指定された型の情報をセーブするための準備を行います。
Key とはいわばセーブ用のラベルであり、名前を付けて保存のことです。この Key の情報をロードする際にも利用します。

 PlayerPrefs クラスにはセーブの方法が3種類用意されており、SetString メソッドもその1つです。
SetString という名前の通り、文字列をセーブしておくことが出来ます。残る2つは SetInt メソッド、SetFloat メソッドであり、これらもメソッド名の型の情報をセーブします。

 今回セーブしたい情報は、【アイテムの名前】【アイテムの所持数】【アイテムの通し番号】の3つですが、これらは複数の型が混在しています。
こういったケースの場合、それぞれを個別にセーブしていては非常に効率が悪く、またロードを行う際にも不便です。

 そのため今回は、これら3つの情報を1つの文字列として作成し、その状態でセーブをします。
つまり、【アイテムの名前 + アイテムの所持数 + アイテムの通し番号】という状態の文字列を作成して、これを1回でセーブを行うという手法になります。

 ただし文字列をくっつける際には、ロードする時のことも考えて置かなければなりませんので、厳密には、カンマを利用して文字列を作成します。

 以上のことをふまえて、次のような処理を実装しています。

  // 所持しているアイテムの数だけ処理を行う
  for (int i = 0; i < itemInventryDatasList.Count; i++) {

      // 所持しているアイテムの情報を1つの文字列としてセーブするための準備を行う
      PlayerPrefs.SetString(itemInventryDatasList[i].itemName.ToString(), itemInventryDatasList[i].itemName.ToString() + "," + itemInventryDatasList[i].count.ToString() + "," + i.ToString());
  }

 SetString メソッドの第1引数が Key になります。【itemInventryDatasList[i].itemName.ToString()】の部分です。
今回は、アイテムの名前をそのまま Key として設定しています。
そのため、ロードを行う際にもアイテムの名前を検索することで確認を行うことができる設計です。

 SetString メソッドの第2引数がセーブされる文字例になります。
【itemInventryDatasList[i].itemName.ToString() + "," + itemInventryDatasList[i].count.ToString() + "," + i.ToString()】の部分です。
先ほども説明したように、3つの情報3つの情報が分かるように、カンマで区切った上で1つの文字列としてセーブしています。
【アイテムの名前, アイテムの所持数 , アイテムの通し番号】の順番です。

 この方法でセーブしておくことで異なる型の情報を1つにパッケージしてセーブしています。
これはアイテムの情報1つにつき1つずつ作成されます(for 文でループ処理しています)ので、所持アイテムが3つあるならば、この1つにまとまった文字列も3つセーブされることになります。

 Set 〜 で始まる3つのメソッドは、セーブを行う対象を設定しています。そのためセーブするための準備を行うメソッドです。
実際には次に解説する Save メソッドを実行することでデータがセーブされます。


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


2.Save メソッド

 Set 〜 メソッドによって準備された情報をセーブするメソッドです。
この処理が実行されて始めてデータがセーブされます。

 複数の Set 〜 メソッドが実行されていた場合、このメソッドはそれらすべてのセーブを行います。

  // 所持しているアイテムの数だけ処理を行う
  for (int i = 0; i < itemInventryDatasList.Count; i++) {

      // 所持しているアイテムの情報を1つの文字列としてセーブするための準備を行う
      PlayerPrefs.SetString(itemInventryDatasList[i].itemName.ToString(), itemInventryDatasList[i].itemName.ToString() + "," + itemInventryDatasList[i].count.ToString() + "," + i.ToString());
  }

  // セーブ
  PlayerPrefs.Save();

 今回の場合には、for 文内でセットされた文字列の情報をまとめてセーブしています。


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


4.ゲームを実行して動作を確認する


 GameData ゲームオブジェクトのインスペクターより、現在所持しているアイテムの情報を確認しておきます。
また、isDebug ボタンにチェックを入れてデバッグ用モードに切り替えておきます。


<インスペクター画像 参考>



 このアイテムの情報をそのまますべてセーブします。アイテムの名前、所持しているアイテムの数、並び順がセーブされます。



 準備が整ったらゲームを実行して、 デバッグ用にセーブ用のボタンを設定しましたので、そのボタンを押してください。
Console ビューにセーブしたキーの名前と、そのキーの名前でセーブされた情報がそれぞれ表示されればセーブ成功です。


<実装画像 セーブ>
動画ファイルへのリンク


 一旦セーブデータを破棄する場合には、Unity Editor の左上にあるメニューより、Edit => Clear All PlayerPrefs を選択してください。
確認のウインドウが開き、Yes を選択することで、PlayerPrefs にセーブされているすべてのデータが削除されます。


<Clear All PlayerPrefs>



 以上でセーブの機能は実装完了です。続いて、このセーブしたアイテムの情報をロードする機能を実装します。


39.GameData スクリプトを修正して所持しているアイテムの情報のロード機能を追加し、以前と同じ所持順で並べる

1.設計


 所持しているアイテムの情報がセーブできましたので、次はこのデータをロードする処理を実装します。
ロード処理についても PlayerPrefs クラスの機能を利用して実装を行います。

 ロード処理も、例えば、タイトル画面から呼び出すようになるのか、ゲーム内で呼び出すようになるのか、
利用する場所が多岐に渡ると想定されますので、やはり、この処理も GameData クラスに実装を行う設計にします。


2.GameData スクリプトを修正する


 実装するメソッドは1つだけですが、処理が複雑です。
利用しているメソッドについては後で解説していますし、処理の流れもコメントしてありますが
それでも一読しただけでは理解が難しいと思いますので、繰り返し読み解いていってください。

 また Update メソッドにロード用の処理を実装しておきます。
今回も任意のタイミングでボタンを押してロードを行ってみて問題がなければどのスクリプトに記述しても大丈夫でしょう。


GameData.cs

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


 スクリプトを修正したらセーブします。


3.<PlayerPrefs クラス◆ HasKey メソッド、GetString メソッド>


 PlayerPrefs クラスの扱えるメソッドを新しく2つ利用しています。


1.HasKey メソッド

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

  // ItemName でセーブしてあるデータが PlayerPrefs 内にあるか
  if (!PlayerPrefs.HasKey(DataBaseManager.instance.GetItemDataFromItemNo(i).itemName.ToString())) {
        
     // セーブデータがなければここで処理を終了し、次のセーブデータを確認する処理へ移る
      continue;
  }

 今回のケースでは、Key の確認を行い、その存在がない場合(false)には次の処理にはいかず、continue 処理によって、次の for 文の処理に移るようになっています。
Key が存在している場合にはこの if 文には入らなくなるため、if 文の下にある処理が実行されます。

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


2.GetString メソッド

 PlayerPrefs 内にセーブされている Key が存在している場合、その情報を string 型で取得するメソッドです。
第2引数を設定することで、もしも Key が存在しなかった場合には Default 値として設定を行うことも出来ます。
 
  // セーブされているデータを読み込んで配列に代入
  string[] stringArray = PlayerPrefs.GetString(DataBaseManager.instance.GetItemDataFromItemNo(i).itemName.ToString()).Split(',');

 今回のケースでは、DataBaseManager.instance.GetItemDataFromItemNo(i).itemName.ToString() メソッドの戻り値を Key として指定しています。
"薬草" や "くさりかたびら" といったアイテムの名前で保存されているデータが PlayerPrefs に存在しているかを確認し、存在している場合には、
文字列として情報を取得します。その後、取得した文字列に対して、Split メソッドが実行されます。


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


4.<String.Split メソッド>


 1つ、または複数の区切記号を持つ1つの文字列を複数の文字列に分割して配列を作成する機能です。

  // ItemName でセーブしてあるデータがあるか
  string[] stringArray = PlayerPrefs.GetString(DataBaseManager.instance.GetItemDataFromItemNo(i).itemName.ToString()).Split(',');

 今回のケースでは、string 型の1つの文字列としてアイテムの情報がセーブされているかを確認し、セーブされていた場合には、1つの文字列の情報が取得出来ます。
それは【アイテムの名前, アイテムの所持数, 所持している際の通し番号】という順番でカンマ区切りされている文字列の情報になります。
例えは、[薬草,1,0] [くさりかたびら,2,1] というような情報になっています。

 セーブされているアイテムのデータはすべてこの書式によってセーブされていますので、もしもセーブされているデータを取得できても
そのままの状態ではゲーム内で利用できる状態になっていません。

 そこでまずは、この1つの文字列を「カンマ」の部分で区切っていき、それを配列にする処理を行います。
それを行うメソッドが Split メソッドになります。



 Split メソッドは文字列(string 型)に対して実行することができるメソッドです。引数で指定した文字(char 型)の部分で対象となる文字列を区切り、それを配列にします。

 先ほどの例の [薬草,1,0] という文字列であれば [薬草] [1] [0] というように、カンマの部分で区切った文字列を作成します。
これを先頭から配列にします。

 上記の例を元に作成される string[] stringArray 変数は、次のような状態になっています。

<[薬草] [1] [0] をカンマで区切った際の string[] stringArray 変数の要素>
stringArray[0] = "薬草"
stringArray[1] = "1"
stringArray[2] = "0"

 数字の情報も文字列であることに注意してください。また、区切文字として指定されている文字は自動的に削除されます。
今回はカンマを区切文字として指定していますので、その情報は削除されて、カンマを除いた文字列の配列が作成されます。

 1つ1つの処理をしっかりと読み解いていくことを覚えていきましょう。


参考サイト
MicroSoft C# ドキュメント
C# で String.Split を使用して文字列を分割する方法
https://docs.microsoft.com/ja-jp/dotnet/csharp/how...
SAMURAI エンジニアブログ 様
【C#入門】String.Splitで文字列を分割(複数文字、文字数でも分割)
https://www.sejuku.net/blog/44242



5.<コンストラクタ ーインスタンスしたクラスにコンストラクタを利用して値を代入する方法ー>


 コンストラクタとは、クラスがインスタンス(new によって初期化)されたときに、引数で指定した値の代入を強制する特殊なメソッドのことです。
クラス名(型名)と同じ名前で定義を作成します。またメソッドですが戻り値を持ちません。(正確にいうと戻り値を返せません。)

 メソッドですので、クラス内に定義を作成します。

    [System.Serializable]
    public class ItemInventryData {
        public ItemName itemName;    // アイテムの名前
        public int count;            // 所持数
        public int number;           // 所持している通し番号

        /// <summary>
        /// ItemInventryData クラスのコンストラクタ
        /// </summary>
        /// <param name="name">アイテムの名前</param>
        /// <param name="value">アイテムの所持数</param>
        /// <param name="num">アイテムを所持した際の通し番号</param>
        public ItemInventryData(ItemName name, int value, int num) {   // 戻り値がありません
            itemName = name;
            count = value;
            number = num;
        }
    }

 クラスで用意している変数に対して、インスタンスを正しく(代入忘れのないように)処理を行うために用意されます。

 コンストラクタを使用する場合には、new による初期化の宣言と同時に、コンストラクタで引数を定義している場合には、引数が必要になります
引数が必要な場合に引数がない場合にはエラーになります。これが値の代入を強制する、という意味になります。



 初期化の方法です。下記の処理内の、Add メソッド内で実行されている処理がクラスをインスタンスし、初期化している部分です。

  // セーブデータからアイテムのデータをコンストラクタ・メソッドを利用して復元
  itemInventryDatasList.Add(new ItemInventryData((ItemName)Enum.Parse(typeof(ItemName), stringArray[0]), int.Parse(stringArray[1]), int.Parse(stringArray[2])));

<上記の処理のうち、この部分がクラスをインスタンスしている部分>
  new ItemInventryData((ItemName)Enum.Parse(typeof(ItemName), stringArray[0]), int.Parse(stringArray[1]), int.Parse(stringArray[2]))

 コンストラクタを持つクラスがインスタンスされると、コンストラクタ・メソッドが自動的に呼び出されて、初期化の処理が行われます
処理の内容は通常のメソッドと同じです。多くの場合はここで、クラスに用意している変数に対して、引数の値を代入することによってクラスに初期情報を登録します

<new ItemInvenrtyData が実行されると、コンストラクタが処理される = 初期化>
  public ItemInventryData(ItemName name, int value, int num) {  // <=  ここに引数の情報が届く
      itemName = name;  // name 変数には、第1引数の値である (ItemName)Enum.Parse(typeof(ItemName), stringArray[0]) の戻り値が代入されている
      count = value;      //  value 変数には、第2引数の値である int.Parse(stringArray[1]) の戻り値が代入されている
      number = num;       //  num 変数には、第3引数の値である int.Parse(stringArray[2]) の戻り値が代入されている
  }



 コンストラクタはオーバーロードが可能です。そのため、同じ名前のコンストラクタ・メソッドであっても引数を別々の形で定義しておくことが可能です。
例えば、以下のような形で用意可能です。オーバーロードがある場合、クラスをインスタンスしたとき、引数の情報に合わせて適用するコンストラクタが自動的に変わります

<コンストラクタ・メソッドのオーバーロード>
  // その1
  public ItemInventryData(ItemName name, int value, int num) {
      itemName = name;
      count = value;
      number = num;
  }

  // その2
  public ItemInventryData(ItemName name, int value, int num, ItemType type) {
     itemName = name;
     count = value;
     number = num;
     itemType = type;
  }

  // その3
  public ItemInventryData(ItemName name) {
     itemName = name;
  }

参考サイト
未確認飛行C様
コンストラクタ
https://ufcpp.net/study/csharp/oo_construct.html


6.<Linqの機能の実装例◆ OrderBy メソッド〜>


 指定したコレクション内の要素を、引数で指定した情報を基準に昇順に並び替える機能です。処理の結果は戻り値として取得出来ます。

  // 以前に所持していた番号順で所持アイテムの並び順をソート
  itemInventryDatasList = itemInventryDatasList.OrderBy(x => x.number).ToList();

 今回のケースでは、itemInventryDatasList 変数を対象として OrderBy メソッドを実行しています。
itemInventryDatasList 変数は ItemInventryData クラスの集合体ですので、これを引数で指定した ItemInventryData クラスの number 変数を確認して昇順に並び替えます。
number 変数は、ゲーム内でアイテムを取得した際の通し番号です。数字が低いほど先に入手し、数字が高いほど後で入手したアイテムとなっています。

 最後に ToList メソッドを実行してキャストすることにより、再び List として構築されます。

 この結果、ロードされた順番に並んでいた itemInventryDatasList 変数の要素を、以前ゲーム内で並んでいたアイテムの順番にソートを行っています。


参考サイト
MicroSoft C# ドキュメント
Enumerable.OrderBy メソッド
https://docs.microsoft.com/ja-jp/dotnet/api/system...
陰干し中のゲーム開発メモ 様
【C#,LINQ】OrderBy,OrderByDescending〜配列やリストを並べ替えたいとき〜
https://www.urablog.xyz/entry/2018/06/28/070000


7.<Enum.Parse メソッド>


 文字列を同じ名称の enum にキャスト(型変換)します。キャストの結果は戻り値として取得できます。

 メソッドの書式は [(キャストしたい enum の型)Enum.Parse(typeof(キャストしたい enum の型), キャストする文字列] です。
 
 利用するためには using System; の宣言が必要になります。

 using System;

  // セーブデータからアイテムのデータをコンストラクタ・メソッドを利用して復元
  itemInventryDatasList.Add(new ItemInventryData((ItemName)Enum.Parse(typeof(ItemName), stringArray[0]), int.Parse(stringArray[1]), int.Parse(stringArray[2])));

 上記の処理の Add メソッドの第1引数として利用しています。

  (ItemName)Enum.Parse(typeof(ItemName), stringArray[0])

 今回のケースでは、stringArray[0] に代入されている文字列の値を、同名の ItemType 型の列挙子にキャストを行うという処理になっています。
例えば、stringArray[0] の要素が "ブルーリボン" という文字列であれば、ItemType 型にある列挙子の ブルーリボン という情報に変換を行う処理になります。


 例外処理(enum に登録されていない文字列を enum に変換しようとした場合)にはエラーが出て処理がストップしますので、
Enum.TryParse メソッドを利用することでより安全にキャストが可能になります。


参考サイト
DOBON.NET 様
数値や文字列を列挙体に変換する
https://dobon.net/vb/dotnet/programing/enumparse.h...
Qiita @TomoProg 様
【C#】文字列とenumの変換テクニック集
https://qiita.com/TomoProg/items/061861c784853b587...
MicroSoft C# ドキュメント
Enum.TryParse メソッド
https://docs.microsoft.com/ja-jp/dotnet/api/system...


8.<int.Parse メソッド>


 文字列を数値にキャスト(型変換)する機能になります。キャストの結果は戻り値として取得できます。
数値の型である int, long, float, double の各型ごとに Parse メソッドと、Convert メソッドがあります。また、Enum.Parse メソッドと同じように TryParse メソッドもあります。

  // セーブデータからアイテムのデータをコンストラクタ・メソッドを利用して復元
  itemInventryDatasList.Add(new ItemInventryData((ItemName)Enum.Parse(typeof(ItemName), stringArray[0]), int.Parse(stringArray[1]), int.Parse(stringArray[2])));

 上記の処理の Add メソッドの第2引数と第3引数して利用しています。

  int.Parse(stringArray[1])
 int.Parse(stringArray[2])

 引数で指定している文字列を数字の型(今回は int 型)にキャストして代入します。
例えば、stringArray[1] 変数の要素が "1" という文字列である場合には、 int 型の 1 という情報に変換されます。

MicroSoft C# ドキュメント
文字列を数値に変換する方法 (C# プログラミング ガイド)
https://docs.microsoft.com/ja-jp/dotnet/csharp/pro...
われこ われこ様
【ワレコの講座】C#のint.ParseとConvert.ToInt32の違いをマスター【完璧や】
https://www.wareko.jp/blog/master-difference-betwe...
SAMURAI エンジニアブログ 様
【C#入門】文字列を数値に、数値を文字列に変換する方法
https://www.sejuku.net/blog/44977


9.ゲームを実行して動作を確認する


 セーブした内容を把握しておいてから、GameData ゲーオブジェクトのインスペクターから所持しているアイテムの情報をリセットしてください。


インスペクター画像



 この状態にしてからゲームを実行して、ロード用のボタンを押してロード処理を行ってください。
先ほどリセットした所持アイテムの情報がセーブしておいた状態になり、所持アイテムの並び順も含めて復元できていれば制御成功です。


<実装画像 ロード>
動画ファイルへのリンク


 以上でこの手順は終了です。

 次は 手順23 −所持しているアイテムの追加・削除機能の追加− です。

コメントをかく


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

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

Menu



プログラムの基礎学習

コード練習

技術/知識(実装例)

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

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

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

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

レースゲーム(抜粋)

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

3D脱出ゲーム(抜粋)

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

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

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

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

3Dトップビューアクション(白猫風)

VideoPlayer イベント連動の実装例

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

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

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

private



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

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