ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

Unity - JsonUtility:データをJSONに変換し、セーブ・ロードする -

初めに

 データを保存する方法の一つとして、JSON ファイルに変換し、保存する方法があります。そこで使用される JsonUtility についてまとめます。

JSON とは

 JSON ファイルは JavaScript Object Notation の略で JavaScript のデータ定義文をベースとした、簡易的なデータ定義言語です。
 今回はデータを JSON ファイルに書き出したり、読み込んだりすることで、セーブ、ロードをすることを目指します。

JSON の記入例
[
  {"id" : "1", "name" : "tanaka"},
  {"id" : "2", "name" : "nakata"}
]

(主な記述のルール)

  • キーをダブルクォーテーションで囲む
  • キーと値をコロンで区切る
  • 複数の組み合わせを記述する場合はカンマで区切る

 JSON の説明は下のリンク先をご覧ください。
www.tohoho-web.com

JsonUtility とは

 Unity 上で Json ファイルを扱う機能になります。

docs.unity3d.com

 できることは下記の3つになります。

  • FromJson:JSONから変換する
  • FromJsonOverwrite:JSON ファイルから既存のオブジェクトに値を上書きする
  • ToJson:JSONファイルに変換する
FromJson

public static T FromJson(string json);

 引数は string(変換したい JSON のファイル名)になります。

FromJsonOverwrite

public static void FromJsonOverwrite(string json, object objectToOverwrite);

 第1引数はロードしたい JSON ファイル、第2引数は JSON ファイルから変換した値を直接代入したいオブジェクトになります。

ToJson

public static string ToJson (オブジェクトobj );

 引数は JSON ファイルに変換したいオブジェクトになります。

JsonUtility を使ってセーブ・ロードする

 今回はステータスを作ってみます。完成品のスクリプトを載せておきます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;  //AssetDatabaseを使うために追加
using System.IO;  //StreamWriterなどを使うために追加
using System.Linq;  //Selectを使うために追加

public class JsonScript : MonoBehaviour
{
    //保存先
    string datapath;

    void Awake()
    {
      //保存先の計算をする
      //これはAssets直下を指定. /以降にファイル名
      datapath = Application.dataPath + "/TestJson.json";
    }

    // Start is called before the first frame update
    void Start()
    {
      //playerデータを取得
      Player player = new Player ();

      //JSONファイルがあればロード, なければ初期化関数へ
      if(FindJsonfile())
      {
        player = loadPlayerData();
      }
      else
      {
        Initialize(player);
      }
    }

    //セーブするための関数
    public void savePlayerData(Player player)
    {
      StreamWriter writer;

      //playerデータをJSONに変換
      string jsonstr = JsonUtility.ToJson (player);

      //JSONファイルに書き込み
      writer = new StreamWriter(datapath, false);
      writer.Write (jsonstr);
      writer.Flush ();
      writer.Close ();
    }

    //JSONファイルを読み込み, ロードするための関数
    public Player loadPlayerData()
    {
      string datastr = "";
      StreamReader reader;
      reader = new StreamReader (datapath);
      datastr = reader.ReadToEnd ();
      reader.Close ();

      return JsonUtility.FromJson<Player> (datastr);
    }

    //JSONファイルがない場合に呼び出す初期化関数
    //初期値をセーブし, JSONファイルを生成する
    public void Initialize(Player player)
    {
      player.name = "aaa";
      player.hp = 12;
      player.attack = 6;
      player.defense = 5;

      savePlayerData(player);
    }

    //JSONファイルの有無を判定するための関数
    public bool FindJsonfile()
    {
      string[] assets = AssetDatabase.FindAssets(datapath);
      Debug.Log(assets.Length);
      if(assets.Length != 0)
      {
        string[] paths = assets.Select(guid => AssetDatabase.GUIDToAssetPath(guid)).ToArray();
        Debug.Log($"検索結果:\n{string.Join("\n", paths)}");
        return true;
      }
      else
      {
        Debug.Log("Jsonファイルがなかった");
        return false;
      }
    }
}

//Playerのデータとなるクラスの定義
[System.Serializable]
public class Player
{
    public string name;
    public int hp;
    public int attack;
    public int defense;
}

 初めて実行したときは、JSONファイルはまだ作られていません。そのため、Initialize 関数に入り、初期値が代入されます。そして、savePlayerData が実行され JSONファイルが作られます。
 2回目以降は、JSONファイルがすでにあるため、loadPlayerData に入り、ロードされます。

(実行前)

(実行後)

 JSON ファイルが生成され、初期値がJSONファイルに保存されていることがわかります。

保存したいデータを記述

 保存したいデータのクラスを定義する必要があります。今回のスクリプトでは Player のクラスに当たる部分です。

//Playerのデータとなるクラスの定義
[System.Serializable]
public class Player
{
    public string name;
    public int hp;
    public int attack;
    public int defense;
}
Application.dataPath

 プラットフォームごとのデータの保存先を返します。

datapath = Application.dataPath + "/TestJson.json";

docs.unity3d.com

StreamWriter、StreamReader

 テキストファイルにデータを書き込んだり、読み込んだりするための関数です。

 名前空間に "using System.IO;" を追加することで使えます。詳細は下のリンク先をご覧ください。

johobase.com
johobase.com

AssetDataBase

 AssetDataBase はアセットにアクセスし、操作するためのインターフェースです。

 名前空間に "using UnityEditor;" を追加することで使えます。

 今回使った "FindAssets" はフィルター検索の文字列を使ってアセットデータベースを検索します。

public static string FindAssets (string filter);

 今回はファイルの有無だけ判定できればよいので、string の長さが 0 であればファイルがないと判定しています。

//JSONファイルの有無を判定するための関数
    public bool FindJsonfile()
    {
      string[] assets = AssetDatabase.FindAssets(datapath);
      Debug.Log(assets.Length);
      if(assets.Length != 0)
      {
        string[] paths = assets.Select(guid => AssetDatabase.GUIDToAssetPath(guid)).ToArray();
        Debug.Log($"検索結果:\n{string.Join("\n", paths)}");
        return true;
      }
      else
      {
        Debug.Log("Jsonファイルがなかった");
        return false;
      }
    }

 細かい使い方は下のサイトで分かりやすく説明してくださっています。
www.urablog.xyz
docs.unity3d.com
docs.unity3d.com

JsonUtility を使う上での注意点

List や Dictionary などが直接使えない

 マニュアルを見ると、サポートする型の部分に「プリミティブ型や配列など、他の型を直接 API に渡すことは現在サポートされていません。それらの型は、クラスや構造体などの型にラップする必要があります。」と書かれています。
docs.unity3d.com

 つまり、List や Dicitionary などの型は対象外となり、直接は使えないことがわかります。

 試しに List が含まれる下のクラスを JSON に変換してみると、次のようになります。

//Playerのデータとなるクラスの定義
[System.Serializable]
public class Player
{
    public string name;
    public int hp;
    public int attack;
    public int defense;
    public List<int[]> list;
}

 list のデータがちゃんと保存されていないことがわかります。(エラーは吐きませんでした)

解決策

 Json.Net というパッケージを使うと解決できるようです。これは JsonUtility より汎用性が高く Unity 以外でも使われている機能になります。JsonUtility と比較すると若干処理が遅いようですが、処理速度を気にしない場合は Json.NET のほうが多機能のため、使いやすいかもしれません。

後日、別に記事をまとめます。

最後に

 JsonUtility とファイルに読み書きするための StreamWriter、StreamReader を使っています。Playerクラスの中身を変えれば、使いまわせると思います。