ExposePropertiesInInspector Generic

From Unify Community Wiki
Jump to: navigation, search

Original author: Venryx (venryx) (variant from Mift (mift) )

Variant author: Tiago Roldão (Tiago Roldão)

Contents

Original Description

The scripts below make the properties (i.e. members having getters/setters) of a class visible to the Unity inspector. This lets you keep the class's fields private, and force all external access to come through the properties (where you can add range limitations or other logic), without losing the ability to still edit those values using the Unity inspector.

Variant Description

This variant of the ExposeProperties system is a generic version, allowing for classes to have exposed properties in the inspector without the need for specific editor scripts for each, having only to inherit from the ExposableMonobehavior Class. Venryx's variant was used as the basis for this, as his changes to only trigger setters when values actually change, a difference that becomes even more important in this implementation.

The attribute

Create a new C# script named "ExposePropertyAttribute" and paste the following code:

C# - ExposePropertyAttribute.cs

using System;
 
[AttributeUsage(AttributeTargets.Property)] public class ExposePropertyAttribute : Attribute {}

The ExposeProperties static helper

Now create another script in the "Assets/Editor" folder named "ExposeProperties" and paste the following code:

C# - ExposeProperties.cs

using UnityEditor;
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
 
public static class ExposeProperties
{
	public static void Expose(PropertyField[] properties)
	{
		var emptyOptions = new GUILayoutOption[0];
		EditorGUILayout.BeginVertical(emptyOptions);
		foreach (PropertyField field in properties)
		{
			EditorGUILayout.BeginHorizontal(emptyOptions);
			if (field.Type == SerializedPropertyType.Integer)
			{
				var oldValue = (int)field.GetValue();
				var newValue = EditorGUILayout.IntField(field.Name, oldValue, emptyOptions);
				if (oldValue != newValue)
					field.SetValue(newValue);
			}
			else if (field.Type == SerializedPropertyType.Float)
			{
				var oldValue = (float)field.GetValue();
				var newValue = EditorGUILayout.FloatField(field.Name, oldValue, emptyOptions);
				if (oldValue != newValue)
					field.SetValue(newValue);
			}
			else if (field.Type == SerializedPropertyType.Boolean)
			{
				var oldValue = (bool)field.GetValue();
				var newValue = EditorGUILayout.Toggle(field.Name, oldValue, emptyOptions);
				if (oldValue != newValue)
					field.SetValue(newValue);
			}
			else if (field.Type == SerializedPropertyType.String)
			{
				var oldValue = (string)field.GetValue();
				var newValue = EditorGUILayout.TextField(field.Name, oldValue, emptyOptions);
				if (oldValue != newValue)
					field.SetValue(newValue);
			}
			else if (field.Type == SerializedPropertyType.Vector2)
			{
				var oldValue = (Vector2)field.GetValue();
				var newValue = EditorGUILayout.Vector2Field(field.Name, oldValue, emptyOptions);
				if (oldValue != newValue)
					field.SetValue(newValue);
			}
			else if (field.Type == SerializedPropertyType.Vector3)
			{
				var oldValue = (Vector3)field.GetValue();
				var newValue = EditorGUILayout.Vector3Field(field.Name, oldValue, emptyOptions);
				if (oldValue != newValue)
					field.SetValue(newValue);
			}
			else if (field.Type == SerializedPropertyType.Enum)
			{
				var oldValue = (Enum)field.GetValue();
				var newValue = EditorGUILayout.EnumPopup(field.Name, oldValue, emptyOptions);
				if (oldValue != newValue)
					field.SetValue(newValue);
			}
			else if (field.Type == SerializedPropertyType.ObjectReference)
			{
				UnityEngine.Object oldValue = (UnityEngine.Object)field.GetValue();
				UnityEngine.Object newValue = EditorGUILayout.ObjectField(field.Name, oldValue, field.Info.PropertyType, emptyOptions);
				if (oldValue != newValue)
					field.SetValue(newValue);
			}
			EditorGUILayout.EndHorizontal();
		}
		EditorGUILayout.EndVertical();
	}
 
	public static PropertyField[] GetProperties(object obj)
	{
		var fields = new List<PropertyField>();
 
		PropertyInfo[] infos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
 
		foreach (PropertyInfo info in infos)
		{
			if (!(info.CanRead && info.CanWrite))
				continue;
 
			object[] attributes = info.GetCustomAttributes(true);
 
			bool isExposed = false;
			foreach (object o in attributes)
				if (o.GetType() == typeof(ExposePropertyAttribute))
			{
				isExposed = true;
				break;
			}
			if (!isExposed)
				continue;
 
			var type = SerializedPropertyType.Integer;
			if (PropertyField.GetPropertyType(info, out type))
			{
				var field = new PropertyField(obj, info, type);
				fields.Add(field);
			}
		}
 
		return fields.ToArray();
	}
}
 
public class PropertyField
{
	object obj;
	PropertyInfo info;
	SerializedPropertyType type;
 
	MethodInfo getter;
	MethodInfo setter;
 
	public PropertyInfo Info
	{
		get { return info; }
	}
 
	public SerializedPropertyType Type
	{
		get { return type; }
	}
 
	public String Name
	{
		get { return ObjectNames.NicifyVariableName(info.Name); }
	}
 
	public PropertyField(object obj, PropertyInfo info, SerializedPropertyType type)
	{
		this.obj = obj;
		this.info = info;
		this.type = type;
 
		getter = this.info.GetGetMethod();
		setter = this.info.GetSetMethod();
	}
 
	public object GetValue() { return getter.Invoke(obj, null); }
	public void SetValue(object value) { setter.Invoke(obj, new[] {value}); }
 
	public static bool GetPropertyType(PropertyInfo info, out SerializedPropertyType propertyType)
	{
		Type type = info.PropertyType;
		propertyType = SerializedPropertyType.Generic;
		if (type == typeof(int))
			propertyType = SerializedPropertyType.Integer;
		else if (type == typeof(float))
			propertyType = SerializedPropertyType.Float;
		else if (type == typeof(bool))
			propertyType = SerializedPropertyType.Boolean;
		else if (type == typeof(string))
			propertyType = SerializedPropertyType.String;
		else if (type == typeof(Vector2))
			propertyType = SerializedPropertyType.Vector2;
		else if (type == typeof(Vector3))
			propertyType = SerializedPropertyType.Vector3;
		else if (type.IsEnum)
			propertyType = SerializedPropertyType.Enum;
		else if (typeof(MonoBehaviour).IsAssignableFrom(type))
			propertyType = SerializedPropertyType.ObjectReference;
		return propertyType != SerializedPropertyType.Generic;
	}
}

The ExposableProperties Class and Editor

Create a script named "ExposableMonobehavior.cs" with the following Class definition:

using UnityEngine;
 
public class ExposableMonobehaviour : MonoBehaviour {}

And another script in the "Assets/Editor" folder named "ExposableMonobehaviorEditor.cs" with the following code:

using UnityEditor;
using UnityEngine;
using System.Collections;
 
[CustomEditor(typeof(ExposableMonobehaviour), true)] public class ExposableMonobehaviourEditor : Editor
{
	ExposableMonobehaviour m_Instance;
	PropertyField[] m_fields;
 
	public virtual void OnEnable()
	{
		m_Instance = target as ExposableMonobehaviour;
		m_fields = ExposeProperties.GetProperties(m_Instance);
	}
 
	public override void OnInspectorGUI()
	{
		if (m_Instance == null)
			return;
		this.DrawDefaultInspector();
		ExposeProperties.Expose(m_fields);
	}
}

Notes

This will allow any class inheriting from ExposableMonobehaviour to be augmented with exposable setters and getters. This isn't applied to Monobehavior for obvious performance issues.

As you probably have seen in the code above, only the following types are currently exposed (though it should be easy enough to implement other ones):

- Integer
- Float
- Boolean
- String
- Vector2
- Vector3
- Enum
- Object Reference (Monobehavior)

Example component

Now create a Class extending ExposableMonobehaviour, and applying the correct attributes. Properties MUST have both getter and setter accessors AND [ExposeProperty] attribute set otherwise the property will not be shown.

In order for the property to maintain values when you hit play, you must add [SerializeField] to the field that the property is changing. Unfortunately, this exposes the field to the Editor (and editing this value does not call the property getter/setter), so you have to explicitly hide it with [HideInInspector].

C# - MyType.cs

using UnityEngine;
 
public class MyType : ExposableMonoBehaviour
{
	[HideInInspector, SerializeField] int m_SomeInt;
	[HideInInspector, SerializeField] float m_SomeFloat;
	[HideInInspector, SerializeField] bool m_SomeBool;
	[HideInInspector, SerializeField] string m_Etc;
        [HideInInspector, SerializeField] Monobehavior m_Obj;
 
	[ExposeProperty] public int SomeInt
	{
		get { return m_SomeInt; }
		set { m_SomeInt = value; }
	}
 
	[ExposeProperty] public float SomeFloat
	{
		get { return m_SomeFloat; }
		set { m_SomeFloat = value; }
	}
 
	[ExposeProperty] public bool SomeBool
	{
		get { return m_SomeBool; }
		set { m_SomeBool = value; }
	}
 
	[ExposeProperty] public string SomeString
	{
		get { return m_Etc; }
		set { m_Etc = value; }
	}
 
        [ExposeProperty] public Monobehavior SomeScript
	{
		get { return m_Obj; }
		set { m_Obj = value; }
	}
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox