近期須要完成一個擴展編輯器中的功能,即在Scene視圖中任意選擇某GameObject,而後給這個GameObject動態添加指定腳本,難點是須要讓腳本的屬性也同時暴露出來,讓咱們能夠隨時修改其中公共屬性,並序列化下來。c#
如上圖所示,具體展現的功能就是能夠給場景中任意物體附加指定的腳本,而且顯示腳本想要序列化的屬性。(這裏我其實想將指定的腳本也作成能夠隨時拖動替換的,奈何技術不夠,只能先將要拖動的腳本寫在代碼裏。)編輯器
爲了實現這個功能,須要有如下幾個腳本:ide
以上腳本中,也能夠清楚地看出後兩個腳本是自定義的,核心是實現前兩個腳本。this
using System; [AttributeUsage(AttributeTargets.Property)] public class ExposePropertyAttribute : Attribute { }
using UnityEditor; using UnityEngine; using System; using System.Collections.Generic; using System.Reflection; /* - Integer - Float - Boolean - String - Vector2 - Vector3 - Enum - UnityEngine.Object 代碼中支持以上幾種形式的顯示,還能夠繼續擴展 */ public static class ExposeProperties { public static void Expose(PropertyField[] properties) { GUILayoutOption[] emptyOptions = new GUILayoutOption[0]; EditorGUILayout.BeginVertical(emptyOptions); foreach (PropertyField field in properties) { EditorGUILayout.BeginHorizontal(emptyOptions); switch (field.Type) { case SerializedPropertyType.Integer: field.SetValue(EditorGUILayout.IntField(field.Name, (int)field.GetValue(), emptyOptions)); break; case SerializedPropertyType.Float: field.SetValue(EditorGUILayout.FloatField(field.Name, (float)field.GetValue(), emptyOptions)); break; case SerializedPropertyType.Boolean: field.SetValue(EditorGUILayout.Toggle(field.Name, (bool)field.GetValue(), emptyOptions)); break; case SerializedPropertyType.String: field.SetValue(EditorGUILayout.TextField(field.Name, (String)field.GetValue(), emptyOptions)); break; case SerializedPropertyType.Vector2: field.SetValue(EditorGUILayout.Vector2Field(field.Name, (Vector2)field.GetValue(), emptyOptions)); break; case SerializedPropertyType.Vector3: field.SetValue(EditorGUILayout.Vector3Field(field.Name, (Vector3)field.GetValue(), emptyOptions)); break; case SerializedPropertyType.Enum: field.SetValue(EditorGUILayout.EnumPopup(field.Name, (Enum)field.GetValue(), emptyOptions)); break; case SerializedPropertyType.ObjectReference: field.SetValue(EditorGUILayout.ObjectField(field.Name, (UnityEngine.Object)field.GetValue(), field.GetPropertyType(), true, emptyOptions)); break; default: break; } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndVertical(); } public static PropertyField[] GetProperties(System.Object obj) { List<PropertyField> fields = new List<PropertyField>(); PropertyInfo[] infos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo info in infos) { if (!(info.CanRead && info.CanWrite)) continue; object[] attributes = info.GetCustomAttributes(true); bool isExposed = false; foreach (object o in attributes) { if (o.GetType() == typeof(ExposePropertyAttribute)) { isExposed = true; break; } } if (!isExposed) continue; SerializedPropertyType type = SerializedPropertyType.Integer; if (PropertyField.GetPropertyType(info, out type)) { PropertyField field = new PropertyField(obj, info, type); fields.Add(field); } } return fields.ToArray(); } } public class PropertyField { System.Object m_Instance; PropertyInfo m_Info; SerializedPropertyType m_Type; MethodInfo m_Getter; MethodInfo m_Setter; public SerializedPropertyType Type { get { return m_Type; } } public String Name { get { return ObjectNames.NicifyVariableName(m_Info.Name); } } public PropertyField(System.Object instance, PropertyInfo info, SerializedPropertyType type) { m_Instance = instance; m_Info = info; m_Type = type; m_Getter = m_Info.GetGetMethod(); m_Setter = m_Info.GetSetMethod(); } public System.Object GetValue() { return m_Getter.Invoke(m_Instance, null); } public void SetValue(System.Object value) { m_Setter.Invoke(m_Instance, new System.Object[] { value }); } public Type GetPropertyType() { return m_Info.PropertyType; } public static bool GetPropertyType(PropertyInfo info, out SerializedPropertyType propertyType) { propertyType = SerializedPropertyType.Generic; Type type = info.PropertyType; if (type == typeof(int)) { propertyType = SerializedPropertyType.Integer; return true; } if (type == typeof(float)) { propertyType = SerializedPropertyType.Float; return true; } if (type == typeof(bool)) { propertyType = SerializedPropertyType.Boolean; return true; } if (type == typeof(string)) { propertyType = SerializedPropertyType.String; return true; } if (type == typeof(Vector2)) { propertyType = SerializedPropertyType.Vector2; return true; } if (type == typeof(Vector3)) { propertyType = SerializedPropertyType.Vector3; return true; } if (type.IsEnum) { propertyType = SerializedPropertyType.Enum; return true; } // COMMENT OUT to NOT expose custom objects/types propertyType = SerializedPropertyType.ObjectReference; return true; //return false; } }
using UnityEngine; public class MyType : MonoBehaviour { [HideInInspector] [SerializeField] int m_SomeInt; [HideInInspector] [SerializeField] float m_SomeFloat; [HideInInspector] [SerializeField] bool m_SomeBool; [HideInInspector] [SerializeField] string m_Etc; [ExposeProperty] public int SomeInt { get { return m_SomeInt; } set { m_SomeInt = value; } } [ExposeProperty] public float SomeFloat { get { return m_SomeFloat; } set { m_SomeFloat = value; } } [ExposeProperty] public bool SomeBool { get { return m_SomeBool; } set { m_SomeBool = value; } } [ExposeProperty] public string SomeString { get { return m_Etc; } set { m_Etc = value; } } }
using UnityEditor; using UnityEngine; using System.Collections; [CustomEditor(typeof(MyType))] public class MyTypeEditor : EditorWindow { private PropertyField[] _fields; [MenuItem("Tools/Test")] static void CreateWindow() { var window = GetWindow(typeof(MyTypeEditor), true); window.Show(); } private void OnGUI() { EditorGUILayout.HelpBox("請在場景中選擇任意物體", MessageType.Info); EditorGUILayout.LabelField("選中的物體:"); foreach (var item in Selection.gameObjects) { EditorGUILayout.BeginVertical("Box"); GUILayout.Label(item.name); var sp = item.GetComponent<MyType>(); if (sp != null) { sp = (MyType)EditorGUILayout.ObjectField(sp, typeof(MyType), true); _fields = ExposeProperties.GetProperties(sp); ExposeProperties.Expose(_fields); EditorGUILayout.BeginHorizontal("HelpBox"); if (GUILayout.Button("刪除腳本")) { DestroyImmediate(sp); } EditorGUILayout.EndHorizontal(); } else { if (GUILayout.Button("添加腳本")) { item.AddComponent<MyType>(); } } EditorGUILayout.EndVertical(); } EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("所有添加腳本")) { foreach (var item in Selection.gameObjects) { item.GetOrAddComponent<MyType>(); } } if (GUILayout.Button("所有刪除腳本")) { foreach (var item in Selection.gameObjects) { var sp = item.GetComponent<MyType>(); if (item != null) { DestroyImmediate(sp); } } } EditorGUILayout.EndHorizontal(); } private void OnInspectorUpdate() { this.Repaint(); } }
OK,以上就是實現該功能的全部源碼啦,都比較簡單。code