【Unity(C#)】画像データと一緒に関連するデータを紐づけて保存する

##はじめに
[【Unity(C#)】テクスチャを画像データに変換して端末に保存、読み込み](https://qiita.com/OKsaiyowa/items/21a9ca438efbf605c52d)
↑前回の続きです。

今回は保存した画像データに文字列の情報も付与して保存したいと思います。

##考え方
画像と文字列を1つの塊として構造体を定義します。
その構造体をJson形式として保存します。
読み込み時にはその構造体をデシリアライズします。

![構造体.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/2bdbf7ca-86bc-eae0-5c3e-0d7266bcc74d.png)


##コード

コードの構成は下記です。
①構造体
②Json変換Utilityクラス
③構造体を保存、読み込みするクラス

---
###構造体

まずは構造体を定義します。
画像のバイナリデータは`File.WriteAllBytes`で書き込むので
構造体に持たせるデータは画像データの保存先のパスとしました。

```C#
using System;

/// <summary>
/// 画像データの構造体
/// </summary>
[Serializable]
public struct ImageData
{
    /// <summary>
    /// 画像の保存先パス
    /// </summary>
    public string ImageSavePath;

    /// <summary>
    /// 画像の名前
    /// </summary>
    public string ImageName;

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="imageSavePath">保存先のパス</param>
    /// <param name="imageName">保存名</param>
    public ImageData(string imageSavePath,string imageName)
    {
        this.ImageSavePath = imageSavePath;
        this.ImageName = imageName;
    }
}
```

---
###Json変換Utilityクラス

次に構造体をJson形式に変換するクラスを定義します。
【参考リンク】：[【Unity(C#)】JsonUtilityを使ったセーブ、ロードの実装](https://qiita.com/OKsaiyowa/items/a7c679b06ddd2825e007)

```C#
using System;
using System.IO;
using UnityEngine;

/// <summary>
/// Save、Load機能の実装
/// </summary>
public static class JsonDataUtility
{
    /// <summary>
    /// 書き込み機能
    /// </summary>
    /// <param name="imageData">シリアライズするデータ</param>
    public static void Save(ImageData imageData,string path)
    {
        //シリアライズ実行
        string jsonSerializedData = JsonUtility.ToJson(imageData);
        Debug.Log(jsonSerializedData);

        //実際にファイル作って書き込む
        using (var sw = new StreamWriter (path, false)) 
        {
            try
            {
                //ファイルに書き込む
                sw.Write (jsonSerializedData);
            }
            catch (Exception e) //失敗した時の処理
            {
                Debug.Log (e);
            }
        }
    }

    /// <summary>
    /// 読み込み機能
    /// </summary>
    /// <returns>デシリアライズした構造体</returns>
    public static ImageData Load(string path)
    {
        ImageData jsonDeserializedData = new ImageData();

        try 
        {
            //ファイルを読み込む
            using (FileStream fs = new FileStream (path, FileMode.Open))
            using (StreamReader sr = new StreamReader (fs)) 
            {
                string result = sr.ReadToEnd ();
                Debug.Log(result);

                //読み込んだJsonを構造体に入れる
                jsonDeserializedData = JsonUtility.FromJson<ImageData>(result);
            }
        }
        catch (Exception e) //失敗した時の処理
        {
            Debug.Log (e);
        }

        //デシリアライズした構造体を返す
        return jsonDeserializedData;
    }
}
```

---
###構造体を保存、読み込みするクラス
構造体を保存、読み込みするクラスです。
他にもいろいろやってますが、サンプルなので良しとします。


```C#
using System.IO;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 構造体を保存、読み込み
/// </summary>
public class UseStructData : MonoBehaviour
{
    [SerializeField] private Button _saveButton;
    [SerializeField] private Button _loadButton;
    [SerializeField] private Button _resetButton;
    [SerializeField] private InputField _inputField;
    [SerializeField] private Text _inputText;
    [SerializeField] private Text _loadText;
    [SerializeField] private Image _paintImage;
    [SerializeField] private Image _loadImage;
    [SerializeField] private Painter _painter;

    private const string IMAGE_SAVE_FOLDER = "Image";
    private const string IMAGE_DATA_SAVE_FOLDER = "ImageData";
    private const string PNG = ".png";
    private const string JSON = ".json";
    
    ///  /// <summary>
    /// 保存先のパス取得
    /// </summary>
    /// <param name="folderName">区切りのフォルダ名</param>
    /// <returns>保存先のパス</returns>
    private string GetSavePath(string folderName,string fileName,string type)
    {
        string directoryPath = Application.persistentDataPath + "/" + folderName + "/";
        
        if (!Directory.Exists(directoryPath))
        {
            //まだ存在してなかったら作成
            Directory.CreateDirectory(directoryPath);
            return directoryPath + fileName + type;
        }

        return directoryPath + fileName + type;
    }

    private void Start()
    {
        //セーブボタン
        _saveButton.OnPointerClickAsObservable().Subscribe(_ =>
            {
                SaveImageData("SoftCream");
                
                //リセット
                _painter.ResetTexture();
                _inputField.text = "";
            })
            .AddTo(this);
        //ロードボタン
        _loadButton.OnPointerClickAsObservable().Subscribe(_ => LoadImageData("SoftCream")).AddTo(this);
        //リセットボタン
        _resetButton.OnPointerClickAsObservable().Subscribe(_ => _painter.ResetTexture()).AddTo(this);;
    }

    /// <summary>
    /// 構造体をシリアライズ
    /// </summary>
    private void SaveImageData(string fileName)
    {
        string pngPath = GetSavePath(IMAGE_SAVE_FOLDER, fileName, PNG);
        string jsonPath = GetSavePath(IMAGE_DATA_SAVE_FOLDER, fileName, JSON);
        
        //構造体にパス、名前を入れる　これでひとつの塊
        ImageData imageData =new ImageData(pngPath,_inputText.text);
        //Pngに変換
        byte[] bytes = _paintImage.sprite.texture.EncodeToPNG();
        //保存
        File.WriteAllBytes(pngPath, bytes);
        //構造体をJsonに変換
        JsonDataUtility.Save(imageData,jsonPath);
    }

    /// <summary>
    /// テクスチャに変換＆読み込み
    /// </summary>
    private void LoadImageData(string fileName)
    {
        string jsonPath = GetSavePath(IMAGE_DATA_SAVE_FOLDER, fileName, JSON);
        ImageData imageData = JsonDataUtility.Load(jsonPath);
        //読み込み
        byte[] bytes = File.ReadAllBytes(imageData.ImageSavePath);
        //画像をテクスチャに変換
        Texture2D loadTexture = new Texture2D(2, 2);
        loadTexture.LoadImage(bytes);
        //テクスチャをスプライトに変換
        _loadImage.sprite = Sprite.Create(loadTexture, new Rect(0, 0, loadTexture.width, loadTexture.height), Vector2.zero);
        //画像の名前を表示
        _loadText.text = imageData.ImageName;
    }
}
```

##Directory.Exists
新しくディレクトリを作って保存先のパスとして利用する方法が地味に詰まりました。
`Directory.Exists`でディレクトリが存在しているか確認して
分岐処理を用意しないとエラーが出ました。

```C#
        if (!Directory.Exists(directoryPath))
        {
            //まだ存在してなかったら作成
            Directory.CreateDirectory(directoryPath);
            return directoryPath + fileName + type;
        }
```

##デモ
上品なイラストとそのイラストの名前として入力した文字列を保存し、
読み込むことに成功しました。

![Qiita画像保存2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/efe61d1c-61bd-0f21-4627-f09c9cba8762.gif)