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.
First script:
Replace “type = type.GetEnumerableType();” ( line 23 ) with “type = type.GetType();”
Thank you for the tip! I tested this and verified the current solution is accurate. The goal of that line is to get the type of the array elements.