#region License and Information /***** * Provides a serializable wrapper that allows you to store a reference to * a serialized UnityEngine.Object that implement a certain interface. * The "Instance" property provides direct easy access to the serialized reference. * This should work with Components as well as ScriptableObjects. * It ships with a convenient property drawer that should streamline the usage. * * Copyright (c) 2023 Bunny83 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * *****/ #endregion License and Information using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; namespace JoeSiu.Common.Serialization.Runtime { [Serializable] public class SerializableInterface where T : class { [SerializeField] private Object _obj; private T _value; public SerializableInterface() { } public SerializableInterface(T value) { SetValue(value); } public T Value { get => GetValue(); set => SetValue(value); } public T GetValue() { if (_value != null && (object)_value == _obj) return _value; if (_obj == null) SetValue(null); else if (_obj is T inst || _obj is GameObject go && go.TryGetComponent(out inst)) _value = inst; else SetValue(null); return _value; } private void SetValue(T value) { _value = value; _obj = _value as Object; } } #if UNITY_EDITOR [CustomPropertyDrawer(typeof(SerializableInterface<>), true)] public class SerializableInterfacePropertyDrawer : PropertyDrawer { private Type _genericType; private readonly List _list = new(); public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { if (_genericType == null) { var fieldType = fieldInfo.FieldType; if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List<>)) // when used in a List<>, grab the actual type from the generic argument of the List fieldType = fieldInfo.FieldType.GetGenericArguments()[0]; else if (fieldType.IsArray) // when used in an array, grab the actual type from the element type. fieldType = fieldType.GetElementType(); var types = fieldType?.GetGenericArguments(); if (types is { Length: 1 }) _genericType = types[0]; } var obj = property.FindPropertyRelative("_obj"); EditorGUI.BeginChangeCheck(); var newObj = EditorGUI.ObjectField(position, label, obj.objectReferenceValue, typeof(Object), true); if (!EditorGUI.EndChangeCheck()) return; if (newObj == null) obj.objectReferenceValue = null; else if (_genericType != null && _genericType.IsAssignableFrom(newObj.GetType())) obj.objectReferenceValue = newObj; else if (newObj is GameObject go) { _list.Clear(); go.GetComponents(_genericType, _list); if (_list.Count == 1) { obj.objectReferenceValue = _list[0]; } else { var m = new GenericMenu(); var n = 1; foreach (var item in _list) m.AddItem(new GUIContent(n++ + " " + item.GetType().Name), false, a => { obj.objectReferenceValue = (Object)a; obj.serializedObject.ApplyModifiedProperties(); }, item); m.ShowAsContext(); } } else { Debug.LogWarning("Dragged object is not compatible with " + _genericType?.Name); } } } #endif }