2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Unity(C#)】JsonUtilityを使ったセーブ、ロードの実装

Last updated at Posted at 2020-04-12

##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);
        }
    }

##デモ

見たところで伝わりませんが、ちゃんとできてました。
PaintSaveLoad.gif

##参考リンク
UnityのJsonUtilityでデータをJson化する

【Unity(C#)】TrailRendererの頂点座標を取得する方法
【Unity】Utf8JsonでJsonファイルを読み込み・書き込みする

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?