1. 程式人生 > >Unity3D開發小貼士(十三)Inspector中使用屬性

Unity3D開發小貼士(十三)Inspector中使用屬性

我們知道Unity的元件類中,public的變數可以直接在Inspector中編輯,而其他訪問級別的變數,可以為它們新增[SerializeField]特性來實現同樣的效果。但是如果我們希望一個變數改變的時候呼叫一個屬性(Property)的set訪問器該怎樣實現呢?

首先我們需要自定義一個特性(參考C#語法小知識(七)特性):

using UnityEngine;
using System.Collections;
using System;

[AttributeUsage(AttributeTargets.Field)]  
public class SetPropertyAttribute : PropertyAttribute {
	public string propertyName { get; private set;}
	public bool dirty { get; set;}
	public SetPropertyAttribute(string propertyName_)
	{
		propertyName = propertyName_;
	}
}

為這個特性添加了一個特性AttributeUsage,表示它的用途,只用在Field(欄位/成員變數)上。

SetPropertyAttribute繼承自PropertyAttribute,這是UnityEngine名稱空間下的一個特性,用來在編輯器的PropertyDrawer中使用,也就是我們下面要實現的。

using UnityEditor;
using UnityEngine;
using System;
using System.Collections;
using System.Reflection;

[CustomPropertyDrawer(typeof(SetPropertyAttribute))]
public class AccessPropertyDrawer : PropertyDrawer {

	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
	{
		// Rely on the default inspector GUI
		EditorGUI.BeginChangeCheck ();
		EditorGUI.PropertyField(position, property, label);

		// Update only when necessary
		SetPropertyAttribute setProperty = attribute as SetPropertyAttribute;
		if (EditorGUI.EndChangeCheck ()) {
			setProperty.dirty = true;

		} else if (setProperty.dirty) {
			object obj = property.serializedObject.targetObject;
			Type type = obj.GetType();
			PropertyInfo pi = type.GetProperty(setProperty.propertyName);
			if (pi == null)
			{
				Debug.LogError("Invalid property name: " + setProperty.propertyName + "\nCheck your [SetProperty] attribute");
			}
			else
			{
				pi.SetValue(obj, fieldInfo.GetValue(obj), null);
			}
			setProperty.dirty = false;
		}
	}
}
CustomPropertyDrawer用了指定這個類是SetPropertyAttribute的Drawer,所有添加了[SetProperty]特性的欄位都會經過這個Drawer來繪製。

BeginChangeCheck和EndChangeCheck之間使用PropertyField來繪製欄位,就可以檢查欄位的值是否發生了變化。

但是變化並不會立即體現出來,需要等下次繪製的時候再呼叫屬性的set訪問器。

最後我們測試一下:

using UnityEngine;
using System.Collections;

public class NewBehaviourScript : MonoBehaviour {

	[SerializeField]
	[SetProperty("Test")]
	protected int _test;

	public int Test
	{
		get { return _test;}
		set { _test = value;
			Debug.Log ("Test"+_test);}
	}

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
	
	}
}