MiniJSON で UnityEngine.Vector3[] を永続化する
はじめに
本記事では「あるプレイヤーがゲーム中に作ったマップ的なものの情報をファイルに書き込んで保存し、それを別のプレイヤーに渡して読み込んで利用する」ようなことを想定しています。
ライブラリ捜索
PlayerPrefs
だと、コンピュータごとにレジストリに書き込んだりするので、データの再配布は難しそうです。
Webのプログラムのように、テキストファイルに書き込んでしまうのが一番楽かと思い、Unityのデータのjsonへの書き込みおよびjsonテキストファイルからUnityのデータへの復元が出来るライブラリは無いかと調べて見たところ、 JsonFX と MiniJSON という2つのライブラリに出会いました。
- paynechu/jsonfx-for-unity3d · GitHub
- Unity3D: MiniJSON Decodes and encodes simple JSON strings. Not intended for use with massive JSON strings, probably < 32k preferred. Handy for parsing JSON from inside Unity3d.
JsonFXの方が多機能で便利そうですが、ライブラリが結構重量級です。今回は非常に軽量でファイル1個プロジェクトに置いておくだけで使えちゃうMiniJSONの方を利用してみます。
やること
「マップ的な物の情報」として、ここでは簡単のため、 Vector3[]
配列情報を扱う事にします。下記のようなデータをファイルに書き出し、その後読み込んでデータとして再現します。
// Vector3 array to be serialized. Vector3[] v3Positions = new Vector3[]{ new Vector3 (0.0f, 0.0f, 0.0f), new Vector3 (1.1f, 1.1f, 1.1f), new Vector3 (2.2f, 2.2f, 2.2f) };
行うのは以下の処理です
Vector3[]
配列をx,y,z にばらしながらList<float>
に格納していく- MiniJSON の
Json.Serialize()
で、List<float>
を json形式のテキストに変換する- そのテキストをファイルに書き込む
以下では、填まった点をいくつかピックアップしてメモしておきます
List<float>
を エンコードデコードする
MiniJSONは IDictionary か IList を実装したクラスのみを扱えるので、ここでは Vector3[]
を List<float>
に変換して扱っています。素直に考えると List<Vector3>
をjsonに変換、復号できるとナイス!と思うのですが、MiniJSONのコードを読んでみたところ、扱えるのはC# 組み込みのデータ型のみのようです。従って、Vector3 の中身を展開してx, y, zひとつずつ格納していきます。
MiniJSONで扱えるデータ型(これらをさらに Dictionary や Listでコレクションして使うのも可能)
MiniJSON.cs Serializer#SerializeOther() https://gist.github.com/darktable/1411710#file-minijson-cs-L523 を参照
- string
- bool
- char
- float
- int, uint, long, sbyte, byte, short, ushort, ulong
- double
- decimal
読み書きするファイルの置き場
こちらの記事様が分かりやすかったです
この記事では以下の様な感じで、ディレクトリの生成、ファイルパスの指定を行っています。
using System.IO; // 中略 // write to a file string storagePath = Application.dataPath + "/data"; // create directory if it doesn't exist. if (!Directory.Exists (storagePath)) { Directory.CreateDirectory (storagePath); } string filepath = storagePath + "/positions.json";
0.0f
が 0
になる問題への対処
MiniJSONでは、Json.Serialize()
時に、floatのつもりで渡した 0.0f
や 100.0f
のような「ちょうど整数に変換できる浮動小数点値」が、jsonテキストに変換される際に整数になってしまいます。
このことは、MiniJSON のページの、ユーザコメントでも指摘がされていますが、いまのところ対処はなされていないようです。
なぜそんなことが起きるか調べてみたところ、数値を文字列に変換する際に、 value.ToString("R")
という指定を行っているからのようです。 "R"
の指定については、下記サイトが詳しいです
1.0f
は間違いなく正確に 1
なので、数値の正確さを保つ、という目的に対してMiniJSONのこの措置は理にかなっていると思われます。
ただ、MiniJSONはListを復号する際、Genericとしてどのデータ型のListになっているかを保障せず、List<object>
として復号します。Listの値をひとつずつ取り出してfloatとして再利用したいのですが、この際に、objectとして0
というデータが入っているか0.0
というデータが入っているかで、castの仕方がことなります。そこで、ここでは以下の様にtry-catchでそれを回避しています
for (int i = 0; i< v3posNum; i++) { v3PositionsAgain [i] = new Vector3 (); v3PositionsAgain[i].x = objectToFloat(deserializedDoubleList [3 * i + 0]); v3PositionsAgain[i].y = objectToFloat(deserializedDoubleList [3 * i + 1]); v3PositionsAgain[i].z = objectToFloat(deserializedDoubleList [3 * i + 2]); } // 中略 float objectToFloat(object objInList) { // 復号されたobjectが intでもdoubleでも対処出来るように用意した関数 try{ return (float)(double)objInList; } catch { return (float)(long)objInList; } }
またここにも一癖あります。MiniJSONは仕様として、floatはdoubleに復号、intはlongに復号するよう求めています。上記 objectToFloat()
関数も、それに従ったかたちとなっています。
完成
最後にコードをまとめて記載します
using UnityEngine; using System.Collections; using System.Collections.Generic; using MiniJSON; using System.IO; public class Serializer : MonoBehaviour { void Start () { // Vector3 array to be serialized. Vector3[] v3Positions = new Vector3[]{ new Vector3 (0.0f, 0.0f, 0.0f), new Vector3 (1.1f, 1.1f, 1.1f), new Vector3 (2.2f, 2.2f, 2.2f) }; List<float> positionList = new List<float> (); foreach (Vector3 v3pos in v3Positions) { positionList.Add (v3pos.x); positionList.Add (v3pos.y); positionList.Add (v3pos.z); } string serialized = Json.Serialize (positionList); Debug.Log ("serialized text = " + serialized); // write to a file string storagePath = Application.dataPath + "/data"; // create directory if it doesn't exist. if (!Directory.Exists (storagePath)) { Directory.CreateDirectory (storagePath); } string filepath = storagePath + "/positions.json"; Debug.Log (filepath); File.WriteAllText (filepath, serialized); //----------------------------------------------------------- // re-read from the same file var jsonTextFromFile = File.ReadAllText (filepath); var deserializedDoubleList = Json.Deserialize (jsonTextFromFile) as List<object>; int v3posNum = deserializedDoubleList.Count / 3; Vector3[] v3PositionsAgain = new Vector3[v3posNum]; for (int i = 0; i< v3posNum; i++) { v3PositionsAgain [i] = new Vector3 (); v3PositionsAgain[i].x = objectToFloat(deserializedDoubleList [3 * i + 0]); v3PositionsAgain[i].y = objectToFloat(deserializedDoubleList [3 * i + 1]); v3PositionsAgain[i].z = objectToFloat(deserializedDoubleList [3 * i + 2]); } } float objectToFloat(object objInList) { try{ return (float)(double)objInList; } catch { return (float)(long)objInList; } } }
その他メモ
参考にさせていただいたサイト様
JsonFXについてはこちらの記事様が良くまとまっていました。
MiniJSON の問題への、本記事で扱わなかった対処
- 0.0f が 0 になる問題
こちらのコメント にあるように、 .ToString("F")
系の指定をすると、数値がちゃんと固定小数点っぽい表示で文字列に変換されます。しかしこの場合、 .ToString("R")
指定をした場合のような数値の正確性は担保されません。ご利用方に気をつけましょう、という感じ。
- Vector3 とか扱えない問題
下記サイトで、MiniJSONの改善版がアップされています。Vector3
, Color
や自作のクラスも扱えるようになる様です。試していません。