【Unity】よく使うディレクトリ構成を一発で生成するEditor拡張

本記事は2023年Unityアドベントカレンダー17日目の記事です。

# はじめに
最近業務で1～2週間程度の期間で一つのゲームを制作する。みたいなスタイルで開発を行う機会が多く、Unityプロジェクト立ち上げ時※やScene毎にディレクトリ階層を分ける際に毎回手動でディレクトリ構成を作るのが面倒だなぁ。と思うことがあったため、ディレクトリ構成を一発で追加するエディター拡張を作ってみました。

※頻繁にプロジェクト立ち上げを行うのであれば、この方法でディレクトリ構成を生成するのではなく、GitHubのテンプレートリポジトリ機能を使う方がおすすめです。なので`ディレクトリ構成が複数存在する`、または`新たにディレクトリ構成追加する`などのユースケースにおすすめです。

https://docs.github.com/ja/repositories/creating-and-managing-repositories/creating-a-template-repository

# この記事を参考にすると作れるもの
### ツールバーにスクリプトを実行する項目を追加
今回は`CreateNewFolders` という名前で項目を追加しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/e2317638-f9f4-5e6f-8610-323214f5be5b.png)

`CreateNewFolders` を選択すると作成するディレクトリ構成のルートディレクトリ名を入力し、ボタンを押すことディレクトリ構成をプロジェクトビューに追加することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/769c3021-553f-e32a-b436-a9a696b26a35.png)


### 生成されるディレクトリ構成
全項目の`CreateFolders` ボタンを押すことで以下のディレクトリ構成が自動で生成されます。
以下のキャプチャでは`GameTitle`,`InGame`,`OutGame` の3ディレクトリを生成しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/87e333dd-db8b-aa26-197c-7d8ff827df02.png)

また、Audioの下位ディレクトリにBGM,SEディレクトリがあり、SctiptsフォルダにDIコンテナ利用時の場合に定義が必要になるlinkファイルも生成されるようにしています。


```
root
├─Animations
├─Audio
│  │
│  ├─BGM
│  └─SE
├─Editor
├─Fonts
├─Materials
├─PhysicsMaterials
├─Plugins
├─Prefabs
├─Resources
├─Scenes
├─ScriptableObjects
├─Scripts
│      link.xml
│
├─Textures
└─Videos
```

※link.xmlファイルについての詳細はこちらを参照のこと

https://docs.unity3d.com/Manual/ManagedCodeStripping.html#LinkXMLAnnotation

# スクリプト

`Editor` ディレクトリに以下のスクリプトを入れると利用できるようになります。

```cs
#if UNITY_EDITOR // 宣言が漏れるとPlayモードやビルド時にエラーが出てしまうので注意すること
using UnityEngine;
using UnityEditor;
using System.IO;

public class CreateNewFolders : EditorWindow
{                                                                                                                                                               
    string rootFolderName = "NewFolder"; // デフォルトのフォルダ名

    /// <summary>
    /// 項目をクリックするとウィンドウを開く
    /// </summary>
    [MenuItem("Tools/CreateNewFolders")]
    static void Init()
    {
        // CreateNewFoldersで定義したウィンドウを開く
        CreateNewFolders window = (CreateNewFolders)GetWindow(typeof(CreateNewFolders));
        window.Show();
    }

    /// <summary>
    /// 画面描画時に毎回実行されるイベント
    /// </summary>
    void OnGUI()
    {
        // タイトルテキスト設定
        GUILayout.Label("新規ディレクトリ構成の生成", EditorStyles.boldLabel);
        
        // フォルダ名入力欄を描画する。この値をディレクトリ生成時のルートディレクトリ名になる。
        rootFolderName = EditorGUILayout.TextField("Folder Name", rootFolderName);
        
        // Create Foldersと表示されたボタンを描画し、クリックされたらCreateAppFoldersを実行する
        if (GUILayout.Button("Create Folders"))
        {
            CreateAppFolders(rootFolderName);
        }
    }

    void CreateAppFolders(string rootFolderName)
    {
        // ディレクトリを生成するパスを設定する
        string basePath = Path.Combine("Assets", rootFolderName);

        // 生成するディレクトリのリスト
        string[] folders = new string[]
        {
            "Scripts",
            "ScriptableObjects",
            "Scenes",
            "Prefabs",
            "Editor",
            "Textures",
            "Animations",
            "Materials",
            "PhysicsMaterials",
            "Fonts",
            "Videos",
            "Audio/BGM",
            "Audio/SE",
            "Resources",
            "Editor",
            "Plugins"
        };

        // 上記リストのフォルダを一つずつ作成
        foreach (string folder in folders)
        {
            string path = Path.Combine(basePath, folder);
            if (!Directory.Exists(path))
            {
                // ディレクトリがまだ存在しない場合は作成する
                Directory.CreateDirectory(path);
                Debug.Log("Created Directory: " + path);
            }
        }

        // link.xmlファイルを作成
        string linkXmlPath = Path.Combine(basePath, "Scripts/link.xml");
        if (!File.Exists(linkXmlPath))
        {
            // link.xmlファイルがまだ存在しない場合は作成する
            File.WriteAllText(linkXmlPath, "<link></link>"); // 最低限のタグだけ記述しておく
            Debug.Log("Created File: " + linkXmlPath);
        }

        // Unity側にディレクトリが追加されたことが検知されないかもしれないので、念のため手動で更新する。
        AssetDatabase.Refresh();
    }
}
#endif
```

# 最後に
開発するグループ内でディレクトリ構成の規約があっても、ちょっとした認識の違いでディレクトリ名が変わったり配置方法が変わったりするので、このようなEditor拡張用のスクリプトを追加することとで確実に規約通りに配置できるので便利ですね。

よろしければ今回のスクリプトを雛形にして色々試してみてください！！