Programming Serendipity

プログラミングを中心に種々雑多に書き留めます

1行プロパティドロアーの実装を改良してみた

前回のコードを改良して、スクリプトに分けてみました。

using UnityEngine;
using UnityEditor;
using System;

/// <summary>
/// PropertyDrawerで、1行に収めるための基底クラス
/// </summary>
public class OneLineDrawer : PropertyDrawer 
{
    /// <summary>描画領域全体</summary>
    private Rect wholeRect;

    /// <summary>X座標の部分和</summary>
    private float partialSum;

    /// <summary>自身のプロパティ</summary>
    private SerializedProperty prop;

    /// <summary>インデント段階を保存</summary>
    private int lastIndentLevel;

    /// <summary>
    /// プロパティの開始
    /// </summary>
    /// <param name="position">OnGUIと同じ</param>
    /// <param name="property">OnGUIと同じ</param>
    /// <param name="label">OnGUIと同じ</param>
    protected void BeginProperty(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUIUtility.labelWidth = 80 + EditorGUI.indentLevel * 20;
        label = EditorGUI.BeginProperty(position, label, property);
        Init(position, property, label);

        this.lastIndentLevel = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;
    }

    /// <summary>
    /// プロパティの終了
    /// </summary>
    protected void EndProperty()
    {
        EditorGUI.indentLevel = this.lastIndentLevel;
        EditorGUI.EndProperty();
    }

    /// <summary>
    /// 初期化
    /// </summary>
    /// <param name="position">OnGUIと同じ</param>
    /// <param name="property">OnGUIと同じ</param>
    /// <param name="label">OnGUIと同じ</param>
    void Init(Rect position, SerializedProperty property, GUIContent label)
    {
        this.partialSum = 0;
        this.prop = property;
        this.wholeRect = EditorGUI.PrefixLabel(position, label);
    }

    /// <summary>
    /// 1行の中での描画の指定
    /// </summary>
    /// <param name="widthRate">全体に占めるプロパティの割合</param>
    /// <param name="propertyName">プロパティの名前</param>
    /// <param name="label">ラベルに使用する文字列</param>
    /// <param name="labelWidth">ラベルの幅</param>
    protected void DividedField(float widthRate, string propertyName, string label = "", float labelWidth = 0)
    {
        Debug.Assert(0 < widthRate);
        Debug.Assert(widthRate < 1);
        Debug.Assert(!string.IsNullOrEmpty(propertyName));
        Debug.Assert(label != null);
        Debug.Assert(labelWidth >= 0);

        // 幅とRectの算出
        var width = this.wholeRect.width * widthRate;
        var rect = new Rect(this.wholeRect.x + this.partialSum, this.wholeRect.y, width, this.wholeRect.height);
        this.partialSum += width;

        // プロパティが見えなくならない様に調整
        labelWidth = Mathf.Clamp(labelWidth, 0, rect.width - 20);

        // ラベル幅を設定し、プロパティを描画する
        EditorGUIUtility.labelWidth = labelWidth;
        var item = this.prop.FindPropertyRelative(propertyName);
        if (item == null)
        {
            Debug.LogWarningFormat("Failed to find property: '{0}' in '{1}'", propertyName, this.GetType());
        }
        else
        {
            EditorGUI.PropertyField(rect, item, new GUIContent(label));
        }
    }

    /// <summary>
    /// BeginProperty()とEndProperty()を自動で呼ぶラッパークラス
    /// </summary>
    protected class PropertyWrapper : IDisposable
    {
        /// <summary>元クラスの参照</summary>
        OneLineDrawer self;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="self">自身</param>
        /// <param name="position">OnGUIと同じ</param>
        /// <param name="property">OnGUIと同じ</param>
        /// <param name="label">OnGUIと同じ</param>
        public PropertyWrapper(OneLineDrawer self, Rect position, SerializedProperty property, GUIContent label)
        {
            this.self = self;
            self.BeginProperty(position, property, label);
        }

        /// <summary>
        /// IDisposableの実装
        /// </summary>
        public void Dispose()
        {
            self.EndProperty();
        }
    }
}

使い方:

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(SomeClass))]
public class SomeClassDrawer  : OneLineDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        using (new PropertyWrapper(this, position, property, label))
        {
            DividedField(0.1f, "active");
            DividedField(0.15f, "stage");
            DividedField(0.15f, "level", "-", 10);
            DividedField(0.3f, "hp", "hp", 44);
            DividedField(0.3f, "gold", "gold", 44);
        }
    }
}

これです、これがやりたかった。最終的なドロアークラスの記述が素晴らしく簡潔になりました。 OneLineDrawer自身はOnGUI()を実装せず、関数とラッパークラスを用意し、それを継承してラッパークラスをインスタンス化して、メソッドの呼び出しをラッパークラスに任せて使用します。 自分で呼ぶと呼び忘れがありますからね。

とりあえず満足しました。