After coming across an issue where the PropertyField method of a SerializedProperty wasn’t working when used in an Editor Window in Unity, I decided to create my own flexible field. This is the result.
ReflectionExtensions.cs
using UnityEditor; using System; using System.Linq; using System.Reflection; using System.Collections.Generic; // Researched from: // https://answers.unity.com/questions/929293/get-field-type-of-serializedproperty.html // https://stackoverflow.com/questions/7072088/why-does-type-getelementtype-return-null public static class ReflectionExtensions { public static Type GetType(SerializedProperty property) { string[] splitPropertyPath = property.propertyPath.Split('.'); Type type = property.serializedObject.targetObject.GetType(); for (int i = 0; i < splitPropertyPath.Length; i++) { if (splitPropertyPath[i] == "Array") { type = type.GetEnumerableType(); i++; //skip "data[x]" } else type = type.GetField(splitPropertyPath[i], BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance).FieldType; } return type; } public static Type GetEnumerableType(this Type type) { if (type == null) throw new ArgumentNullException("type"); if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) return type.GetGenericArguments()[0]; var iface = (from i in type.GetInterfaces() where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>) select i).FirstOrDefault(); if (iface == null) throw new ArgumentException("Does not represent an enumerable type.", "type"); return GetEnumerableType(iface); } }
CustomEditorGUI.cs
using UnityEngine; using UnityEditor; using System.Linq; using System.Reflection; /* * Generic and Gradient property types are unimplemented * It's possible gradient can be implemented: https://answers.unity.com/questions/436295/how-to-have-a-gradient-editor-in-an-editor-script.html * ExposedReference or ObjectReference may not be * Reference: * https://github.com/Unity-Technologies/UnityCsReference * https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/EditorGUI.cs */ public static class CustomEditorGUI { public static void PropertyField(Rect position, SerializedProperty property, bool includeChildren = false) { PropertyField(position, property, new GUIContent(property.displayName), includeChildren); } public static void PropertyField(Rect position, SerializedProperty property, GUIContent label, bool includeChildren = false) { if (includeChildren || property.propertyType == SerializedPropertyType.Generic) { property.isExpanded = EditorGUILayout.Foldout(property.isExpanded, property.displayName); if (includeChildren && property.isExpanded) { foreach (SerializedProperty childProperty in property) PropertyField(position, childProperty, new GUIContent(property.displayName), false); } return; } switch (property.propertyType) { case SerializedPropertyType.AnimationCurve: property.animationCurveValue = EditorGUI.CurveField(position, label, property.animationCurveValue); break; case SerializedPropertyType.ArraySize: property.intValue = EditorGUI.DelayedIntField(position, label, property.intValue); break; case SerializedPropertyType.Boolean: property.boolValue = EditorGUI.Toggle(position, label, property.boolValue); break; case SerializedPropertyType.Bounds: property.boundsValue = EditorGUI.BoundsField(position, label, property.boundsValue); break; case SerializedPropertyType.BoundsInt: property.boundsIntValue = EditorGUI.BoundsIntField(position, label, property.boundsIntValue); break; case SerializedPropertyType.Character: string newValue = EditorGUI.TextField(position, label, new string(new char[] { (char)property.intValue })); property.intValue = newValue.Length > 0 ? newValue[0] : '\0'; break; case SerializedPropertyType.Color: property.colorValue = EditorGUI.ColorField(position, label, property.colorValue); break; case SerializedPropertyType.Enum: GUIContent[] displayNames = property.enumDisplayNames.Select(name => new GUIContent(name)).ToArray(); property.enumValueIndex = EditorGUI.Popup(position, label, property.enumValueIndex, displayNames); break; case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = EditorGUI.ObjectField(position, label, property.objectReferenceValue, ReflectionExtensions.GetType(property), true); break; case SerializedPropertyType.Float: property.floatValue = EditorGUI.FloatField(position, label, property.floatValue); break; case SerializedPropertyType.Integer: property.intValue = EditorGUI.IntField(position, label, property.intValue); break; case SerializedPropertyType.LayerMask: MethodInfo method = typeof(EditorGUI).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).First(t => t.Name == "LayerMaskField"); method.Invoke(null, new object[] { position, property, label }); break; case SerializedPropertyType.ObjectReference: property.objectReferenceValue = EditorGUI.ObjectField(position, label, property.objectReferenceValue, ReflectionExtensions.GetType(property), true); break; case SerializedPropertyType.Quaternion: Quaternion quaternion = property.quaternionValue; Vector4 quaternionValues = new Vector4(quaternion.x, quaternion.y, quaternion.z, quaternion.w); quaternionValues = EditorGUI.Vector4Field(position, label, quaternionValues); property.quaternionValue = new Quaternion(quaternionValues.x, quaternionValues.y, quaternionValues.z, quaternionValues.w); break; case SerializedPropertyType.Rect: property.rectValue = EditorGUI.RectField(position, label, property.rectValue); break; case SerializedPropertyType.RectInt: property.rectIntValue = EditorGUI.RectIntField(position, label, property.rectIntValue); break; case SerializedPropertyType.String: property.stringValue = EditorGUI.TextField(position, label, property.stringValue); break; case SerializedPropertyType.Vector2: property.vector2Value = EditorGUI.Vector2Field(position, label, property.vector2Value); break; case SerializedPropertyType.Vector2Int: property.vector2IntValue = EditorGUI.Vector2IntField(position, label, property.vector2IntValue); break; case SerializedPropertyType.Vector3: property.vector3Value = EditorGUI.Vector3Field(position, label, property.vector3Value); break; case SerializedPropertyType.Vector3Int: property.vector3IntValue = EditorGUI.Vector3IntField(position, label, property.vector3IntValue); break; case SerializedPropertyType.Vector4: property.vector4Value = EditorGUI.Vector4Field(position, label, property.vector4Value); break; /* case SerializedPropertyType.Gradient: var method = typeof(EditorGUI).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).First(t => t.Name == "GradientField"); var change = m.Invoke(null, new object[] { rect, gradient }); method = typeof(EditorGUI).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).First(t => t.Name == "DefaultPropertyField"); method.Invoke(null, new object[] { position, property, label }); break; */ default: Debug.LogError("SerializedPropertyType: " + property.propertyType + " not handled"); break; } } }
CustomEditorGUILayout.cs
sing UnityEngine; using UnityEditor; public static class CustomEditorGUILayout { public static bool PropertyField(SerializedProperty property, params GUILayoutOption[] options) { return PropertyField(property, new GUIContent(property.displayName), false, options); } public static bool PropertyField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options) { return PropertyField(property, label, false, options); } public static bool PropertyField(SerializedProperty property, bool includeChildren, params GUILayoutOption[] options) { return PropertyField(property, new GUIContent(property.displayName), includeChildren, options); } public static bool PropertyField(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options) { if (includeChildren || property.propertyType == SerializedPropertyType.Generic) { property.isExpanded = EditorGUILayout.Foldout(property.isExpanded, property.displayName); if (includeChildren && property.isExpanded) { foreach (SerializedProperty childProperty in property) PropertyField(childProperty, new GUIContent(property.displayName), false, options); } return false; } Rect position = EditorGUILayout.GetControlRect(label.text.Length > 0, EditorGUI.GetPropertyHeight(property), options); CustomEditorGUI.PropertyField(position, property, label, includeChildren); return property.hasChildren && property.isExpanded && !includeChildren; } }
The above code is provided under the MIT license.
Summary
It’s a pretty narrow use case, but I hope it helps anyone else doing something similar! Good luck. If you appreciate it, consider donating or checking out one of my assets on the Unity Asset store.