【Unity】JSON ファイルから定数ファイル(*.cs)を生成するEditor拡張

DEVELOP, Unity

JSON ファイルから定数ファイルの一覧を生成したいことが時々あります。サーバー側のエラーコード一覧とか。

目次

ツールの外観

必要なファイルをドラッグ&ドロップで放り込んで、ファイル名を設定してMAKEボタンを押すと生成されます。

サンプルの生成元JSONファイル

今回はこんな形のJSONファイルを *cs の定数ファイルとして生成します。

{
    "master": [
        {
            "name": "Kazupon",
            "value": "100"
        }
    ]
}

生成される*.cs ファイル

namespace mira
{
    //----------------------------------------------------------------------------------------------
    /// <summary>
    /// ConstantMakerを使用して Sample.txt から生成した定数
    /// </summary>
    //----------------------------------------------------------------------------------------------
    public class Sample
    {
        public const int Kazupon = 100;
    }
}

Editor 拡張 コード

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEditor;
 
namespace mira
{
    /// <summary>
    /// Jsonを基に 定数 を定義したファイルを生成します
    /// </summary>
    public class JsonToConstant : EditorWindow
    {
        [System.Serializable]
        public class JsonModel
        {
            [System.Serializable]
            public class Record
            {
                public string name;
                public string value;
            }
 
            public List<Record> master = null;
        }
 
        //=== Fielad
        private readonly string FileExtension = ".cs";
        private readonly string Tab = "    ";
        private readonly string[] ModeList = new string[] {
            "Make new file",
            "Replace existed file",
        };
 
        private TextAsset targetAsset = null;
        private TextAsset outputFile = null;
        private DefaultAsset outputDir = null;
        private string outputPath = string.Empty;
        private string className = string.Empty;
        private int mode = 0;
 
        private GUIStyleState tipsLabelStyle = new GUIStyleState();
 
        private void Awake()
        {
            Reset();
        }
 
        private void Reset()
        {
            targetAsset = null;
            outputFile = null;
            outputDir = null;
            outputPath = string.Empty;
            className = string.Empty;
        }
 
        /// <summary>
        /// メニューにアイテムを追加、選択するとWindowを開きます 
        /// </summary>
        [MenuItem("Tools/JsonToConstant")]
        static void Open()
        {
            var window = EditorWindow.GetWindow<JsonToConstant>(title: "JsonToConstant");
            window.Show();
        }
        /// <summary>
        /// Jsonを基にした定数クラスを `path` に作成します. 既に存在していれば上書きされます
        /// </summary>
        private void MakeConstants(TextAsset json, string className, string path)
        {
            Debug.Log("Making Start");
 
            StringBuilder text;
            akeText(out text, json, className);
 
            Debug.Log("Save file to = " + path);
            File.WriteAllText(path, text.ToString(), Encoding.UTF8);
            AssetDatabase.Refresh(ImportAssetOptions.ImportRecursive);
 
            Debug.Log("Making Finish");
        }
 
        /// <summary>
        /// ファイルに書き込むテキストデータを作成します
        /// </summary>
        private void MakeText(out StringBuilder text, TextAsset json, string className)
        {
            string jsonFileName = Path.GetFileName(AssetDatabase.GetAssetPath(json));
 
            JsonModel model;
            MakeConstantWithJson(out model, json);
 
            text = new StringBuilder();
 
            text.AppendLine("namespace mira");
            text.AppendLine("{");
            text.AppendLine(MakeClassSummary(jsonFileName));
            text.AppendLine(Tab + "public class " + className);
            text.AppendLine(Tab + "{");
 
            foreach (JsonModel.Record record in model.master)
            {
                string define = MakeDefine(record);
                text.AppendLine(define);
 
                Debug.Log("Maked define = " + record.name);
            }
 
            text.AppendLine(Tab + "}");
            text.AppendLine("}");
        }
 
        private void MakeConstantWithJson(out JsonModel model, TextAsset json)
        {
            model = JsonUtility.FromJson<JsonModel>(json.text);
            Debug.Assert(model != null, "Json conversion failed.");
        }
 
        private string MakeClassSummary(string jsonFileName)
        {
            string summary =
            Tab + "/// <summary>\n" +
            Tab + "/// JsonToConstantを使用して " + jsonFileName + " から生成した定数\n" +
            Tab + "/// </summary>\n";
 
            return summary;
        }
        /// <summary>
        /// int > float > string の優先度で変換可能かチェックします
        /// </summary>
        private string MakeDefine(JsonModel.Record record)
        {
            string constant = record.name;
            string value = record.value.TrimStart();   // 不要なスペースを削除
 
            int iv = 0;
 
            if (int.TryParse(value, out iv) == true)
            {
                return MakeDefineAsInt(constant, value);
            }
 
            float fv = 0;
            if (float.TryParse(value, out fv) == true)
            {
                return MakeDefineAsFloat(constant, value);
            }
 
            return MakeDefineAsString(constant, value);
        }
 
        private string MakeDefineAsInt(string constant, string value)
        {
            return string.Format(Tab + Tab + "public const int {0} = {1};", constant, value);
        }
 
