徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【Unity】Jsonデータ3分クッキング

 皆さん、こんにちは。
 お昼のUnity3分クッキングの時間です。

 今回はUnityエディタを使ってJsonファイルを作っていきます。
 それでは調理を開始していきましょう。


この記事にはUnity2017.1.1f1を使用しています。

下準備

 さて、今回Jsonファイルを作っていくわけですが、そのためのエディタも拡張して作っていきますよ。
 Jsonファイルを作るだけならば、わざわざUnityをつかなくとも、コンソールのアプリケーションでも作ったほうが早い、と思う方も多いと思います。
 ぶっちゃけその通りだと思います。
 ですが、状況によってはUnityエディタ上でJsonファイルを作ることが作業の効率が良い場合もありえます。
 そんな時にぜひともご利用ください。

 それでは、材料です。

  • Unity
    ネット上でダウンロードしてください。
  • ちょっとしたC#の知識
    分からないコードはググってください。
  • 熱き正義の心
    コードを組む以上、必ず用意してください。

 ご用意いただけたでしょうか。
 それでは調理に入りましょう。

Jsonのデータになるクラスを作成

 まずJsonデータになるクラスを作っていきましょう。
 データの型としては、クラスでも構造体でもどちらでもお好みの方お使いください。
 ですが必ずSystem.Serializableのアトリビュートを付けることを忘れないでくださいね。
 そして、Jsonのパラメータとして使う変数にSerializeFieldのアトリビュートを付けてください。
 今回はクラスと構造体の合わせたものを用意してみました。

JsonData.cs

using UnityEngine;

[System.Serializable]
public class JsonData
{
    [SerializeField]
    public PersonalData[]   party   = null;
}

[System.Serializable]
public class PersonalData
{
    [SerializeField]
    public string   name    = string.Empty;
    [SerializeField, Range(1, 100)]
    public int      life    = 1;
    [SerializeField, Range(0, 50)]
    public int      attack  = 0;
    [SerializeField]
    public AccessoryData[]  accessories = null;

} // class PersonalData

[System.Serializable]
public struct AccessoryData
{
    [SerializeField]
    public string   name;
    [SerializeField, Range(0, 10)]
    public int      defense;
} // AccessoryData

 このクラスから作られるJsonデータは変数名がそのままパラメータ名になります。
 私は普段は変数名に付け合わせとしてm_を頭に付けるのですが、味がしつこくなるため今回は使用致しません。

エディタウィンドウを作成

 つづいて、Jsonデータを作成するUIとしてエディタウィンドウを作っていきましょう。
 エディタウィンドウとしては指定したパラメータをJsonファイルとして吐き出すだけのシンプルなものを作ります。
 そんなときに便利な調味料がScriptableWizardです。
 このScriptableWizardをサッと掛けるだけで、簡単にシリアライズした変数とボタンを表示するための下味を付けることが出来ます。

ScriptableWizardスクリプトリファレンス*
https://docs.unity3d.com/ScriptReference/ScriptableWizard.htmldocs.unity3d.com

 それではScriptableWizardを使って調理していきます。
 エディタ用の関数を使うので、Editorフォルダ内にスクリプトを作ることをお間違いなく。

JsonEditorWindow.cs

using UnityEngine;
using UnityEditor;

public class JsonEditorWindow : ScriptableWizard
{
    [SerializeField]
    private JsonData    m_jsonData  = null;

    [MenuItem( "Tool/JsonEditor" )]
    public static void Open()
    {
        DisplayWizard<JsonEditorWindow>( "JsonEditor", "Save" );
    }

    /// <summary>
    /// ScriptableWizardのメインとなるボタンが押された際に呼ばれるよ!
    /// 今回の場合はDisplayWizardの第二引数で指定した"Save"ボタンが押されたとき。
    /// </summary>
    private void OnWizardCreate()
    {
        string json = JsonUtility.ToJson( m_jsonData );
        string path = EditorUtility.SaveFilePanel( "名前を付けてJsonを保存しよう", "", "Setting", "json" );

        System.IO.File.WriteAllText( path, json );

        // プロジェクトフォルダ内に保存された際の対応.
        AssetDatabase.Refresh();
    }

} // class JsonEditorWindow

f:id:urahimono:20170917144506p:plain

 Saveボタンを押すことで、保存ダイアログが開かれてファイルを保存できるようになりました。

f:id:urahimono:20170917144526p:plain

 今回はUnityヘルパー産のEditorUtility.SaveFilePanel()を使って保存ダイアログを表示する調理をしました。
 ほかにもUnityヘルパー産の関数の中にはEditorUtility.SaveFilePanelInProject()というものもあります。
 EditorUtility.SaveFilePanelInProject()の場合は、同じように保存ダイアログを出すことが出来るのですが、現在のUnityプロジェクトフォルダ外に保存しようとするとエラーメッセージが出ます。
 Unityプロジェクトフォルダ内に保存しなくてはいけない場合は、こちらのほうを最寄りのスーパーなどでお買い求めください。

Jsonの盛り付けを整える

 さて、先ほどできたJsonデータをお皿に盛りつけてみましょう。

{"party":[{"name":"上カルビ","life":50,"attack":20,"accessories":[]},{"name":"冷麺","life":0,"attack":0,"accessories":[{"name":"酢","defense":5},{"name":"玉子","defense":3}]}]}

 もちろんこのままでもお召し上がりいただけるのですが、少し盛り付けを整えてみましょう。
 料理である以上、見た目も重要です。データ量は増えてしまいますが。
 盛り付ける方法は、以前FaceAPIを作っていた際のMicrosoft様の盛り付け方法を利用させていただきましょう。
docs.microsoft.com

JsonEditorWindow.cs

/// <summary>
/// ScriptableWizardのメインとなるボタンが押された際に呼ばれるよ!
/// 今回の場合はDisplayWizardの第二引数で指定した"Save"ボタンが押されたとき。
/// </summary>
private void OnWizardCreate()
{
    string json = JsonUtility.ToJson( m_jsonData );
    json        = JsonPrettyPrint( json );
    string path = EditorUtility.SaveFilePanel( "名前を付けてJsonを保存しよう", "", "Setting", "json" );

    System.IO.File.WriteAllText( path, json );

    // プロジェクトフォルダ内に保存された際の対応.
    AssetDatabase.Refresh();
}


private string JsonPrettyPrint( string i_json )
{
    if( string.IsNullOrEmpty( i_json ) )
    {
        return string.Empty;
    }
        
    i_json = i_json.Replace( System.Environment.NewLine, "" ).Replace( "\t", "" );

    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    bool quote          = false;
    bool ignore         = false;
    int  offset         = 0;
    int  indentLength   = 3;

    foreach( char ch in i_json )
    {
        switch( ch )
        {
            case '"':
                if( !ignore )
                {
                    quote = !quote;
                }
                break;
            case '\'':
                if( quote )
                {
                    ignore = !ignore;
                }
                break;
        }

        if( quote )
        {
            sb.Append( ch );
        }
        else
        {
            switch( ch )
            {
                case '{':
                case '[':
                    sb.Append( ch );
                    sb.Append( System.Environment.NewLine );
                    sb.Append( new string( ' ', ++offset * indentLength ) );
                    break;
                case '}':
                case ']':
                    sb.Append( System.Environment.NewLine );
                    sb.Append( new string( ' ', --offset * indentLength ) );
                    sb.Append( ch );
                    break;
                case ',':
                    sb.Append( ch );
                    sb.Append( System.Environment.NewLine );
                    sb.Append( new string( ' ', offset * indentLength ) );
                    break;
                case ':':
                    sb.Append( ch );
                    sb.Append( ' ' );
                    break;
                default:
                    if( ch != ' ' )
                    {
                        sb.Append( ch );
                    }
                    break;
            }
        }
    }

    return sb.ToString().Trim();
}

 これでつくられたJsonデータをお皿に盛りつけるとこのようになります。

{
   "party": [
      {
         "name": "上カルビ",
         "life": 50,
         "attack": 20,
         "accessories": [
            
         ]
      },
      {
         "name": "冷麺",
         "life": 30,
         "attack": 40,
         "accessories": [
            {
               "name": "酢",
               "defense": 5
            },
            {
               "name": "玉子",
               "defense": 3
            }
         ]
      }
   ]
}

 繰り返しになりますが、情報は美味しそうに見えるようになりますが、データ量はスペースや改行が追加した分確実に増えますので、使うかどうかお好みで。

ScriptableObjectに入力情報を保存

 さて、このまま食卓に出してもいいのですが、もう少し細部にこだわっていきましょう。
 今の状態では、エディタウィンドウを閉じると、入力していたデータが消えてしまいます。

f:id:urahimono:20170917144546p:plain

 これでは少々食べ辛いので、入力した情報をScriptableObjectに保存するようにしましょう。
 まずScriptableObject用のクラスを作成しましょう。

JsonScriptableObject.cs

using UnityEngine;

