【Unity】オブジェクト(クラス・構造体)、配列、リスト、辞書をJSONでPlayerPrefsに保存する 
2017/12/15 Fri [edit]
今回はちょっと工夫してJSON変換を利用して、配列や辞書などを保存する方法をやってみよう。一般的には PlayerPrefsX など PlayerPrefs を拡張したライブラリなどは既に配布されているので、そちらを使ってるのなら必要ないかも知れない。しかしそういうライブラリも基本的には要素などを文字列化して保存しているようなので、JSON形式にするのもアリだと思った。今回紹介する方法は正しいやり方というわけでもなく「JSON使うとわりと短く書けるよ」という1つの手段と思って貰えれば良い。

なお、これらの関数(メソッド)は現在配布しているプラグインにライブラリとして入っている。自分で作るのが面倒だと思ったら、ダウンロードして使うのも良いだろう。クラス名は「XPlayerPrefs」となっていて、フルパスは「Assets/FantomLib/Scripts/System/XPlayerPrefs.cs」だ。考え方がわかれば、他の型のオブジェクトでも保存可能にできるだろう。
■オブジェクト(クラス・構造体)を PlayerPrefs に保存/取得する
■配列を PlayerPrefs に保存/取得する
■リスト(List)を PlayerPrefs に保存/取得する
■辞書(Dictionary)を PlayerPrefs に保存する
(※) Unity 2017.2.0f3 / Windows10(x64) で確認
■UnityでJSON変換をする
まず最初に UnityでJSON変換する方法をおさらいしておこう。JSON形式自体はWikipediaなどの資料をみて欲しいが、おおむねキーと値のペアを文字列で表現する方法と考えれば良い。Unity では JsonUtility という静的クラスで簡単に変換できるので、その例を書いてみよう。
●クラスをJSONに変換する
using System;
using UnityEngine;
//変換するクラス(※内容は任意)
[Serializable]
class Data
{
public string s = "abc";
public int i = 2;
public float f = 3.5;
public bool b = true;
}
string json = JsonUtility.ToJson(new Data());
Debug.Log(json);
●JSONからクラスに変換する(オブジェクトを新規に生成)
using System;
using UnityEngine;
//変換するクラス(※内容は任意)
[Serializable]
class Data
{
public string s = "abc";
public int i = 2;
public float f = 3.5;
public bool b = true;
public override string ToString() {
return "s = " + s + ", i = " + i + ", f = " + f + ", b = " + b;
}
}
string json = "{\"s\":\"xyz\",\"i\":2,\"f\":3.5,\"b\":false}";
Data data = JsonUtility.FromJson<Data>(json);
Debug.Log(data);
●JSONからクラスに変換する(既存のオブジェクトに上書き)
using System;
using UnityEngine;
//変換するクラス(※内容は任意)
[Serializable]
class Data
{
public string s = "abc";
public int i = 2;
public float f = 3.5;
public bool b = true;
public override string ToString() {
return "s = " + s + ", i = " + i + ", f = " + f + ", b = " + b + ", hashCode = " + this.GetHashCode();
}
}
string json = "{\"s\":\"xyz\",\"i\":1,\"f\":1.4142,\"b\":false}";
Data data = new Data();
Debug.Log(data);
JsonUtility.FromJsonOverwrite(json, data);
Debug.Log(data);
s = xyz, i = 1, f = 1.4142, b = False, hashCode = -1543062080
例として使っている Data というクラス(構造体でも可)は任意のメンバでも構わない。
JSONからクラスに変換する際には「JsonUtility.FromJson()」と「JsonUtility.FromJsonOverwrite()」を使う。これらはオブジェクト(クラス)を新規に生成するか、既存に上書きするかの違いだけだ。上書きの方は「this.GetHashCode()」を ToString() に付け加えているが、値が変わってないことがわかる(ref のようなものと思えば良い)。
JSON化するオブジェクトはシリアライズできるように "[Serializable]" を付けておく必要がある。なお、保存できるフィールドは public になるが、実行時に private で使いたい場合は "[SerializeField] private ~" のように定義すれば良い。
基本はこれだけなので、ここからはオブジェクトの型によって少し工夫して保存する方法を考えてみよう。
■オブジェクト(クラス・構造体)を PlayerPrefs に保存/取得する
基本的には前述したクラスからJSONに変換する方法で、クラスなどを文字列化し、PlayerPrefs を利用して保存・取得するやり方になる。後述する配列やリスト、辞書などもこのメソッドを利用する。使い回しできるので static なメソッドで良いだろう。
●オブジェクト(クラス・構造体)を PlayerPrefs に保存/取得する
using System;
using System.Collections.Generic;
using UnityEngine;
//オブジェクトを JSON 形式(文字列型)に変換して保存する
public static void SetObject<T>(string key, T obj)
{
PlayerPrefs.SetString(key, JsonUtility.ToJson(obj));
}
//JSON 形式(文字列型)で保存されたデータをオブジェクトとして生成して返す
public static T GetObject<T>(string key, T def = default(T))
{
string json = PlayerPrefs.GetString(key);
return !string.IsNullOrEmpty(json) ? JsonUtility.FromJson<T>(json) : def;
}
//JSON 形式(文字列型)で保存されたデータをオブジェクトに上書きする
public static void GetObjectOverwrite<T>(string key, ref T obj)
{
string json = PlayerPrefs.GetString(key);
if (!string.IsNullOrEmpty(json))
JsonUtility.FromJsonOverwrite(json, obj);
}
//メインでは・・・
//クラスを保存(構造体でも同じ)
Data data = new Data(); //※任意のクラス
SetObject("key", data); //key は保存キー
PlayerPrefs.Save();
//保存されたクラスを読み込む(新規に生成)
Data data = GetObject<Data>("key");
//保存されたクラスを読み込む(既存に上書き)
Data data = new Data();
GetObjectOverwrite<Data>("key", ref data);
例に使っている「Dataクラス」は前述のものを使っていると考えて欲しい(※内容は任意)。
「GetObject()」「GetObjectOverwrite()」の違いは前述のJSON保存/取得と同じように、読み込んだオブジェクトを新規で生成するか、既存に上書きするかの違いだけだ。Unity の JSON利用そのままなので難しくはないだろう。
ちなみに中身はただの string 型で保存した PlayerPrefs なので、以下のように文字列で取得すれば、保存された JSON の文字列が確認できる。
string json = PlayerPrefs.GetString("key");
Debug.Log(json);
■配列を PlayerPrefs に保存/取得する
ここからは主に前述の「SetObject()」「GetObject()」を利用して配列などの型を保存する方法になる。といっても配列そのままでは JsonUtility にはつっこめない。ではどうすればいいかと言うと、無理矢理クラスのメンバにしてしまえば良いのである。実際には配列とリストくらいしかメンバにしても保存できないようだが、一時的にクラスでラップすることにより、オブジェクトとして保存するのと同様にできる。結果的にいくつか変換を重ねてるので一番良いやり方とは言えないかもしれないが、何より簡単に書けるのと、保存内容はJSON形式なので、色々応用が効くというメリットはあるだろう(独自形式の方が高速かも知れないが、互換性は低くくなる)。用途に合わせて選べば良いと思う。
using System;
using System.Collections.Generic;
using UnityEngine;
//静的配列を JSON 形式(文字列型)に変換して保存する
public static void SetArray<T>(string key, T[] arr)
{
SetObject(key, new ArrayWrap<T>(arr));
}
//JSON 形式(文字列型)で保存された要素を配列として生成して返す
public static T[] GetArray<T>(string key, T[] def = null)
{
ArrayWrap<T> obj = GetObject<ArrayWrap<T>>(key);
return obj != null ? obj.ToArray() : def;
}
//静的配列をラップするクラス
[Serializable]
private class ArrayWrap<T>
{
public T[] array;
public ArrayWrap(T[] array)
{
this.array = array;
}
public T[] ToArray()
{
return (T[])array.Clone(); //複製を返す
}
}
//メインでは・・・
//配列を保存
int[] arr = { 1, 3, 5, 7, 9 };
SetArray("key", arr);
PlayerPrefs.Save();
//保存された配列を読み込む(新規に生成)
int[] arr = GetArray("key");
このコードのキモは「ArrayWrap」というクラスである。このクラスは要するにメンバに配列を1つ持ち、それを利用して JsonUtility で保存してしまえというわけである。
中身はただの string 型で保存した PlayerPrefs なので、以下のように文字列で取得すれば、メンバの名前 "array" をキーとした配列 "[1,3,~]" になっていることがわかる。
string json = PlayerPrefs.GetString("key");
Debug.Log(json);
■リスト(List)を PlayerPrefs に保存/取得する
前述の SetArray() を理解できたのなら、ほとんど説明はいらないだろう。仕組みは同じで内容がリスト(List)になっただけだ。
using System;
using System.Collections.Generic;
using UnityEngine;
//リストを JSON 形式(文字列型)に変換して保存する
public static void SetList<T>(string key, List<T> list)
{
SetObject(key, new ListWrap<T>(list));
}
//JSON 形式(文字列型)で保存された要素をリストとして生成して返す
public static List<T> GetList<T>(string key, List<T> def = null)
{
ListWrap<T> obj = GetObject<ListWrap<T>>(key);
return obj != null ? obj.ToList() : def;
}
//リスト(List)をラップするクラス
[Serializable]
private class ListWrap<T>
{
public List<T> list;
public ListWrap(List<T> list)
{
this.list = list;
}
public List<T> ToList()
{
return new List<T>(list); //複製を返す
}
}
//メインでは・・・
//リストを保存
List<int> list = new List<int>() { 2, 4, 6, 8 };
SetList("key", list);
PlayerPrefs.Save();
//保存されたリストを読み込む(新規に生成)
List<int> list = GetList<int>("key");
これも同じようにリスト(List)をラップするクラス(ListWrap)を作り、無理矢理メンバにすることにより JsonUtility で保存している。基本的には配列の保存と変わらない(例えばメンバの名前を合わせれば、どっちでも読み込める)。
中身はただの string 型で保存した PlayerPrefs なので、以下のように文字列で取得すれば、メンバの名前 "list" をキーとした配列 "[2,4,~]" になっていることがわかる。
string json = PlayerPrefs.GetString("key");
Debug.Log(json);
■辞書(Dictionary)を PlayerPrefs に保存/取得する
これも前述の配列の保存を少し応用したものと言って良い。残念ながら Dictionary はクラスのメンバにしても JSONには変換できないのだが、そのキーの配列/値の配列を取り出すことは簡単にできるので、キーの配列と値の配列の2つをメンバに持つクラスでラップすれば良いだけだ。
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
//辞書(Dictionary)を JSON 形式(文字列型)に変換して保存する
public static void SetDictionary<K, V>(string key, Dictionary<K, V> dic)
{
SetObject(key, new DictionaryWrap<K, V>(dic));
}
//JSON 形式(文字列型)で保存された要素を辞書として生成して返す
public static Dictionary<K, V> GetDictionary<K, V>(string key, Dictionary<K, V> def = null)
{
DictionaryWrap<K, V> obj = GetObject<DictionaryWrap<K, V>>(key);
return obj != null ? obj.ToDictionary() : def;
}
//辞書(Dictionary)をラップ[※配列に変換]
[Serializable]
private class DictionaryWrap<K, V>
{
[SerializeField] private K[] keys; //キー配列(JSON保存される)
[SerializeField] private V[] values; //値 配列(JSON保存される)
public DictionaryWrap(Dictionary<K, V> dic)
{
keys = dic.Keys.ToArray();
values = dic.Values.ToArray();
}
public Dictionary<K, V> ToDictionary()
{
return keys.Select((k, i) => new { k, v = values[i] })
.ToDictionary(a => a.k, a => a.v); //※重複キーはエラーになるので注意
}
}
//メインでは・・・
//辞書を保存
Dictionary<string, int> dic = new Dictionary<string, int>() {
{"alice", 65 },
{"becky", 80 },
{"cindy", 72 },
{"daisy", 91 },
{"eliza", 88 },
};
SetDictionary("key", dic);
PlayerPrefs.Save();
//保存された配列を読み込む(新規に生成)
Dictionary<string, int> dic = GetDictionary<string, int>("key");
これも考え方は今までと同じだ。辞書の代わりにキーの配列と値の配列をメンバに持つクラス(DictionaryWrap)を作り、JsonUtility で保存しているだけだ。
読み出したJSONで辞書を生成する箇所は System.Linq(Enumerable) を使っているが、重複キーを許さないことだけ注意しよう。普通に使ってる分には Dictionary は重複キーを持てないので気にする必要はないハズだ。
中身はただの string 型で保存した PlayerPrefs なので、以下のように文字列で取得すれば、メンバの名前 "keys" と "values" の2つの配列になっていることがわかる。
string json = PlayerPrefs.GetString("key");
Debug.Log(json);
どのメソッドも結局は単なる文字列として同じフォーマットで保存されているので、相互変換は簡単だ。その辺りがJSONで保存するメリットでもある。JSONは元々ネットワークを介した通信で使われることが多いので、他のアプリとの連携などもしやすいだろう。せっかくなので色々なものに利用するのもアリだと思う(ちなみにプラグイン内のカスタムダイアログは、JSONを使って Unity→Android[Java]に全データを送ってダイアログを生成している)。
(関連記事)
【Unity】Androidでカスタマイズしたダイアログを動的生成できるプラグインを作ってみた
【Unity】Androidのトーストやダイアログ、通知、音声認識、ハード音量操作など基本的な機能を使えるプラグインを作ってみた
【Unity】Androidのテキスト読み上げ(TextToSpeech)を使う
【Unity】Androidでスライダーで設定を変更するダイアログを使う
【Unity】Androidでスイッチで設定を変更するダイアログを使う
【Unity】Androidの選択ダイアログを使う
【Unity】Androidで日付・時刻選択ダイアログ(DatePicker, TimePicker)を使う
【Unity】Androidで数値・半角英数・パスワード入力ダイアログを使う
【Unity】Androidでテキスト入力ダイアログを使う
- 関連記事
-
-
【Unity】カスタムダイアログを簡単に生成するプレファブを作ってみた(CustomDialogController)
-
【Unity】アイコン画像のフォーマット警告:Compressed texture XXX is used as icon. This might compromise visual quality of~ を消す
-
【Unity】【C#】InputSystem.TouchPhase の IsActive(), IsEndedOrCanceled() [拡張メソッド] の具体値
-
【Unity】Unity2018 でビルドエラー「CommandInvokationFailure: Gradle build failed.」が出る
-
【Unity】InputField のキャレット(点滅するカーソル) が見えないときは…
-
トラックバック
トラックバックURL
→http://fantom1x.blog130.fc2.com/tb.php/285-31aa1bec
この記事にトラックバックする(FC2ブログユーザー)
| h o m e |