Skip to content

Instantly share code, notes, and snippets.

@DCFApixels
Last active November 8, 2023 05:32
Show Gist options
  • Save DCFApixels/231e1c840e12e5710b11262c36d34729 to your computer and use it in GitHub Desktop.
Save DCFApixels/231e1c840e12e5710b11262c36d34729 to your computer and use it in GitHub Desktop.
Serializable reference to a UnityObject with an interface for Unity
// README
//There are two ways to use this script:
//
//[InterfaceReference(typeof(IInterface))]
//public MonoBehaviour someObject;
//
//[InterfaceReference(typeof(IInterface))]
//public InterfaceReference<IInterface> someObject;
//
//The InterfaceReference attribute auto validates the reference, if the object does not contain a suitable interface, it will remove the reference.
//The InterfaceReference<T> structure, a wrapper for unity class Object, the InterfaceReference<T>.Value property is guaranteed to return either the TIterface or Null.
// author: DCFApixels
// license: MIT
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace DCFApixels
{
[Serializable]
public struct InterfaceReference<TIterface> where TIterface : class
{
[SerializeField]
private UnityObject _object;
public UnityObject UnityObject => _object;
public InterfaceReference(TIterface reference)
{
_object = reference as UnityObject;
}
public TIterface Value
{
get
{
_object = InterfaceReferenceUtility.FindReference<TIterface>(_object);
return _object as TIterface;
}
set => _object = value as UnityObject;
}
public static bool operator ==(InterfaceReference<TIterface> a, object b) => a.Value == b;
public static bool operator !=(InterfaceReference<TIterface> a, object b) => a.Value != b;
public static bool operator ==(object a, InterfaceReference<TIterface> b) => a == b.Value;
public static bool operator !=(object a, InterfaceReference<TIterface> b) => a != b.Value;
public static bool operator ==(InterfaceReference<TIterface> a, InterfaceReference<TIterface> b) => a.Value == b.Value;
public static bool operator !=(InterfaceReference<TIterface> a, InterfaceReference<TIterface> b) => a.Value != b.Value;
public static explicit operator TIterface(InterfaceReference<TIterface> value)
{
return value.Value;
}
public static implicit operator InterfaceReference<TIterface>(TIterface value)
{
return new InterfaceReference<TIterface>(value);
}
public override string ToString()
{
return "InterfaceReference " + Value.ToString();
}
public override bool Equals(object obj)
{
return obj is InterfaceReference<TIterface> reference && Value == reference.Value;
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
}
public class InterfaceReferenceAttribute : PropertyAttribute
{
public Type type;
public Type typeFilter = typeof(UnityObject);
public InterfaceReferenceAttribute(Type type)
{
this.type = type;
}
public InterfaceReferenceAttribute(Type type, Type typeFilter)
{
#if DEBUG
if (typeFilter.IsSubclassOf(typeof(UnityObject)) == false)
throw new ArgumentException("typeFilter.IsSubclassOf(typeof(UnityObject)) == false");
#endif
this.type = type;
this.typeFilter = typeFilter;
}
}
public static class InterfaceReferenceUtility
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ConvertAs<T>(UnityObject obj) where T : class
{
if (obj is T result)
return result;
if (obj is Component component)
{
return component.GetComponent<T>();
}
if (obj is GameObject gameObject)
{
return gameObject.GetComponent<T>();
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnityObject FindReference<T>(UnityObject obj) where T : class
{
if (obj == null)
return null;
if (obj is T)
return obj;
if (obj is Component component)
return component.GetComponent(typeof(T));
if (obj is GameObject gameObject)
return gameObject.GetComponent(typeof(T));
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnityObject FindReference(UnityObject obj, Type type, out bool isCorrect)
{
isCorrect = true;
if (obj == null)
return null;
Type objType = obj.GetType();
if (objType == type || type.IsAssignableFrom(objType) || objType.IsSubclassOf(type))
return obj;
if (obj is Component component)
{
isCorrect = component.TryGetComponent(type, out Component targetComponent);
return targetComponent;
}
if (obj is GameObject gameObject)
{
isCorrect = gameObject.TryGetComponent(type, out Component targetComponent);
return targetComponent;
}
isCorrect = false;
return null;
}
}
}
#if UNITY_EDITOR
namespace DCFApixels.Editors
{
using UnityEditor;
[CustomPropertyDrawer(typeof(InterfaceReferenceAttribute))]
public class IterfaceReferenceDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Rect topPosition = position;
topPosition.height = EditorGUIUtility.singleLineHeight;
Rect suffixLabelPosition = position;
suffixLabelPosition.yMin = topPosition.yMax;
suffixLabelPosition.xMin += EditorGUIUtility.labelWidth + EditorGUIUtility.standardVerticalSpacing;
Rect suffixPanelPosition = suffixLabelPosition;
suffixPanelPosition.yMin = topPosition.yMin;
SerializedProperty targetProp;
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
targetProp = property;
}
else
{
targetProp = property.FindPropertyRelative("_object");
}
EditorGUI.BeginProperty(position, label, property);
GUI.Label(suffixPanelPosition, "", EditorStyles.helpBox);
var targetAttribute = attribute as InterfaceReferenceAttribute;
if (targetProp == null)
{
GUI.Label(topPosition, "ERROR");
goto exit;
}
GUIStyle stylLabel = targetProp.prefabOverride ? EditorStyles.boldLabel : EditorStyles.label;
Rect topLabelPosition = topPosition;
topLabelPosition.width = EditorGUIUtility.labelWidth;
Rect topFieldPosition = topPosition;
topFieldPosition.xMin = topLabelPosition.xMax + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.BeginChangeCheck();
EditorGUI.LabelField(topLabelPosition, label, style: stylLabel);
UnityObject unityObject = EditorGUI.ObjectField(topFieldPosition, targetProp.objectReferenceValue, targetAttribute.typeFilter, true);
if (EditorGUI.EndChangeCheck())
{
UnityObject target = InterfaceReferenceUtility.FindReference(unityObject, targetAttribute.type, out bool isCorrect);
if (target != null || isCorrect == true)
{
targetProp.objectReferenceValue = target;
property.serializedObject.ApplyModifiedProperties();
}
}
exit:
EditorGUI.EndProperty();
GUI.Label(suffixLabelPosition, targetAttribute.type.Name, EditorStyles.miniLabel);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight + EditorGUIUtility.singleLineHeight * 0.74f;
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment