Is it possible to expose properties (as opposed to fields) of a custom class to the Unity editor?
The fields of a custom class can be exposed to the editor by adding a System.Serializable attribute (this is implicit in JavaScript, but not so in C#). This is documented here. However, I have noticed that this only works for fields, but not properties.
For example:
using UnityEngine;
using System.Collections;
public class NewBehaviourScript : MonoBehaviour {
public Test test = new Test();
}
[System.Serializable]
public class Test
{
public int publicField = 5;
private int _privateProperty = 0;
public int publicProperty { get { return _privateProperty; } set { _privateProperty = value; } }
}
In this example, publicField will be visible and editable to the Unity editor. Even _privateProperty will be visible to the editor (just not editable). However, publicProperty will not be accessible at all.
Is there any way to make properties exposed to the editor?
You may want to write a custom editor. Unity just can't safely blanket serialize/deserialize properties, since getters and setters can have side effects.
It can be done with a PropertyAttribute. An older version of Unity PostProcessing contained GetSetAttribute which calls a field’s GetSet if it is changed via the inspector.
Put GetSetAttribute.cs in Assets/Scripts (not Editor):
using UnityEngine;
public sealed class GetSetAttribute : PropertyAttribute {
public readonly string name;
public bool dirty;
public GetSetAttribute(string name) {
this.name = name;
}
}
Put GetSetDrawer.cs in Assets/Scripts/Editor:
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(GetSetAttribute))]
sealed class GetSetDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
GetSetAttribute attribute = (GetSetAttribute)base.attribute;
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label);
if (EditorGUI.EndChangeCheck()) {
attribute.dirty = true;
} else if (attribute.dirty) {
var parent = GetParentObject(property.propertyPath, property.serializedObject.targetObject);
var type = parent.GetType();
var info = type.GetProperty(attribute.name);
if (info == null)
Debug.LogError("Invalid property name \"" + attribute.name + "\"");
else
info.SetValue(parent, fieldInfo.GetValue(parent), null);
attribute.dirty = false;
}
}
public static object GetParentObject(string path, object obj) {
var fields = path.Split('.');
if (fields.Length == 1)
return obj;
FieldInfo info = obj.GetType().GetField(fields[0], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
obj = info.GetValue(obj);
return GetParentObject(string.Join(".", fields, 1, fields.Length - 1), obj);
}
}
Use it like this:
using UnityEngine;
using UnityEngine.UI;
public class MyComponent : MonoBehaviour {
[SerializeField, GetSet("fillAmount")]
private float _fillAmount = 0.5f;
public float fillAmount {
get {
return _fillAmount;
}
set {
Debug.Log("Calling set!");
_fillAmount = value;
fillImage.fillAmount = value;
}
}
}
In addition, you can hide _privateProperty with `[HideInInspector]` (link) if you want. Also, convention dictates that your properties should begin with a capital letter, if you care.
With C# version > 7.3, there is a very neat trick to expose auto-properties. All auto properties have a backing field, so all you have to do is mark that field with SerializeField like this:
[field: SerializeField]
public float Speed { get; set; }
Note: u have to use "field: ". This syntax tells compiler that all attributes in that block refer to backing field.
@SarahNorthway 's solution seems pretty deluxe. I’m probably going to use that in future projects.
However, there’s a simpler way you can build on @DenninDalke 's cheap/easy solution, which is to create an OnValidate() function which handles what your getters would do. It gets called when any value changes in the inspector (and when the script loads in the inspector), so you can’t do anything heavy in it, but it gets the job done well with the least effort.
[SerializeField]
private int _SomeProperty;
public int SomeProperty
{
get { return _SomeProperty; }
set { DoSomething(); }
}
private void OnValidate()
{
if (_SomeProperty == 1)
{
DoSomething();
}
}
You could also cache the old value to see if it changed, but at this point you might just be better off using Sarah’s PropertyAttribute.