Last active
November 8, 2023 05:32
-
-
Save DCFApixels/231e1c840e12e5710b11262c36d34729 to your computer and use it in GitHub Desktop.
Serializable reference to a UnityObject with an interface for Unity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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