パハットノート

※当サイトではGoogleアドセンス広告を利用しています

Unity CSVファイルからエディタ上でEnumの再構築




概要

CSVファイルからスクリプト(csファイル)に記載されているenum(列挙型)を再構成する方法を考えました。
今回は「EditorWindow」から編集できるようにしました。
CSVファイルの読み込みについては下の記事を参考にしてください。
kiironomidori.hatenablog.com



必要なクラス

EnumRebuilder

  • 今回の核となるクラスその1
  • 次に紹介するクラスによってEditorWindowに表示します。
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Text;

[Serializable]
public class EnumRebuilder
{
    [SerializeField] public bool key = false;
    [SerializeField] public string label;
    [SerializeField] public TextAsset csvFile;
    [SerializeField] public TextAsset scriptFile;
    [SerializeField] public int enumNum;
    [SerializeField] public int nameColumn = 0;
    [SerializeField] public int startCsvLine = 1;

    //コンストラクタ
    public EnumRebuilder(int i)
    {
        label = $"Element{i}";
    }

    //enumを更新する
    public void RebuildEnum(string enumName)
    {
        //ファイルを読み込む
        List<string> scriptLines = FileReader.ReadTextFile(scriptFile);

        //リストから目的のenumの位置を取得
        int enumPos = scriptLines.ContainFirstIndex(enumName);

        if (enumPos == -1)
        {
            Debug.LogError($"{enumName}が見つかりません");
            return;
        }

        //続いて「{」と「}」の位置を取得
        int startWriteLine = scriptLines.ContainFirstIndex("{", enumPos);//「{」の位置
        int endWriteLine = scriptLines.ContainFirstIndex("}", enumPos);//「}」の位置

        //enumの中身を空にする
        if (endWriteLine - (startWriteLine + 1) > 0)
        {
            scriptLines.RemoveRange(startWriteLine + 1, endWriteLine - (startWriteLine + 1));
        }

        //CSVファイルを読み取る
        List<string[]> csvLines = FileReader.ReadCSVFile(csvFile);

        for (int j = 0; j < csvLines.Count - startCsvLine; j++)
        {
            scriptLines.Insert(startWriteLine + 1 + j, $"\t{csvLines[j + startCsvLine][nameColumn]} = {j},");
        }

        //ファイルを書き込む
        File.WriteAllLines(AssetDatabase.GetAssetPath(scriptFile), scriptLines, Encoding.UTF8);

        //スクリプトのリフレッシュ
        EditorApplication.ExecuteMenuItem("Assets/Refresh");
    }
}




RebuildEnumWindow

  • 今回の核となるクラスその2
  • EditorWindowを作成するクラスです。
  • 複数のenumを編集できるようにするために「EnumRebuilder」をリストで表示します。
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;

public class RebuildEnumWindow : EditorWindow
{
    [SerializeField] int arraySize;
    [SerializeField] private List<EnumRebuilder> list = new List<EnumRebuilder>();

    [MenuItem("エディタ拡張/RebuildEnumWindow")]
    public static void Open()
    {
        var window = GetWindow<RebuildEnumWindow>("RebuildEnumWindow");
        window.Show();
    }

    void ResetArraySize()//配列の大きさを変更
    {
        int preArraySize = list.Count;
        if (preArraySize != arraySize)//現在の配列の大きさ≠入力した配列の大きさの時
        {
            if (arraySize > preArraySize)//配列を大きくする
            {
                for (int i = preArraySize; i < arraySize; i++)
                {
                    list.Add(new EnumRebuilder(i));
                }
            }
            else//小さくする
            {
                for (int i = arraySize; i < preArraySize; i++)
                {
                    list.RemoveAt(arraySize);
                }
            }
        }
    }

