インスペクタに画像を表示する [Unityエディター拡張, Odin対応]

こんな感じに、インスペクタのフィールド上に画像を表示する属性を作ります。
![スクリーンショット 2022-12-07 232656.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2987563/a5be44fd-53ec-1b80-7c9e-63075e60c134.png)

# 実装
画像を表示する属性`ImageHeaderAttribute`とそのドロワーの実装を、通常のUnityのものと、アセットOdinに対応したものの2種類を紹介します。
Odin版の方のみ、画像を自動的に中央揃えで表示します。

## 閑話
2022/12/07 23:37現在、Odinがセール中だそうです。
インスペクタがかなり使いやすくなる便利なアセットなので、購入して損はないと思います。

https://assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041

(特にアフィリエイトリンクなどは付けていません)

## 通常
```CS:ImageHeaderAttribute.cs
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class ImageHeaderAttribute : PropertyAttribute
{
    public string AssetPath { get; }
    public int Height { get; }

    public ImageHeaderAttribute(string assetPath, int height)
    {
        AssetPath = assetPath;
        Height = height;
    }

#if UNITY_EDITOR
    [CustomPropertyDrawer(typeof(ImageHeaderAttribute))]
    public class ImageHeaderAttributeDrawer : PropertyDrawer
    {
        private bool showImage;
        private float imageHeight;

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            var targetAttribute = (ImageHeaderAttribute)attribute;

            // パスが指定されていない場合や高さが正値でない場合は表示しない
            if (string.IsNullOrEmpty(targetAttribute.AssetPath) || targetAttribute.Height <= 0)
            {
                showImage = false;
                EditorGUI.PropertyField(position, property, label, true);
                return;
            }

            var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(targetAttribute.AssetPath);
            // テクスチャを取得できない場合は表示しない
            if (texture == null)
            {
                showImage = false;
                EditorGUI.PropertyField(position, property, label, true);
                return;
            }

            showImage = true;
            imageHeight = targetAttribute.Height;
            var imagePos = position;
            imagePos.height = imageHeight;
            imagePos.width = texture.width * (targetAttribute.Height / (float)texture.height);
            GUI.Label(imagePos, texture);
            position.y += imagePos.height;

            EditorGUI.PropertyField(position, property, label, true);
        }

        /// <summary>
        /// 全体の高さを返す
        /// </summary>
        /// <param name="property">属性が付加されているプロパティ</param>
        /// <param name="label">ラベル</param>
        /// <returns>全体の高さ</returns>
        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            if (!showImage) return base.GetPropertyHeight(property, label);

            return (imageHeight > 0f ? imageHeight : 0f) + EditorGUIUtility.singleLineHeight;
        }
    }
#endif
}
```

## Odin版

```CS:ImageHeaderAttribute.cs
using System;
using UnityEngine;
#if UNITY_EDITOR
using Sirenix.OdinInspector.Editor;
using UnityEditor;
#endif

public class ImageHeaderAttribute : Attribute
{
    public string AssetPath { get; }
    public int Height { get; }

    public ImageHeaderAttribute(string assetPath, int height)
    {
        AssetPath = assetPath;
        Height = height;
    }

#if UNITY_EDITOR
    [DrawerPriority(0.2, 0, 0)]
    public class ImageHeaderAttributeDrawer : OdinAttributeDrawer<ImageHeaderAttribute>
    {
        protected override void DrawPropertyLayout(GUIContent label)
        {
            // パスが指定されていない場合や高さが正値でない場合は表示しない
            if (string.IsNullOrEmpty(Attribute.AssetPath) || Attribute.Height <= 0)
            {
                CallNextDrawer(label);
                return;
            }

            var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(Attribute.AssetPath);
            // テクスチャを取得できない場合は表示しない
            if (texture != null)
            {
                var width = texture.width * (Attribute.Height / (float)texture.height);
                GUILayout.BeginHorizontal();
                GUILayout.FlexibleSpace();
                GUILayout.Label(texture, GUILayout.Width(width), GUILayout.Height(Attribute.Height));
                GUILayout.FlexibleSpace();
                GUILayout.EndHorizontal();
            }

            CallNextDrawer(label);
        }
    }
#endif
}
```

# 使い方
以下のように、フィールドに`ImageHeader`属性を付加します。
```CS:SampleMonoBehaviour.cs
using UnityEngine;

public class SampleMonoBehaviour : MonoBehaviour
{
    [SerializeField] [ImageHeader("Assets/Images/my_icon.jpg", 100)]
    private string headerField;
}
```

第1引数が`Assets`から指定した画像アセットのパスで、第2引数は表示したい画像の高さです。
これで、以下のようにフィールドの上に画像を表示できるようになります。
![スクリーンショット 2022-12-07 232656.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2987563/5eb1a4b6-62b5-f7c8-f0bb-720cb6828b17.png)