        private string MakeDefineAsFloat(string constant, string value)
        {
            value = value + "f";
            return string.Format(Tab + Tab + "public const float {0} = {1};", constant, value);
        }
 
        private string MakeDefineAsString(string constant, string value)
        {
            return string.Format(Tab + Tab + "public const string {0} = \"{1}\";", constant, value);
        }
 
        private void OnGUI()
        {
            EditorGUILayout.HelpBox("Jsonから定数定義した.csファイルを作成します", MessageType.Info);
 
            UpdateGUIOfTargetJsonAsset(ref targetAsset);
            EditorGUILayout.Separator();
 
            UpdateGUIOfMode(ref mode, ModeList);
            EditorGUILayout.Separator();
 
            if (mode == 0)
            {
                UpdateGUIOfMakeNewFile(ref outputDir, ref className, ref outputPath);
            }
            else
            {
                UpdateGUIOfReplaceExistedFile(ref outputFile, ref className, ref outputPath);
            }
            EditorGUILayout.Separator();
 
            UpdateGUIOfOutputPath(outputPath, className);
            EditorGUILayout.Space();
            EditorGUILayout.Space();
            EditorGUILayout.Space();
 
            UpdateGUIOfMakingButton();
        }
 
        private void UpdateGUIOfTargetJsonAsset(ref TextAsset target)
        {
            GUILayout.Label("# Json file", EditorStyles.boldLabel);
            GUILayout.Label("Please drag & drop the source file.");
 
            TextAsset asset = EditorGUILayout.ObjectField(target, typeof(TextAsset), false) as TextAsset;
 
            if (asset == null)
            {
                EditorGUILayout.HelpBox("Json file is empty.", MessageType.Error);
                return;
            }
 
            target = asset;
 
            string path = AssetDatabase.GetAssetPath(target);
            GUILayout.Label(path);
        }
 
        private void UpdateGUIOfMode(ref int mode, string[] list)
        {
            GUILayout.Label("# Mode", EditorStyles.boldLabel);
            mode = GUILayout.Toolbar(mode, list);
        }
 
        private void UpdateGUIOfMakeNewFile(ref DefaultAsset dirAsset, ref string className, ref string path)
        {
            GUILayout.Label("# Output", EditorStyles.boldLabel);
            GUILayout.Label("Directory", EditorStyles.boldLabel);
            GUILayout.Label("Please drag & drop directory.");
 
            path = string.Empty;
 
            dirAsset = EditorGUILayout.ObjectField(dirAsset, typeof(DefaultAsset), false) as DefaultAsset;
 
            if (dirAsset == null)
            {
                EditorGUILayout.HelpBox("Directory is empty.", MessageType.Error);
                return;
            }
 
            string dir = AssetDatabase.GetAssetPath(dirAsset);
 
            if (Path.HasExtension(dir) == true)
            {
                EditorGUILayout.HelpBox("Arrow directory only.", MessageType.Error);
                return;
            }
 
            GUILayout.Label("Class & File Name", EditorStyles.boldLabel);
            GUILayout.Label("Please input names.");
            className = EditorGUILayout.TextField(className);
 
            if (string.IsNullOrEmpty(className) == true)
            {
                EditorGUILayout.HelpBox("Class name is empty.", MessageType.Error);
                return;
            }
 
            string file = className + FileExtension;
            path = Path.Combine(dir, file);
        }
 
        private void UpdateGUIOfReplaceExistedFile(ref TextAsset fileAsset, ref string className, ref string path)
        {
            GUILayout.Label("# Output", EditorStyles.boldLabel);
            GUILayout.Label("Replace Target File", EditorStyles.boldLabel);
            GUILayout.Label("Please drag & drop existed file.");
 
            path = string.Empty;
 
            fileAsset = EditorGUILayout.ObjectField(fileAsset, typeof(TextAsset), false) as TextAsset;
 
            if (fileAsset == null)
            {
                EditorGUILayout.HelpBox("Target file is empty.", MessageType.Error);
                return;
            }
 
            string file = AssetDatabase.GetAssetPath(fileAsset);
            string extension = Path.GetExtension(file);
 
            if (extension.Equals(FileExtension) == false)
            {
                EditorGUILayout.HelpBox("Target is .cs file only.", MessageType.Error);
                return;
            }
 
            className = Path.GetFileNameWithoutExtension(file);
            path = file;
        }
 
        private void UpdateGUIOfOutputPath(string path, string className)
        {
            GUILayout.Label("Path", EditorStyles.boldLabel);
            GUILayout.Label(path);
 
            GUILayout.Label("Class Name", EditorStyles.boldLabel);
            GUILayout.Label(className);
        }
 
        private void UpdateGUIOfMakingButton()
        {
            if (string.IsNullOrEmpty(outputPath))
            {
                return;
            }
 
            if (string.IsNullOrEmpty(className))
            {
                return;
            }
            if (targetAsset == null)
            {
                return;
            }
 
            var buttonIsClicked = GUILayout.Button("MAKE");
            if (buttonIsClicked == true)
            {
                MakeConstants(targetAsset, className, outputPath);
            }
        }
    }
}

※ 拡張子は *.txt です。

Posted by kazupon