    void DrawArrayElement(int i)//配列の要素を描画
    {
        //畳み込み表示をするかどうか
        list[i].key = EditorGUILayout.Foldout(list[i].key, list[i].label);

        if (list[i].key)
        {
            list[i].label = EditorGUILayout.TextField("ラベル", list[i].label);
            list[i].csvFile = (TextAsset)EditorGUILayout.ObjectField
                ("CSVファイル", list[i].csvFile, typeof(TextAsset), false);
            list[i].scriptFile = (TextAsset)EditorGUILayout.ObjectField
                ("スクリプトファイル", list[i].scriptFile, typeof(TextAsset), false);

            //スクリプトファイルが存在するなら描画
            if (list[i].scriptFile != null)
            {
                string[] enums = FileReader.ReadTextFile(list[i].scriptFile).Where(s => s.Contains(" enum ")).ToArray();

                if (enums?.Length > 0)
                {
                    //scriptFIle内のすべてのenumを取得
                    list[i].enumNum = EditorGUILayout.Popup("設定するenumの名称", list[i].enumNum, enums);

                    //読み取りについて
                    list[i].startCsvLine = EditorGUILayout.IntField("読み取り開始行番号", list[i].startCsvLine);
                    list[i].nameColumn = EditorGUILayout.IntField("enumに追加する要素列番号", list[i].nameColumn);

                    //元のInspector部分の下にボタンを表示

                    if (GUILayout.Button("Enumの再設計"))
                    {
                        list[i].RebuildEnum(enums[list[i].enumNum]);
                    }
                }
            }
        }
    }

    private void OnGUI()
    {
        var serialObj = new SerializedObject(this);
        serialObj.Update();

        //配列のサイズの設定
        arraySize = EditorGUILayout.IntField("サイズ", arraySize);
        if (GUILayout.Button("サイズの更新"))
        {
            ResetArraySize();
            serialObj.ApplyModifiedProperties();
            return;
        }
        
        //配列の要素を描画する
        for (int i = 0; i < list.Count; i++)
        {
            DrawArrayElement(i);
        }

        serialObj.ApplyModifiedProperties();
    }
}



FileReader & ListStringExtensions

  • 先ほど紹介した2クラスの補助的なクラス
  • CSVファイルの読み込みなどに必要な機能をまとめたもの
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Linq;
using UnityEditor;

public static class ListStringExtensions//List<string>の拡張メソッド
{
    //List<string>から指定した文字列を含む先頭の要素番号を取得
    public static int ContainFirstIndex(this List<string> list, string keyword, int startIndex = 0)
    {
        for (int i = startIndex; i < list.Count; i++)
        {
            if (list[i].Contains(keyword))
            {
                return i;
            }
        }
        return -1;
    }
}


public static class FileReader
{
    //CSVファイルを読み取る
    public static List<string[]> ReadCSVFile(TextAsset csvFile)
    {
        //1行ごとに配列としてCSV読み取り、それをリストに変換する
        List<string> list = File.ReadAllLines(AssetDatabase.GetAssetPath(csvFile)).ToList();

        //1行ごとの文字列を更に、カンマで区切る
        List<string[]> lists = new List<string[]>();
        for (int i = 0; i < list.Count; i++)
        {
            lists.Add(list[i].Split(','));
        }

        return lists;
    }

    //テキストファイルを読み取る
    public static List<string> ReadTextFile(TextAsset textAsset)
    {
        return File.ReadAllLines(AssetDatabase.GetAssetPath(textAsset)).ToList();
    }
}




使用例

①. Excelで次のファイルを用意して、ファイル名「dic.csv」としてCSV形式で保存する(形式はCSF UTF-8(コンマ区切り))

行名\列名 A B
0 日本語名 英語
1 りんご apple
2 みかん orange
3 dog
4 red

ちなみに、このCSVファイルをメモ帳で開くと以下のようになっています。

日本語名,英語
りんご,apple
みかん,orange
犬,dog
赤,red


②. 次の「Sample.cs」を作成する。

public enum Jan
{
}

public enum Eng
{
}


③. エディタ上で「エディタ拡張」⇒「RebuidEnumWindow」の順に選択してEditorWindowを表示する。
f:id:KiironoMidori:20220307214656g:plain

④. 先ほど作成したウィンドウに対して「サイズ」に2を入力して、「サイズの更新」ボタンを押す。
f:id:KiironoMidori:20220307214700g:plain

⑤. 更に、次のような設定(ラベルは見やすくするためのものなので設定は自由で可)をして、それぞれの「Enumの再設計」ボタンを押す。
f:id:KiironoMidori:20220307214705g:plain

⑥. 「Sample.cs」を開くと以下のように書き換えられていることが確認できる。

public enum Jan
{
	りんご = 0,
	みかん = 1,
	犬 = 2,
	赤 = 3,
}

public enum Eng
{
	apple = 0,
	orange = 1,
	dog = 2,
	red = 3,
}