【Unity】【エディタ拡張】AnimationCurveを正規化するアトリビュートを作る

Unityのエディタ拡張でAnimationCurveを正規化するアトリビュートを作る方法についてまとめます。

Unity2019.4

やりたいこと

以下の図のように、0~1の値しか取れないアニメーションカーブを作成したいとします。

f:id:halya_11:20210326002332g:plain
正規化されたAnimationCurve

正規化の処理自体は以前書いた以下の記事の通りなのですが、

light11.hatenadiary.com

今回はAnimationCurveのフィールドに以下のようにアトリビュートを付けるだけで正規化できるようにします。

[NormalizedAnimationCurve]
public AnimationCurve animationCurve;

ソースコード

ソースコードは以下の通りです。

using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;

#endif

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class NormalizedAnimationCurveAttribute : PropertyAttribute
{
    public NormalizedAnimationCurveAttribute(bool normalizeValue = true, bool normalizeTime = true)
    {
        NormalizeValue = normalizeValue;
        NormalizeTime = normalizeTime;
    }

    public bool NormalizeValue { get; }
    public bool NormalizeTime { get; }
}

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(NormalizedAnimationCurveAttribute))]
public class NormalizedAnimationCurveAttributeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var attr = (NormalizedAnimationCurveAttribute) attribute;

        if (property.propertyType != SerializedPropertyType.AnimationCurve)
        {
            // AnimationCurve以外のフィールドにアトリビュートが付けられていた場合のエラー表示
            position = EditorGUI.PrefixLabel(position, label);
            var preIndent = EditorGUI.indentLevel;
            EditorGUI.indentLevel = 0;
            EditorGUI.LabelField(position, "Use NormalizedAnimationCurveAttribute with AnimationCurve.");
            EditorGUI.indentLevel = preIndent;
            return;
        }

        using (var ccs = new EditorGUI.ChangeCheckScope())
        {
            EditorGUI.PropertyField(position, property, label, true);
            if (ccs.changed)
            {
                if (attr.NormalizeValue)
                {
                    property.animationCurveValue = NormalizeValue(property.animationCurveValue);
                }

                if (attr.NormalizeTime)
                {
                    property.animationCurveValue = NormalizeTime(property.animationCurveValue);
                }
            }
        }
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return EditorGUI.GetPropertyHeight(property);
    }

    // アニメーションカーブの値(縦軸)を正規化する
    private static AnimationCurve NormalizeValue(AnimationCurve curve)
    {
        var keys = curve.keys;
        if (keys.Length <= 0)
        {
            return curve;
        }

        var minVal = keys[0].value;
        var maxVal = minVal;
        foreach (var t in keys)
        {
            minVal = Mathf.Min(minVal, t.value);
            maxVal = Mathf.Max(maxVal, t.value);
        }

        var range = maxVal - minVal;
        var valScale = range < 1 ? 1 : 1 / range;
        var valOffset = 0f;
        if (range < 1)
        {
            if (minVal > 0 && minVal + range <= 1)
            {
                valOffset = minVal;
            }
            else
            {
                valOffset = 1 - range;
            }
        }

        for (var i = 0; i < keys.Length; ++i)
        {
            keys[i].value = (keys[i].value - minVal) * valScale + valOffset;
        }

        curve.keys = keys;
        return curve;
    }

    // アニメーションカーブの時間(横軸)を正規化する
    private static AnimationCurve NormalizeTime(AnimationCurve curve)
    {
        var keys = curve.keys;
        if (keys.Length <= 0)
        {
            return curve;
        }

        var minTime = keys[0].time;
        var maxTime = minTime;
        foreach (var t in keys)
        {
            minTime = Mathf.Min(minTime, t.time);
            maxTime = Mathf.Max(maxTime, t.time);
        }

        var range = maxTime - minTime;
        var timeScale = range < 0.0001f ? 1 : 1 / range;
        for (var i = 0; i < keys.Length; ++i)
        {
            keys[i].time = (keys[i].time - minTime) * timeScale;
        }

        curve.keys = keys;
        return curve;
    }
}
#endif

使い方

使い方はこんな感じです。

[NormalizedAnimationCurve]
public AnimationCurve animationCurve;

これで正規化されたアニメーションカーブが実現できます。

f:id:halya_11:20210326002332g:plain
正規化されたAnimationCurve

関連

light11.hatenadiary.com

light11.hatenadiary.com