69
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

自分だけのPropertyDrawerを作ろう!

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を作ろう!でした!

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
69
Help us understand the problem. What are the problem?