I’ve been using C# for a while now but this is the first time I’ve used Unity. Normal practice is to not expose fields and instead use properties.
When using Unity it seems, for some reason, that to use the Unity editor you need to have these fields as public and not use properties. This feels very weird to me after using properties for everything else I’ve written.
Is there some way of making the editor use properties instead of public fields?
Additionally, I have a lot of code already written that that uses properties. For example, for procedurally generating a map I have a collection of code that I can use independently of the user front end (In this case Unity) and it would be impractical to go through all my code and start changing to use fields instead of properties, as this might break the ability to use my code with another front end.
You can use properties. But you’ll have to make sure that they have backing fields, and that those fields are decorated with the “SerializeField” attribute, e.g:
[SerializeField]
private int foo;
public int Foo
{
get { return foo; }
set { foo = value; }
}
Shaderop, in your example, the Inspector still display the field and not the property. The point of properties is to be able to run test or action when a value change.
By default, the Inspector is unable to draw properties.
There’s a few asset on the asset store that makes the Inspector able to display properties. My own - see my signature for “Advanced Inspector” - will soon be on the store too and also has that ability.
Use a custom C# attribute above the property and then derive your own editor class to check serializedObject for those CustomAttributes displaying and altering the ones you want.
You would have to alter previous code but only to add these attributes
Its also good for added little extras in custom editors for your components
Just be careful… When Unity’s documentation refer to “FindProperty”, it search for a “serialized property”… In .NET term, it’s a field. You will not be able to get a SerializedProperty out of a real .NET property.
If you want to retrieve real properties, you’ll have to do;
foreach(PropertyInfo property in target.GetType().GetProperties())
{
object[] attributes = property.GetCustomAttributes(typeof(MyCustomAttribute), true);
// Do something with it... like drawing it.
}
The net effect for me is that the field will get validated according to the rules in the corresponding properties.
So, to revise my own example:
[SerializeField]
private int foo;
public int Foo
{
get { return foo; }
set
{
// TODO: Add own validation logic here.
// Don't set foo unless you really need to.
if (foo != value)
foo = value;
}
}
#if UNITY_EDITOR
private void OnValidate()
{
Foo = foo;
}
#endif
By the way: That’s one nice looking asset, LightStriker. Looking forward to it showing up i in the Asset Store.
Be very careful with OnValidate. This method in invoked even on prefabs and disabled objects. So if you will change some properties inside its will change your assets unpredictable, and this is not good for source control.
In my project i use something like that:
using System;using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class SerializableProperty : PropertyAttribute{}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(SerializableProperty))]
public class SerializablePropertyAttributeDrawer : PropertyDrawer
{
private const BindingFlags BINDING_FLAGS = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var prevValue = GetInstance(property).ToArray();
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property, label, property.hasChildren);
if (EditorGUI.EndChangeCheck())
{
property.serializedObject.ApplyModifiedProperties();
var instanceValues = GetInstance(property).ToArray();
var instances = GetInstance(property, 1).ToArray();
for(var i = 0; i < instances.Length; i++)
{
var instanceVal = instanceValues[i];
var instance = instances[i];
if (instance == null)
{
Debug.Log("Instance not found");
return;
}
var field = GetField(instance.GetType(), property.name);
field.SetValue(instance, prevValue[i]);
var p = GetProperty(instance.GetType(), property.name.TrimStart('_'));
p.SetValue(instance, instanceVal, new object[0]);
}
}
}
static public FieldInfo GetField(Type type, string name)
{
var field = type.GetField(name, BINDING_FLAGS);
if (field == null)
{
if (type.BaseType != typeof(object))
return GetField(type.BaseType, name);
}
return field;
}
static public PropertyInfo GetProperty(Type type, string name)
{
var field = type.GetProperty(name, BINDING_FLAGS);
if (field == null)
{
if (type.BaseType != typeof(object))
return GetProperty(type.BaseType, name);
}
return field;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label, property.hasChildren);
}
public IEnumerable<object> GetInstance(SerializedProperty property, int ignore = 0)
{
foreach (var targetObject in property.serializedObject.targetObjects)
{
var obj = targetObject;
var path = property.propertyPath;
path = path.Replace(".Array.data", "");
var split = path.Split('.');
var stack = split;
if (stack.Length == 1 ignore == 1)
{
yield return obj;
continue;
}
object v = obj;
try
{
var i = stack.Length;
foreach (var name in stack)
{
if (i-- < ignore)
continue;
if (name.Contains("["))
{
var n = name.Split('[', ']');
v = getField(v, n[0], int.Parse(n[1]));
}
else
v = getField(v, name);
}
}
catch (Exception e)
{
Debug.LogException(e);
}
yield return v;
}
}
static private object getField(object obj, string field, int index = -1)
{
try
{
return index == -1 ? GetField(obj.GetType(), field).GetValue(obj) : (GetField(obj.GetType(), field).GetValue(obj) as IList)[index];
}
catch (Exception)
{
return null;
}
}
}
#endif
Using:
[SerializeField, SerializableProperty] private bool showAttention;
public bool ShowAttention
{
get
{
return showAttention;
}
set
{
if (showAttention == value)
return;
showAttention = value;
OnShowAttentionChanged();
}
}
@shaderop, just a warning, when building for Windows Store Apps the [SerializeField] will throw compiler warnings about how the field will always have the default value null.
The warnings are harmless but it was a little unsettling to have hundreds of warnings so suddenly when I started porting.
[SerializeField] Transform m_someTransform; // Warning here
public Transform someTransform { get { return m_someTransform; } }