##JsonUtility
Unityの用意してくれた機能です。
JsonUtilityを利用してセーブとロードの機能を実装していきます。
今回のセーブ、ロードはアプリを終了しても
データが保存されている、読み込める状態を指します。
下記リンクのお絵描きした絵のデータをセーブ、ロードしていきます。
【参考リンク】:【Unity(C#)】ハンドトラッキングで簡易版VRお絵かきアプリ
##JSONとは
JSON (JavaScript Object Notation)は、軽量のデータ交換フォーマットです。人間にとって読み書きが容易で、マシンにとっても簡単にパースや生成を行なえる形式です。
【参考リンク】:JSON の紹介
描いた絵のデータ(TrailRendererのパラメータ)をJSON形式に変換して保存、
保存したJSON形式のデータを読み込んで、TrailRendererのパラメータに再設定する
ということを行えばセーブ、ロードが可能になります。
##シリアライズ、デシリアライズ
シリアライズとは、複数の並列データを直列化して送信することである。
具体的には、メモリ上に存在する情報を、ファイルとして保存したり、ネットワークで送受信したりできるように変換することである。他方、既にファイルとして存在しているデータや、一旦シリアライズされたデータがネットワークから送られてきた際に、プログラムで扱えるようにする作業をデシリアライズと呼ぶ。
【参考リンク】:辞典・百科事典の検索サービス - Weblio辞書
今回の例で言うと
描いた絵のデータ(TrailRendererのパラメータ)を
JSON形式に変換してファイルへの保存に利用可能に
→シリアライズ
保存したJSON形式のデータを読み込んでUnity(C#)で利用可能に
→デシリアライズ
となります
##コード
前提として、今回作成したお絵描きアプリは
一筆ごとにTrailRendererを生成しています。
実装の順序としては下記です。
①TrailRendererのパラメータを保存する構造体を作成
②複数個存在するTrailRendererそれぞれのパラメータをまとめてリスト化
③作成したリストをJson形式にシリアライズし保存
④読み込み時にデシリアライズし、リスト分のTrailRendererを再生成して設定
では順を追って見ていきます
まずデータを入れる構造体を定義します。
こちらの構造体にはシリアライズを可能にするために
[Serializable]
というアトリビュートが必要です。
using System;
using UnityEngine;
/// <summary>
/// ペイントデータを入れる構造体
/// </summary>
[Serializable]
public struct PaintData
{
/// <summary>
/// TrailRendererの頂点用配列
/// </summary>
public Vector3[] PaintVertices;
/// <summary>
/// Paintオブジェクト生成された位置
/// </summary>
public Vector3 PaintObjectPosition;
/// <summary>
/// Paintの色情報用
/// </summary>
public Color PaintColor;
}
次に、リストを作成します。
わざわざクラスを用意してリストを定義しているのは、
リストを直接Json形式にシリアライズすることができないからです。
やってみると空のリストが返ってきました。
なので、クラスをJson形式にシリアライズする形をとっています。
こちらにも[Serializable]
をつけます。
using System;
using System.Collections.Generic;
/// <summary>
/// Jsonとして扱うデータ リストを利用するためにクラスでラップ
/// </summary>
[Serializable]
public class PaintDataWrapper
{
/// <summary>
/// ペイントデータを入れるリスト
/// </summary>
public List<PaintData> DataList = new List<PaintData>();
}
つづいて、シリアライズ、デシリアライズのロジックです。
usingステートメントは外部リソースにアクセスするときに便利らしいです。
外部リソースを利用したあと、勝手に利用停止してくれる、みたいな感じです。
メモリリークと呼ばれる現象を防ぐことができます。
【参考リンク】:外部リソースの解放には using ステートメントを使う
using System;
using System.IO;
using UnityEngine;
/// <summary>
/// Save、Load機能の実装
/// </summary>
public static class JsonDataManager
{
/// <summary>
/// パスを取得 & セーブファイル名記録
/// </summary>
private static string getFilePath () { return Application.persistentDataPath + "/PaintData" + ".json"; }
/// <summary>
/// 書き込み機能
/// </summary>
/// <param name="paintDataWrapper">シリアライズするデータ</param>
public static void Save(PaintDataWrapper paintDataWrapper)
{
//シリアライズ実行
string jsonSerializedData = JsonUtility.ToJson(paintDataWrapper);
Debug.Log(jsonSerializedData);
//実際にファイル作って書き込む
using (var sw = new StreamWriter (getFilePath(), false))
{
try
{
//ファイルに書き込む
sw.Write (jsonSerializedData);
}
catch (Exception e) //失敗した時の処理
{
Debug.Log (e);
}
}
}
/// <summary>
/// 読み込み機能
/// </summary>
/// <returns>デシリアライズした構造体</returns>
public static PaintDataWrapper Load()
{
PaintDataWrapper jsonDeserializedData = new paintDataWrapper();
try
{
//ファイルを読み込む
using (FileStream fs = new FileStream (getFilePath(), FileMode.Open))
using (StreamReader sr = new StreamReader (fs))
{
string result = sr.ReadToEnd ();
Debug.Log(result);
//読み込んだJsonを構造体にぶちこむ
jsonDeserializedData = JsonUtility.FromJson<PaintDataWrapper>(result);
}
}
catch (Exception e) //失敗した時の処理
{
Debug.Log (e);
}
//デシリアライズした構造体を返す
return jsonDeserializedData;
}
}
最後に先ほどのロジックの利用側です。
[SerializeField] private Transform _paintTrailRendererParent;
[SerializeField] private GameObject _paintTrailRendererPrefab;
private MaterialPropertyBlock _materialPropertyBlock;
private int _propertyID;
private void Start()
{
_materialPropertyBlock = new MaterialPropertyBlock();
//色のプロパティIDをintで保持
_propertyID = Shader.PropertyToID("_Color");
}
/// <summary>
/// セーブ機能
/// </summary>
private void save()
{
//ここでTrailRendererの情報を構造体、及びリストに格納する
PaintDataWrapper paintDataWrapper = new PaintDataWrapper();
PaintData paintData = new PaintData();
//Paintオブジェクト(TrailRenderer)のリストを作成
foreach (Transform child in _paintTrailRendererParent.transform)
{
//TrailRendererの情報を取得
TrailRenderer tr = child.GetComponent<TrailRenderer>();
int posCount = tr.positionCount;
Vector3[] posArray = new Vector3[posCount];
int vertCount = tr.GetPositions(posArray);
//構造体にTrailRendererの座標を格納
paintData.PaintObjectPosition = child.position;
//構造体にTrailRendererの頂点座標の配列を格納
paintData.PaintVertices = posArray;
//描画した頂点座標を確認
for (int i = 0; i < vertCount; i++)
{
Debug.Log(posArray[i]);
}
//構造体に色情報を格納
_materialPropertyBlock.SetColor(_propertyID,tr.material.color);
paintData.PaintColor = _materialPropertyBlock.GetColor(_propertyID);
//構造体をリストに追加
paintDataWrapper.DataList.Add(paintData);
}
//シリアライズ
JsonDataManager.Save(paintDataWrapper);
}
/// <summary>
/// ロード機能
/// </summary>
private void load()
{
//デシリアライズ
PaintDataWrapper paintDataWrapper = JsonDataManager.Load();
foreach (PaintData paintData in paintDataWrapper.DataList)
{
//リストのデータ分Instantiate
GameObject paintObj = Instantiate(_paintTrailRendererPrefab, paintData.PaintObjectPosition ,Quaternion.identity,_paintTrailRendererParent);
//==============================================================================================================
// TrailRenderer再設定
//==============================================================================================================
TrailRenderer paintObjTrailRenderer = paintObj.GetComponent<TrailRenderer>();
//全ての頂点を復元
paintObjTrailRenderer.AddPositions(paintData.PaintVertices);
//色情報を復元
_materialPropertyBlock.SetColor(_propertyID, paintData.PaintColor);
paintObjTrailRenderer.SetPropertyBlock(_materialPropertyBlock);
}
}
##デモ
##参考リンク
UnityのJsonUtilityでデータをJson化する
【Unity(C#)】TrailRendererの頂点座標を取得する方法
【Unity】Utf8JsonでJsonファイルを読み込み・書き込みする