Skip to content

Instantly share code, notes, and snippets.

@v01pe
Last active November 14, 2022 18:03
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save v01pe/79db7566e2feff7ffab87676e220fd20 to your computer and use it in GitHub Desktop.
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
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour
{
public ExampleClass example;
void Start() {}
void Update() {}
}
using UnityEngine;
using System;
[Serializable]
public class ExampleClass
{
public string field = "example string";
public void Print()
{
Debug.Log("ExampleClass.field: " + field);
}
}
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();
}
}
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);
}
}
@v01pe
Copy link
Author

v01pe commented Aug 19, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment