Edited at
UnityDay 16

自分だけのPropertyDrawerを作ろう!

More than 5 years have passed since last update.

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