Last active
November 14, 2022 18:03
-
-
Save v01pe/79db7566e2feff7ffab87676e220fd20 to your computer and use it in GitHub Desktop.
A relative simple way to get the instance object from a custom property drawer that draws a serialized class inside a component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
public class Example : MonoBehaviour | |
{ | |
public ExampleClass example; | |
void Start() {} | |
void Update() {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System; | |
[Serializable] | |
public class ExampleClass | |
{ | |
public string field = "example string"; | |
public void Print() | |
{ | |
Debug.Log("ExampleClass.field: " + field); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
[CustomPropertyDrawer (typeof(ExampleClass))] | |
public class ExampleProperty : NestablePropertyDrawer | |
{ | |
protected new ExampleClass propertyObject { get { return (ExampleClass)base.propertyObject; } } | |
private SerializedProperty stringField = null; | |
protected override void Initialize(SerializedProperty prop) | |
{ | |
base.Initialize(prop); | |
if (stringField == null) | |
stringField = prop.FindPropertyRelative("field"); | |
} | |
public override void OnGUI(Rect position, SerializedProperty prop, GUIContent label) | |
{ | |
base.OnGUI(position, prop, label); | |
EditorGUI.BeginProperty(position, label, prop); | |
EditorGUI.BeginChangeCheck(); | |
EditorGUI.PropertyField(position, stringField); | |
if (EditorGUI.EndChangeCheck()) | |
{ | |
stringField.serializedObject.ApplyModifiedProperties(); | |
propertyObject.Print(); | |
} | |
EditorGUI.EndProperty(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
using System; | |
using System.Collections; | |
using System.Reflection; | |
using System.Text.RegularExpressions; | |
public class NestablePropertyDrawer : PropertyDrawer | |
{ | |
private bool initialized = false; | |
protected object propertyObject = null; | |
protected Type objectType = null; | |
private static readonly Regex matchArrayElement = new Regex(@"^data\[(\d+)\]$"); | |
protected virtual void Initialize(SerializedProperty prop) | |
{ | |
if (initialized) | |
return; | |
SerializedObject serializedObject = prop.serializedObject; | |
string path = prop.propertyPath; | |
propertyObject = serializedObject == null || serializedObject.targetObject == null ? null : serializedObject.targetObject; | |
objectType = propertyObject == null ? null : propertyObject.GetType(); | |
if (!string.IsNullOrEmpty(path) && propertyObject != null) | |
{ | |
string[] splitPath = path.Split('.'); | |
Type fieldType = null; | |
//work through the given property path, node by node | |
for (int i = 0; i < splitPath.Length; i++) | |
{ | |
string pathNode = splitPath[i]; | |
//both arrays and lists implement the IList interface | |
if (fieldType != null && typeof(IList).IsAssignableFrom(fieldType)) | |
{ | |
//IList items are serialized like this: `Array.data[0]` | |
Debug.AssertFormat(pathNode.Equals("Array", StringComparison.Ordinal), serializedObject.targetObject, "Expected path node 'Array', but found '{0}'", pathNode); | |
//just skip the `Array` part of the path | |
pathNode = splitPath[++i]; | |
//match the `data[0]` part of the path and extract the IList item index | |
Match elementMatch = matchArrayElement.Match(pathNode); | |
int index; | |
if (elementMatch.Success && int.TryParse(elementMatch.Groups[1].Value, out index)) | |
{ | |
IList objectArray = (IList)propertyObject; | |
bool validArrayEntry = objectArray != null && index < objectArray.Count; | |
propertyObject = validArrayEntry ? objectArray[index] : null; | |
objectType = fieldType.IsArray | |
? fieldType.GetElementType() //only set for arrays | |
: fieldType.GenericTypeArguments[0]; //type of `T` in List<T> | |
} | |
else | |
{ | |
Debug.LogErrorFormat(serializedObject.targetObject, "Unexpected path format for array item: '{0}'", pathNode); | |
} | |
//reset fieldType, so we don't end up in the IList branch again next iteration | |
fieldType = null; | |
} | |
else | |
{ | |
FieldInfo field; | |
Type instanceType = objectType; | |
BindingFlags fieldBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; | |
do | |
{ | |
field = instanceType.GetField(pathNode, fieldBindingFlags); | |
//b/c a private, serialized field of a subclass isn't directly retrievable, | |
fieldBindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; | |
//if neccessary, work up the inheritance chain until we find it. | |
instanceType = instanceType.BaseType; | |
} | |
while (field == null && instanceType != typeof(object)); | |
//store object info for next iteration or to return | |
propertyObject = field == null || propertyObject == null ? null : field.GetValue(propertyObject); | |
fieldType = field == null ? null : field.FieldType; | |
objectType = fieldType; | |
} | |
} | |
} | |
initialized = true; | |
} | |
public override void OnGUI(Rect position, SerializedProperty prop, GUIContent label) | |
{ | |
Initialize(prop); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yeah, I remember doing a similar thing for a project at some point. I think in my case it was about multi object editing, which also reuses the same drawer instance. But I guess I'll not update this gist to support that.