ExposePropertiesInInspector SetOnlyWhenChanged

From Unify Community Wiki
Jump to: navigation, search

Original author: Mift (mift)

Variant author: Venryx (venryx)

Note: An updated version of this script package (which allows for exposing properties without dedicated editor scripts) can be found here: ExposePropertiesInInspector_Generic

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 only calls the property's setter method with the new value, when the new value has become different than the old value. This prevents problems in some projects where the setters contain extra logic that could mess things up if called every frame (which happens when the object is open in the inspector, and in play mode).

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);
			}
			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 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;
		return propertyType != SerializedPropertyType.Generic;
	}
}

Note

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

Example component

Now create your type, i.e. MyType. 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 : MonoBehaviour
{
	[HideInInspector, SerializeField] int m_SomeInt;
	[HideInInspector, SerializeField] float m_SomeFloat;
	[HideInInspector, SerializeField] bool m_SomeBool;
	[HideInInspector, SerializeField] string m_Etc;
 
	[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; }
	}
}

The custom editor

Finally, create a custom editor script in "Assets/Editor" for the type whose properties you want to see exposed. For example:

C# - MyTypeEditor.cs

using UnityEditor;
using UnityEngine;
using System.Collections;
 
[CustomEditor(typeof(MyType))] public class MyTypeEditor : Editor
{
	MyType m_Instance;
	PropertyField[] m_fields;
 
	public void OnEnable()
	{
		m_Instance = target as MyType;
		m_fields = ExposeProperties.GetProperties(m_Instance);
	}
 
	public override void OnInspectorGUI()
	{
		if (m_Instance == null)
			return;
		this.DrawDefaultInspector();
		ExposeProperties.Expose(m_fields);
	}
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox