Unityで音ゲー制作するためにUnityEditor拡張したよ

# 前置き
ゲームとかイラストとかBotとか書いてるHayaoです。

今、音ゲー制作をやってます。
ようやくα版がリリースしました。この制作の上で自作のUnityEditorを作ったのでそれを紹介します。

# 作ったもの
https://github.com/Hayao3001/TapMEditor.git

音ゲーと制作する上で必要になってくる譜面を製作するためのUnityEditorです。

# そもそも
音ゲー作る上で一番きつい部分はどこでしょうか？
私自身が一番きつい部分は**譜面制作**です。~~(個人差あり)~~

で、私はCSVファイルに書き込み、それをUnity上で読み込むというふうにシステムを作り出しました。
ただ・・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626737/d9b8925a-eb39-0987-bd52-eedbdcd15c78.png)

これを手書きするのは面倒くさい・・・・
ということで自作でUnityEditorを作りました。うおーーー－

# 完成物
実際に作ったものは以下のものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626737/d0e31d6c-f0d0-9c8e-a2eb-3497bc70abdc.png)

# 機能
これはaddblockという自作Editorで何小節目のどこかを指定してそれをCSVファイルに書き、画面上に反映させるものです。

# 作り方
実際にどのように作っていったか。部分的ではありますがコードを載せたいと思います。

まず縦線を表示する部分です。
```C#:Editor.cs
for (int i = 0; i <= lineCount; i++)
{
    var spos = new Vector3(xpos, 20.0f);
    var vector = new Vector3(xpos, 2.5f, 0f);
    var beat_4_top = new Vector3(before_xpos_per, 20.0f, 0f);
    var beat_4_bottom = new Vector3(before_xpos_per, 1000.0f, 0f);
    var beat_8_top = new Vector3(before_xpos_per, 20.0f, 0f);
    var beat_8_bottom = new Vector3(before_xpos_per, 1000.0f, 0f);
    var beat_12_top = new Vector3(before_xpos_per, 20.0f, 0f);
    var beat_12_bottom = new Vector3(before_xpos_per, 1000.0f, 0f);
    var beat_16_top = new Vector3(before_xpos_per, 20.0f, 0f);
    var beat_16_bottom = new Vector3(before_xpos_per, 1000.0f, 0f);
    int beat_count_4 = 0;
    int beat_count_8 = 0;
    int beat_count_12 = 0;
    // baseLinePosは起点のPosition
    if (i % 5 == 0)
    {
        vector.y = 0f;
    }
    EditorGUILayout.LabelField(i.ToString(), GUILayout.Width(perspace-3f));
    Handles.DrawLine(spos, vector);
    if(xpos != 0.0f){
        for(int beat = 0; beat < 16; beat++){
            beat_16_top.x = (space * scale) / 16 + beat_16_top.x;
            beat_16_bottom.x = beat_16_top.x;
            var beatColor = Handles.color;
            Handles.color = new Color(35f,35f,35f,0.1f);
            Handles.DrawLine(beat_16_top, beat_16_bottom);
            Handles.color = beatColor;
            if(beat_count_4 < 4){
                beat_4_top.x = (space * scale) / 4 + beat_4_top.x;
                beat_4_bottom.x = beat_4_top.x;
                beatColor = Handles.color;
                Handles.color = new Color(35f,35f,35f,0.4f);
                Handles.DrawLine(beat_4_top, beat_4_bottom);
                Handles.color = beatColor;
                beat_count_4++;
            }
            if(beat_count_8 < 8){
                beat_8_top.x = (space * scale) / 8 + beat_8_top.x;
                beat_8_bottom.x = beat_8_top.x;
                beatColor = Handles.color;
                Handles.color = new Color(35f,35f,35f,0.3f);
                Handles.DrawLine(beat_8_top, beat_8_bottom);
                Handles.color = beatColor;
                beat_count_8++;
            }
            if(beat_count_12 < 12){
                beat_12_top.x = (space * scale) / 12 + beat_12_top.x;
                beat_12_bottom.x = beat_12_top.x;
                beatColor = Handles.color;
                Handles.color = new Color(35f,35f,35f,0.05f);
                Handles.DrawLine(beat_12_top, beat_12_bottom);
                Handles.color = beatColor;
                beat_count_12++;
            }
            before_xpos_per = xpos;
        }
    }
    xpos = xpos + space * scale;
}
Handles.DrawLine(startPos, endPos);
Handles.color = prev;
EditorGUILayout.EndHorizontal();
```

このEditorでの1目盛りは1小節のため4分、8分、12分、16分で縦線を引いています。

