Addressableのアドレス一覧からモデルクラスを生成するEditor拡張

## はじめに
この記事は[Unity Advent Calendar 2022](https://qiita.com/advent-calendar/2022/unity)の14日目の記事です。
今年も変わらず大きな盛り上がりを見せている`AdventCalendar`、是非他の記事も覗いてみてください！!

https://qiita.com/advent-calendar/2022/unity

## 概要
Addressableを導入したプロジェクトでコーディングをしていると、以下のような不満がありました。

- アセットのアドレスを確認するのめんどくさい
- EditorとUnityを切り替えるのがめんどくさい
- アドレスのtypoによってランタイムエラーを起こしてしまう

そこで、AddressableのアドレスがまとまっているモデルクラスがあったらEditorのコード補完でアドレスを確認できると思い、実際に実装してみました。


本記事で利用したコードは[GitHubGist](https://gist.github.com/KouheiShoji/bc97ee795729b06018889ecbe176f3ac)にまとまっています。

https://gist.github.com/KouheiShoji/bc97ee795729b06018889ecbe176f3ac


## Addressableとは？
Unityのアセットを管理するための仕組み
アセットに一意の名前（アドレス）を付与してマップすることで、アセットを簡単に取得することができる

https://docs.unity3d.com/ja/2021.3/Manual/com.unity.addressables.html

## 実装方針
どんな形でモデルクラスが生成されるといいかを考えた時に、以下のような理想を持っていました。
- Editorの入力補完によって、現在のプロジェクトでAddressable管理されているアセットをGroupごとに探すことができる
- そのアドレスを取得することができる

![スクリーンショット 2022-12-13 23.57.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/968526/666cee52-5182-7ee8-8952-1535d035fa3f.png)


その為生成されるモデルクラスは以下のような形式になります。
```c#
namespace hoge
{
    /// <summary>
    /// サマリーコメント
    /// </summary>
    public static class piyo
    {
        public static class AddressableGroupName
        {
            public static readonly string AddressableLabel = "AddressableLabel";
        }
    }
}
```

## モデルクラスを生成するためのモデルを定義

上記のモデルクラスを生成する為のモデルを以下のように定義しました。
```c#
public class AddressableAddress
{
    public string Group { get; private set; }
    public Dictionary<string, string> AddressDictionary { get; private set; }

    public AddressableAddress(string group, Dictionary<string, string> addressDictionary)
    {
        Group = group;
        AddressDictionary = addressDictionary;
    }
}
```

## C#のコードからクラスファイルを生成する
記事量が多くなってしまう為外部の参考記事を貼らせていただきますmm

https://kan-kikuchi.hatenablog.com/entry/ConstantsClassCreator


[@Kan_Kikuchi](https://kan-kikuchi.hatenablog.com/) さんのブログを参考に以下のような実装をしました。

https://gist.github.com/KouheiShoji/81cab0e682247090e138458ff4391c69




## Addressable管理されているアドレス一覧を取得

プロジェクトでAddressable管理されているアドレス一覧をコードから取得して、モデルクラスを生成する為に以下のような実装を行いました。
```c#
static void AddressBookGenerate()
{
    var list = new List<AddressableAddress>();
    var addressableSetting = AddressableAssetSettingsDefaultObject.Settings;
    var assets = new List<AddressableAssetEntry>();
    var GroupList = new List<string>();

    addressableSetting.GetAllAssets(assets, true);
    foreach (var a in assets)
    {
        // NOTE: BuiltInDataやデフォルトグループは除外している
        if (a.parentGroup.Name.Contains("Built In Data")) continue;
        if (a.parentGroup.Name.Contains("Default Local Group")) continue;

        if (!GroupList.Contains(a.parentGroup.Name))
        {
            GroupList.Add(a.parentGroup.Name);
            list.Add(new AddressableAddress(
               a.parentGroup.Name,
               new Dictionary<string, string>()
            ));
        }

        list.Find(x => x.Group == a.parentGroup.Name).AddressDictionary.Add(a.address, a.address);
    }

    ModelClassGenerator.Create(
        className: "AddressBook",
        classInfo: "Addressableのアドレスリスト",
        namespaceName: "Hoge.ModelClass",
        saveDirectory: "Assets/Script/Model",
        valueList: list
    );
}
```


#### 実装解説
`var addressableSetting = AddressableAssetSettingsDefaultObject.Settings;`
Addressableの設定を拾ってくる(Addressableに関するEditor拡張を書くとき大体こいつをどうにかする)

`var assets = new List<AddressableAssetEntry>();`
`AddressableAssetEntry`型は

`addressableSetting.GetAllAssets(assets, true);`
第一引数に指定したListに対して、すべてのアセットエントリを取得

※[ドキュメント](https://docs.unity3d.com/Packages/com.unity.addressables@1.9/api/UnityEditor.AddressableAssets.Settings.AddressableAssetSettings.html?q=GetAllAssets)を見ると特定のGroupやエントリでフィルタすることもできそう

`list.Add(new AddressableAddress(a.parentGroup.Name,new Dictionary<string, string>()));`
あとは、listにGroup名とAddressを突っ込んで上にあったModelClassを生成するクラスに突っ込むだけ


## 実際に生成したクラス

Unity公式サンプルの[LoadyDungeons](https://unity.com/ja/demos/loady-dungeons)を使ってモデルクラスを生成してみました。
```C#
namespace Model.Addressables
{
    /// <summary>
    /// Addressableのアドレスリスト
    /// </summary>
    public static class AddressBook
    {
        public static class HatsGroup
        {
            public static readonly string Hat00 = "Hat00";
            public static readonly string Hat01 = "Hat01";
            public static readonly string Hat02 = "Hat02";
            public static readonly string Hat03 = "Hat03";
        }
        public static class Level_00Group
        {
            public static readonly string Level_00 = "Level_00";
        }
        public static class Level_01Group
        {
            public static readonly string Level_01 = "Level_01";
        }
        public static class Level_02Group
        {
            public static readonly string Level_02 = "Level_02";
        }
        public static class Level_03Group
        {
            public static readonly string Level_03 = "Level_03";
        }
    }
}
```


![スクリーンショット 2022-12-14 1.49.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/968526/df774ed1-372d-13ef-ac27-7937ddb4316c.png)



## まとめ
ちゃんとAddressableをプロジェクトに導入して運用するには、アドレスを自動付与するEditor拡張が必須になるかと思います。
また、そのEditor拡張はCI環境で実行されることも多いと思うのでついでにモデルクラスも生成しておくと、実装時にちょっとだけ幸せになれます。

※バグ等ありましたらコメントにてご教授ください🙏🙏🙏


## ドキュメント

https://docs.unity3d.com/Packages/com.unity.addressables@1.9/manual/index.html

https://docs.unity3d.com/Packages/com.unity.addressables@0.4/api/UnityEditor.AddressableAssets.AddressableAssetEntry.html

https://docs.unity3d.com/Packages/com.unity.addressables@1.9/api/UnityEditor.AddressableAssets.Settings.AddressableAssetSettings.html?q=GetAllAssets