public class JsonScriptableObject : ScriptableObject
{
    [SerializeField]
    private JsonData    m_jsonData  = null;
    public  JsonData    Json
    {
        get { return m_jsonData; }
        set { m_jsonData = value; }
    }

} // class JsonScriptableObject

 このScriptableObject用のスクリプトをエディタウィンドウが開いた瞬間に読み込み、ウィンドウのパラメータを書き換えた場合に情報を書き込むようにしましょう。

JsonEditorWindow.cs

[SerializeField]
private JsonScriptableObject    m_jsonScriptableObject  = null;

private static readonly string SAVE_ASSET_PATH = "Assets/Editor/JsonScriptableObject.asset";


[MenuItem( "Tool/JsonEditor" )]
public static void Open()
{
    var window      = DisplayWizard<JsonEditorWindow>( "JsonEditor", "Save" );

    var saveAsset   = AssetDatabase.LoadAssetAtPath<JsonScriptableObject>( SAVE_ASSET_PATH );
    // ファイルが存在しないときには作成しよう。
    if( saveAsset == null )
    {
        saveAsset   = CreateInstance<JsonScriptableObject>();
        AssetDatabase.CreateAsset( saveAsset, SAVE_ASSET_PATH );
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    window.m_jsonScriptableObject   = saveAsset;
    window.m_jsonData               = saveAsset.Json;
}

/// <summary>
/// パラメータを更新した時に呼ばれるよ!
/// </summary>
private void OnWizardUpdate()
{
    if( m_jsonScriptableObject != null )
    {
        m_jsonScriptableObject.Json = m_jsonData;
    }
}

f:id:urahimono:20170917144604p:plain

 これでウィンドウを再度開いた場合にも、先ほど記述していた情報が残るようになり、とても食べやすくなってお子さんも喜ぶと思います。

f:id:urahimono:20170917144831p:plain

外部Jsonファイルを読み込む

 最後に逆にJsonファイルを読み込んで、エディタのパラメータに表示する機能を付け合わせとして用意して品数を増やしてみましょう。

 ScriptableWizardには、引数を追加することでデフォルトのボタンとは別にもう1つボタンを付けることが簡単にできます。
 忙しい奥様にはぴったりの機能です。

JsonEditorWindow.cs

[MenuItem( "Tool/JsonEditor" )]
public static void Open()
{
    var window      = DisplayWizard<JsonEditorWindow>( "JsonEditor", "Save", "Load" );

    var saveAsset   = AssetDatabase.LoadAssetAtPath<JsonScriptableObject>( SAVE_ASSET_PATH );
    // ファイルが存在しないときには作成しよう。
    if( saveAsset == null )
    {
        saveAsset   = CreateInstance<JsonScriptableObject>();
        AssetDatabase.CreateAsset( saveAsset, SAVE_ASSET_PATH );
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    window.m_jsonScriptableObject   = saveAsset;
    window.m_jsonData               = saveAsset.Json;
}

/// <summary>
/// ScriptableWizardのサブとなるボタンが押された際に呼ばれるよ!
/// 今回の場合はDisplayWizardの第三引数で指定した"Load"ボタンが押されたとき。
/// </summary>
private void OnWizardOtherButton()
{
    string path     = EditorUtility.OpenFilePanel( "Jsonファイルを開く", "", "json" );
    string json     = System.IO.File.ReadAllText( path );

    JsonData loadedJsonData = null;

    // Jsonとは異なるファイルを読んだとき用の対策。
    try
    {
        loadedJsonData  = JsonUtility.FromJson<JsonData>( json );
    }
    catch( System.Exception i_exception )
    {
        Debug.LogError( i_exception );
        loadedJsonData  = null;
    }

    if( loadedJsonData != null )
    {
        m_jsonData = loadedJsonData;
        OnWizardUpdate();
    }
}

f:id:urahimono:20170917144623p:plain
f:id:urahimono:20170917144634p:plain

追記

※ 2017/10/17 追記

 JsonScriptableObjectの情報がUnityを閉じてしまうと保存されない不具合がありました。  以下の一文を追加することで修正することができます。

JsonEditorWindow.cs

private void OnWizardUpdate()
{
    if( m_jsonScriptableObject != null )
    {
        m_jsonScriptableObject.Json = m_jsonData;
        EditorUtility.SetDirty( m_jsonScriptableObject );
    }
}

www.urablog.xyz

エンディング

 いかがでしたでしょうか。
 ジャスト3分で美味しいJsonデータが出来たと思います。
 レシピのまとめは以下に記述しておきますので、明日のお昼ご飯にご利用ください。

 来週は美味しい焼肉の焼き方についてですが、一部地域ではご覧いただけないのでご了承ください。
 それではまた来週にお会いしましょう。


【Unity】JsonEditorの作成