Skip to content

Instantly share code, notes, and snippets.

@h-sigma
Last active June 8, 2020 21:30
Show Gist options
  • Save h-sigma/4575bf4caf12ada8f4f9fb95a6c97ac2 to your computer and use it in GitHub Desktop.
Save h-sigma/4575bf4caf12ada8f4f9fb95a6c97ac2 to your computer and use it in GitHub Desktop.
An Attribute to use a c# property to get/set the value on a serialized field in the Unity Editor.

Drop ThroughPropertyAttribute.cs anywhere in Assets.

Drop ThroughPropertyDrawer.cs anywhere inside an Editor folder in Assets.

Attach Test to a gameobject to make sure it works. See the code in Test for an example.

Features:

  • Displays field as readonly if setter is missing.
  • Works with non-public getter/setters.
using UnityEngine;
using Utility;
public class Test : MonoBehaviour
{
[SerializeField, ThroughProperty(nameof(Prop))]
private bool owo;
public bool Prop
{
get
{
Debug.Log("Getting " + nameof(owo) + " and it's " + owo.ToString());
return owo;
}
set
{
Debug.Log("Setting " + nameof(owo) + " to " + value.ToString());
owo = value;
}
}
}
using System;
using UnityEngine;
[AttributeUsage(AttributeTargets.Field, Inherited = true)]
public class ThroughPropertyAttribute : PropertyAttribute
{
public string propertySourceField;
public ThroughPropertyAttribute(string propertySourceField)
{
this.propertySourceField = propertySourceField;
}
}
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using Utility;
[CustomPropertyDrawer(typeof(ThroughPropertyAttribute))]
public class ThroughPropertyDrawer : PropertyDrawer
{
private ThroughPropertyAttribute throughAttr;
private SerializedProperty currentProperty;
private PropertyInfo _propertyInfo;
private MethodInfo _getter;
private MethodInfo _setter;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
throughAttr = (ThroughPropertyAttribute) attribute;
currentProperty = property;
if (_getter == null)
{
GetGetter();
if (_getter == null)
{
EditorGUILayout.HelpBox(new GUIContent("Getter missing on property for ThroughPropertyAttribute."));
return;
}
}
if (_setter == null) GetSetter();
var value = _getter.Invoke(property.serializedObject.targetObject, new object[0]);
EditorGUI.BeginChangeCheck();
if (_setter == null) GUI.enabled = false;
switch (property.propertyType)
{
case SerializedPropertyType.Boolean:
value = EditorGUI.Toggle(position, property.displayName, (bool) value);
break;
case SerializedPropertyType.String:
value = EditorGUI.TextField(position, property.displayName, (string) value);
break;
default:
UnityEngine.Debug.Log("owo");
return;
}
if (_setter == null) GUI.enabled = true;
if (EditorGUI.EndChangeCheck() && _setter != null)
{
Undo.RecordObject(property.serializedObject.targetObject, "Set value of " + property.displayName);
_setter.Invoke(property.serializedObject.targetObject, new object[] {value});
}
}
private void GetGetter()
{
if(_propertyInfo == null)
{
var srlPropertyPath = currentProperty.propertyPath;
var csharpPropertyPath = srlPropertyPath.Replace(currentProperty.name, throughAttr.propertySourceField);
_propertyInfo = GetPropertyInfo(currentProperty, csharpPropertyPath);
}
_getter = _propertyInfo.GetGetMethod(true);
}
private void GetSetter()
{
if(_propertyInfo == null)
{
var srlPropertyPath = currentProperty.propertyPath;
var csharpPropertyPath = srlPropertyPath.Replace(currentProperty.name, throughAttr.propertySourceField);
_propertyInfo = GetPropertyInfo(currentProperty, csharpPropertyPath);
}
_setter = _propertyInfo.GetSetMethod(true);
}
public static PropertyInfo GetPropertyInfo(SerializedProperty property, string csharpPropPath)
{
object obj = property.serializedObject.targetObject;
var flag = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
var paths = csharpPropPath.Split('.');
PropertyInfo prop = null;
for (int i = 0; i < paths.Length; i++)
{
var path = paths[i];
if (obj == null)
throw new System.NullReferenceException("Can't set a value on a null instance");
var type = obj.GetType();
if (path == "Array")
{
path = paths[++i];
var iter = (obj as System.Collections.IEnumerable);
if (iter == null)
//Property path thinks this property was an enumerable, but isn't. property path can't be parsed
throw new System.ArgumentException("SerializedProperty.PropertyPath [" + csharpPropPath +
"] thinks that [" + paths[i - 2] + "] is Enumerable.");
var sind = path.Split('[', ']');
int index = -1;
if (sind == null || sind.Length < 2)
// the array string index is malformed. the property path can't be parsed
throw new System.FormatException("PropertyPath [" + csharpPropPath + "] is malformed");
if (!Int32.TryParse(sind[1], out index))
//the array string index in the property path couldn't be parsed,
throw new System.FormatException("PropertyPath [" + csharpPropPath + "] is malformed");
obj = ElementAtOrDefault(iter, index);
continue;
}
prop = type.GetProperty(path, flag);
if (prop == null)
//field wasn't found
throw new System.MissingFieldException("The field [" + path + "] in [" + csharpPropPath +
"] could not be found");
if (i < paths.Length - 1)
obj = prop.GetValue(obj);
}
if (prop == null) throw new System.Exception();
return prop;
}
public static PropertyInfo GetPropertyInfo(SerializedProperty property, string csharpPropPath,
Type chsarpPropType)
{
var prop = GetPropertyInfo(property, csharpPropPath);
if (chsarpPropType == null || property.serializedObject.targetObject.GetType() == null || !prop.PropertyType.IsAssignableFrom(chsarpPropType))
{
throw new System.InvalidCastException("Cannot cast [" + chsarpPropType + "] into Field type [" +
prop.PropertyType + "]");
}
return prop;
}
public static System.Object ElementAtOrDefault(System.Collections.IEnumerable collection, int index)
{
var enumerator = collection.GetEnumerator();
int j = 0;
for (; enumerator.MoveNext(); j++)
{
if (j == index) break;
}
System.Object element = (j == index)
? enumerator.Current
: default(System.Object);
var disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
return element;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment