【Unity Editor拡張 with Odin】 Addressable AssetReferenceのプレビュー表示とグループでのフィルタリング

## 環境
[Unity 2019.2.17f1](https://unity.com/ja)
[Addressable 1.5](https://docs.unity3d.com/Packages/com.unity.addressables@1.5/manual/index.html)
[Odin Inspector 2.1.8 (有料プラグイン)](https://assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041)
[<img width="466" alt="odin.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/7cb10400-40e5-c1c7-24ea-361b377b307f.png">](https://assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041)

## AssetReferenceって何を参照しているのか分かりにくい。。。
名前を設定している場合はまだ分かりやすいですが、フォルダを指定している場合フルパスが表示されるのでかなり分かり辛いです。それを有料プラグインのOdinを使って解決してみようと思います。

## まずは Before / After
Before...
![Addressable.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/bece2df9-a95c-80f7-da14-c1c2878d767f.gif)
After !!!!
![AddressableWithOdin.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/104bf470-b43d-a3a2-d6e4-15e63b247a8f.gif)
だいぶ分かりやすくなりましたね。
Prefab名をクリックするとProject内のPrefabをハイライトしてくれます。
そしてグループでのフィルタリングにも対応しました。

## Code
```C#:OdinWithAddressableAssetReference.cs
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using Sirenix.OdinInspector;
using Object = UnityEngine.Object;

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
#endif

[CreateAssetMenu(menuName = "Sample/OdinWithAddressableAssetReference")]
public sealed class OdinWithAddressableAssetReference : ScriptableObject
{
    [SerializeField, HideInInspector] AssetReferenceGameObject references;

    public AssetReferenceGameObject References => references;

    #region Inspector
#if UNITY_EDITOR
#pragma warning disable 0649
    [SerializeField, ValueDropdown("ReferenceDropDownEditorOnly"), OnValueChanged("ReferenceValueChangedEditorOnly"), LabelText("References")] string referenceGuidEditorOnly;
    [ShowInInspector, ReadOnly, InlineEditor(InlineEditorModes.LargePreview), ShowIf("ReferencePreviewShowIfEditorOnly")] GameObject PreviewOnEditor;
    [SerializeField, ValueDropdown("AssetGroupDropDownEditorOnly", IsUniqueList = true), LabelText("Addressable Group Filter")] string[] targetAssetGroupGuidsEditorOnly;
#pragma warning restore 0649

    IEnumerable ReferenceDropDownEditorOnly()
    {
        return GetAllAssetEntrys(targetAssetGroupGuidsEditorOnly)
                .Select(x => new ValueDropdownItem(x.AssetPath, x.guid));
    }

    IEnumerable AssetGroupDropDownEditorOnly()
    {
        return GetAllAssetGroups()
                .Select(x => new ValueDropdownItem(x.Name, x.Guid));
    }

    void ReferenceValueChangedEditorOnly(string guid)
    {
        var entrys = GetAllAssetEntrys(targetAssetGroupGuidsEditorOnly);
        var item = entrys.FirstOrDefault(x => x.guid == guid);

        if (item != default)
        {
            references = new AssetReferenceGameObject(item.guid);
            UpdatePreviewAssetEditorOnly();
        }
    }

    bool ReferencePreviewShowIfEditorOnly()
    {
        if(PreviewOnEditor == default)
        {
            UpdatePreviewAssetEditorOnly();
        }

        return PreviewOnEditor != default;
    }

    void UpdatePreviewAssetEditorOnly()
    {
        PreviewOnEditor = default;

        if(references.RuntimeKeyIsValid())
        {
            PreviewOnEditor = LoadAsset<GameObject>(references);
        }
    }

    /// <summary>
    /// アセットの取得
    /// </summary>
    /// <returns>The asset.</returns>
    /// <param name="reference">Reference.</param>
    /// <typeparam name="T">The 1st type parameter.</typeparam>
    public static T LoadAsset<T>(AssetReference reference) where T : Object
    {
        return reference.RuntimeKeyIsValid()
                      ? AssetDatabase.LoadAssetAtPath<T>(AssetDatabase.GUIDToAssetPath(reference.AssetGUID))
                      : default;
    }

    /// <summary>
    /// グループ一覧を取得
    /// </summary>
    /// <returns>The asset groups.</returns>
    public static List<AddressableAssetGroup> GetAllAssetGroups()
    {
        var setting = AddressableAssetSettingsDefaultObject.GetSettings(false);
        var gropus = new List<AddressableAssetGroup>();

        if (setting != default)
        {
            gropus = setting.groups;
        }

        return gropus;
    }

    /// <summary>
    /// 全エントリーを取得
    /// </summary>
    /// <returns>The asset entrys editor only.</returns>
    /// <param name="groupFilterGuids">Group filter.</param>
    public static List<AddressableAssetEntry> GetAllAssetEntrys(string[] groupFilterGuids = null)
    {
        var setting = AddressableAssetSettingsDefaultObject.GetSettings(false);
        var entrys = new List<AddressableAssetEntry>();

        if (setting != default)
        {
            foreach (var group in setting.groups)
            {
                if (groupFilterGuids != null
                    && groupFilterGuids.Length > 0
                    && !groupFilterGuids.Any(guid => guid == group.Guid))
                {
                    continue;
                }

                var _entrys = new List<AddressableAssetEntry>();
                group.GatherAllAssets(_entrys, false, true, false);
                entrys.AddRange(_entrys);
            }
        }

        return entrys;
    }
#endif
    #endregion

}
```

## 実装のポイント
#### AddressableAssetSettingsの取得方法が変わりました
```C#:OdinWithAddressableAssetReference.cs
var setting = AddressableAssetSettingsDefaultObject.GetSettings(false);
```

#### プレビュー用GameObjectをシリアライズされないようにする
プレビューを表示するためのGameObjectがシリアライズされないようにprivateでShowInInspectorアトリビュートを使用しています。
publicにしたい場合は、[System.NonSerialized]を追加。

更に、プレビューは参照だけで使用されることが無いので#pragma warningでCS0649警告が表示されないようにしています。

```C#:OdinWithAddressableAssetReference.cs
[ShowInInspector, (略)] GameObject PreviewOnEditor;
```
#### OdinでAddressableは非対応なのでGuidを保存する
<img width="444" alt="Reference.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/b3c5a6ce-fc42-7f83-7dcc-d51176eae3e7.png">
ドロップダウンは実はテキストです(string referenceGuidEditorOnly)。
表示するリストを生成する際(ReferenceDropDownEditorOnly())、表示用のアセットパスと保存用のGuidをOdinのValueDropdownItemに代入しています。
そして、Guidに変更があった場合(ReferenceValueChangedEditorOnly(string guid))でプレビューを更新する仕様です。

```C#:OdinWithAddressableAssetReference.cs
[SerializeField, ValueDropdown("ReferenceDropDownEditorOnly"), OnValueChanged("ReferenceValueChangedEditorOnly"), LabelText("References")] string referenceGuidEditorOnly;

IEnumerable ReferenceDropDownEditorOnly()
{
    return GetAllAssetEntrys(targetAssetGroupGuidsEditorOnly)
            .Select(x => new ValueDropdownItem(x.AssetPath, x.guid));
}

void ReferenceValueChangedEditorOnly(string guid)
{
    var entrys = GetAllAssetEntrys(targetAssetGroupGuidsEditorOnly);
    var item = entrys.FirstOrDefault(x => x.guid == guid);

    if (item != default)
    {
        references = new AssetReferenceGameObject(item.guid);
        UpdatePreviewAssetEditorOnly();
    }
}

/// <summary>
/// 全エントリーを取得
/// </summary>
/// <returns>The asset entrys editor only.</returns>
/// <param name="groupFilterGuids">Group filter.</param>
public static List<AddressableAssetEntry> GetAllAssetEntrys(string[] groupFilterGuids = null)
{
    var setting = AddressableAssetSettingsDefaultObject.GetSettings(false);
    var entrys = new List<AddressableAssetEntry>();

    if (setting != default)
    {
        foreach (var group in setting.groups)
        {
            if (groupFilterGuids != null
                && groupFilterGuids.Length > 0
                && !groupFilterGuids.Any(guid => guid == group.Guid))
            {
                continue;
            }

            var _entrys = new List<AddressableAssetEntry>();
            group.GatherAllAssets(_entrys, false, true, false);
            entrys.AddRange(_entrys);
        }
    }

    return entrys;
}
```

#### グループフィルターに対応
<img width="445" alt="Group.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389614/6da394f9-c4c4-9c62-4294-717c2afdcd6a.png">
フィルタ機能もつけてみました。
ほんとはLabel用のフィルタ機能もつけたかったのですが、 LabelTableがprivateの為一覧取得ができず断念しました。