次に降ってくるブロックを設定する部分です。
```C#:Editor.cs
if(!EditMusicScore.GetisPlay() && !canSet){
    startime = EditMusicScore.GetMusicTime();
    endtime = EditMusicScore.GetMusicTime();
    canSet = !canSet;
}
if(GUILayout.Button("Add File")){
    filepath = EditorUtility.OpenFilePanel("Open csv", "", "CSV");
    if(filepath == null){
        return;
    }else{
        filename = Path.GetFileNameWithoutExtension(filepath);
    }
}
GUILayout.Space(15f);
GUILayout.Label("Time or Measure");
_StartWirteIndex = GUILayout.SelectionGrid(_StartWirteIndex, WriteTipe, 3);
if(_StartWirteIndex == 1){
    GUILayout.Space(15f);
    GUILayout.Label("Measure Type");
    _MeasureIndex =  GUILayout.SelectionGrid(_MeasureIndex, MeasureType, 3);
    GUILayout.Space(15f);
    GUILayout.Label("拍子数");
    measure_num = EditorGUILayout.DelayedFloatField(measure_num);
    GUILayout.Label("拍数");
    beat_num = EditorGUILayout.DelayedFloatField(beat_num);
}else{
    GUILayout.Space(15f);
    GUILayout.Label("Now Time");
    startime = EditorGUILayout.DelayedFloatField(startime);
}
GUILayout.Space(15f);
GUILayout.Label("Select line");
_LineIndex = GUILayout.SelectionGrid(_LineIndex, Line, 3);
GUILayout.Label("Select Block Type");
_TypeIndex = GUILayout.SelectionGrid(_TypeIndex, BlockType, 3);
if(_TypeIndex == 1){
    GUILayout.Space(15f);
    GUILayout.Label("Time or Measure");
    _EndWriteIndex = GUILayout.SelectionGrid(_EndWriteIndex, WriteTipe, 3);
    if(_EndWriteIndex == 1){
        GUILayout.Space(15f);
        GUILayout.Label("Measure Type");
        _EndMeasureIndex =  GUILayout.SelectionGrid(_EndMeasureIndex, MeasureType, 3);
        GUILayout.Space(15f);
        GUILayout.Label("拍子数");
        end_measure_num = EditorGUILayout.DelayedFloatField(end_measure_num);
        GUILayout.Label("拍数");
        end_beat_num = EditorGUILayout.DelayedFloatField(end_beat_num);
    }else{
        GUILayout.Label("Input Last time");
        endtime = EditorGUILayout.DelayedFloatField(endtime);
    }
}
GUILayout.Space(35f);

GUILayout.Label("notes speed");
notes_speed = EditorGUILayout.DelayedFloatField(notes_speed);

GUILayout.Space(35f);

if(GUILayout.Button("Add")){
    if(_StartWirteIndex == 1){
        startime = ((float)240/(float)EditMusicScore.GetBpm()/(float)((float)4*(float)(_MeasureIndex+1)))*((float)((4*(_MeasureIndex+1))*measure_num + beat_num));
    }
    if(_EndWriteIndex == 1){
        endtime = ((float)240/(float)EditMusicScore.GetBpm()/(float)((float)4*(float)(_EndMeasureIndex+1)))*((float)((4*(_EndMeasureIndex+1))*end_measure_num + end_beat_num));
    }
    if(_TypeIndex == 1){
        CSV.AddCSV(filepath, Line[_LineIndex], BlockType[_TypeIndex], startime, endtime, notes_speed);
    }else{
        CSV.AddCSV(filepath, Line[_LineIndex], BlockType[_TypeIndex], startime, 0.0f, notes_speed);
    }
    CSV.GetCSV(filename, CSVData);
    EditMusicScore.UpdateCSV();
}
```

これとCSVファイルに書き込む部分と読み込む部分は以下の通りになります

```C#:Editor.cs
public class CSV : MonoBehaviour
{
    private static int rownum = 0;

    public static void AddCSV(string filename, string linename, string tyepname, float startime, float endtime, float notes_speed){
        var list = "";
        Debug.Log(filename);
        list = linename + "," + tyepname + "," + startime.ToString() + "," + endtime.ToString() + "," + notes_speed.ToString() + "\n";
        if(filename == null){
            return;
        }
        using (var file = new StreamWriter(filename, true, System.Text.Encoding.GetEncoding("UTF-8"))){
            file.Write(list);
        }
    }

    public static void GetCSV(string filename, List<string[]> csvDatas){
        var csvfile = Resources.Load(filename) as TextAsset;
        StringReader file = new StringReader(csvfile.text);
        rownum = 0;
        while(file.Peek() != -1){
            string line = file.ReadLine();
            csvDatas.Add(line.Split(','));
            rownum = rownum + 1;
        }
    }

    public static bool HasData(string filename){
        int count = 0;
        var csvfile = Resources.Load(filename) as TextAsset;
        StringReader file = new StringReader(csvfile.text);
        while(file.Peek() != -1){
            string line = file.ReadLine();
            count++;
        }
        if(count == 0){
            return false;
        }else{
            Debug.Log(count);
            return true;
        }
    }

    public static int GetRowNum(){
        return rownum;
    }
}
```
こんな感じでゴニョゴニョすれば譜面を作るためのUnityEditorが完成します。

# 終わりに
実際にこのEditorを使用して、一譜面完成させましたが、しんどかったです。まじで・・・・
あと、改修しない部分も多く、削除部分だったり、既存の部分の編集だったり・・・これらの問題自体はUnityEditorはEditorWindowを継承しているのでMonoBehaviorよりとっつきにくく、難しいのです・・・・。今後も頑張っていこうと思います。

明日は鈴木乖離さんの「醜く生きるお絵描き道～大学生から目指すつよつよ絵描き～」です。よろしくお願いします。

# 参考にさせていただいた資料
https://qiita.com/albatrus/items/2374da40919d78f0b81b