自分だけのPropertyDrawerを作ろう!

  • 48
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Unity4には新機能としてPropertyDrawerというものがあります。みなさんは普段スクリプトに

SampleScript.cs
using UnityEngine;
using System.Collections;

public class SampleScript : MonoBehaviour {

    public float hp;

    void Update()
    {
        Debug.Log( hp );
    }
}

public float hp;を記述し、インスペクタ上で自由に編集を行なっていると思います。

例えばこのhpですが、仮に自キャラのHPだとしましょう。
自キャラのHPがマイナスになって良いでしょうか?HPの上限を設定しなくて良いでしょうか?(そのようなゲームなら構いませんが…)

  • 自キャラのHPがマイナスになってはいけない
  • HPの上限は決まっている

上記の設定がすでにあるのであれば、開発の時点で考慮しておくべきです。
その考慮の1つとしてPropertyDrawerを使用しましょう。
PropertyDrawerはAttributeがあることによって成り立つ機能になっています。

Attributeを使う

上記のHPの話、つまりHPの下限と上限を設定するということです。

結論から言うと

SampleScript.cs
using UnityEngine;

public class SampleScript : MonoBehaviour
{
    [Range2( 0f, 100f )]
    public float hp;
}

で実装が可能です。

Range( float min , float max )で下限と上限を決めているわけです。理解はすぐ出来ます。

これはUnityが事前に用意しているRangeAttributeです。

今回はこのRangeAttributeを自作してみましょう。

Attributeを作る

Attributeを作るには2つのファイルが必要です。

  • Attibuteを定義するスクリプトファイル
  • Propertyを描画するスクリプトファイル

まずはAttibuteを定義するスクリプトファイルを作成しましょう

Attibuteを定義するスクリプトファイルの作成

早速、作成して行きましょう。RangeAttributeだと被ってしまうのでRange2Attributeとします。

Range2Attribute.cs
using UnityEngine;

public class Range2Attribute : PropertyAttribute
{
    public float min;
    public float max;

    public Range2Attribute (float min, float max)
    {
        this.min = min;
        this.max = max;
    }
}

PropertyAttributeを継承したRange2Attribute.csを作成します。
引数としてminとmaxを書くだけでなく、

  • public float min;
  • public float max;

も必ず記述するようにしましょう。これは次のPropertyを描画するスクリプトファイルで使用します。

Propertyを描画するスクリプトファイル

さて、Attributeの定義が終わったら、次はインスペクターに描画するためのPropertyDrawerを作成しましょう

Range2Drawer.cs
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer( typeof ( Range2Attribute ) )]
public class Range2Drawer : PropertyDrawer
{
    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    {
        Range2Attribute range2Attribute = (Range2Attribute)attribute;

        if (property.propertyType == SerializedPropertyType.Float) {
            EditorGUI.Slider (position, property, range2Attribute.min, range2Attribute.max, label);
        }
    }
}

[CustomPropertyDrawer( typeof ( Range2Attribute ) )]をつけ、PropertyDrawerを継承して下さい。そしてOnGUIをオーバーライドします。OnGUIで描画するものがインスペクターに表示されます。
変数としてattributeがあるのでこれをRange2Attributeにキャストします。
次にOnGUIの引数としてpropertyが取得できます。これはAttributeをつけているプロパティがシリアライズ化されたものです。

Range2AttributeExample.cs
using UnityEngine;

public class Range2AttributeExample : MonoBehaviour
{
    [Range2( 0f, 100f )]
    public float hp;
}

上記のコードだとhpがシリアライズ化されたプロパティとなります。

...話はRange2Drawer.csに戻りますが、positionが通常の描画で使用される(はずだった)Rect情報です。通常はこのpositionをそのまま使用して下さい。
labelはプロパティ名や場合により画像が含まれています。今回はHpの文字がlabelの中に格納されています。
そして、今回RangeAttributeで定義したminmaxも上記の通りに使用することができます。
さて、これで必要な情報が揃いました。文字にすると大変ですね。

今回はEditorGUIクラスのSliderを使用してスライダーを描画します。詳しい使い方はドキュメントを見ていただくとして...

以上で完成です。お疲れ様でした。
さて、次は動作を確認してみましょう。

作ったAttributeを試す

Range2AttributeExample.cs
using UnityEngine;

public class Range2AttributeExample : MonoBehaviour
{
    [Range2( 0f, 100f )]
    public float hp;
}

Range2AttributeExample.csを適当なGameObjectにアタッチして下さい。
すると、以下の画像のようなスライダーが表示されるはずです。

おめでとうございます。これであなたはエディタ拡張(プロパティ拡張?)を行いました。
エディタ拡張の世界へようこそ!

TIPS

今回作ったようなRange2Attributeなら別にいいのですが、アイデアによって

  • 処理時間のかかるAttributeを作成したい

という場合があると思います。良いですね。処理がかかるけど素晴らしいAttributeを作成しているんでしょうね。

処理時間のかかるAttributeを作成する注意点として、

  • PropertyDrawerの処理はインスペクターの描画処理と同時に行われるため、PropertyDrawerの描画処理が終わるまでインスペクターは描画されない。

という点に注意して下さい。
3秒かかる処理だとインスペクターは3秒間表示されずユーザーは待つ必要があります。
これで困ることは、Transformや他のコンポーネントの情報を見たいのに待たないと見れない。という点です。

なので

SampleAttribute.cs
using UnityEngine;

public class SampleAttribute : PropertyAttribute
{
    public bool init = false;

    public SampleAttribute ()
    {

    }
}
SampleDrawer.cs
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(SampleAttribute))]
public class SampleDrawer : PropertyDrawer
{

    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    {
        if (sampleAttribute.init == false) {
            sampleAttribute.init = true;
            return;
        }


        ...


    }

    public override float GetPropertyHeight (SerializedProperty property, GUIContent label)
    {
        if (sampleAttribute.init == false) {
            return 0;
        }

        return base.GetPropertyHeight (property, label);
    }
}

Attributeにbool属性のプロパティを追加して初回のみすぐreturnさせるようにします。
これで、とりあえず、インスペクターが表示されます。
Attributeの実際の処理は2回目以降行われるということですね。

GetPropertyHeightはインスペクターで確保するGUIの高さです。

以上、自分だけのAttributeを作ろう!でした!