using System; using UnityEngine; namespace Jovian.InspectorTools { public class InterfaceComponentAttribute : PropertyAttribute { public readonly Type interfaceType; public readonly bool allowMismatch; public InterfaceComponentAttribute(Type interfaceType, bool allowMismatch = false) { this.interfaceType = interfaceType; this.allowMismatch = allowMismatch; } } } #if UNITY_EDITOR namespace Jovian.InspectorTools.Internal { using UnityEditor; [CustomPropertyDrawer(typeof(InterfaceComponentAttribute))] public class InterfaceComponentAttributeDrawer : PropertyDrawer { private const float MESSAGE_HEIGHT = 40f; private const float FIELD_HEIGHT = 18f; private enum ValueImplementation { Null, Match, Mismatch } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { ValueImplementation valueImplementation = GetValueInterfaceImplementation(property, ((InterfaceComponentAttribute)attribute).interfaceType); return base.GetPropertyHeight(property, label) + (valueImplementation != ValueImplementation.Match ? MESSAGE_HEIGHT : 0f); } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { InterfaceComponentAttribute interfaceAttribute = (InterfaceComponentAttribute)attribute; Type interfaceType = interfaceAttribute.interfaceType; bool allowMismatch = interfaceAttribute.allowMismatch; UnityEngine.Object value = property.objectReferenceValue; if(value is GameObject gameObject) { value = gameObject.GetComponent(interfaceType) as Component; if(value != null) { property.objectReferenceValue = value; property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); } } else if(value is Transform transform) { value = transform.GetComponent(interfaceType) as Component; if(value != null) { property.objectReferenceValue = value; property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); } } ValueImplementation valueImplementation = GetValueInterfaceImplementation(property, interfaceType); string message = null; if(valueImplementation == ValueImplementation.Null) { message = $"Component must implement '{interfaceType.Name}'"; } else if(valueImplementation == ValueImplementation.Mismatch) { message = $"Component does not implement '{interfaceType.Name}'"; if(allowMismatch == false) { property.objectReferenceValue = null; property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); } } if(string.IsNullOrEmpty(message) == false) { Rect boxPosition = new Rect(position.x, position.y, position.width, MESSAGE_HEIGHT - 2f); EditorGUI.HelpBox(boxPosition, message, valueImplementation == ValueImplementation.Mismatch ? MessageType.Warning : MessageType.Info); } Rect fieldPosition = new Rect(position.x, position.yMax - FIELD_HEIGHT, position.width, FIELD_HEIGHT); EditorGUI.PropertyField(fieldPosition, property, label); } private ValueImplementation GetValueInterfaceImplementation(SerializedProperty property, Type interfaceType) { UnityEngine.Object value = property.objectReferenceValue; if(value == null) { return ValueImplementation.Null; } else if(interfaceType.IsAssignableFrom(property.objectReferenceValue.GetType())) { return ValueImplementation.Match; } return ValueImplementation.Mismatch; } } } #endif