Unity3D開發小貼士(十三)Inspector中使用屬性
阿新 • • 發佈:2019-02-17
我們知道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中使用,也就是我們下面要實現的。
CustomPropertyDrawer用了指定這個類是SetPropertyAttribute的Drawer,所有添加了[SetProperty]特性的欄位都會經過這個Drawer來繪製。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; } } }
在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 () { } }