接口在编辑器中的拓展
前言
经常会希望在Unity编辑器中有这样一种功能的实现,可以直接引用(序列化)一个实现了指定接口的组件。
面向接口可以大大降低代码的耦合,实现依赖反转,同时更加灵活,后续的修改与扩展都更加方便。
但官方就是不做,可以从这个帖子中看到官方的态度。
所以下面就列举一下自己实现这个功能的各种方法。
问题描述
假使有这样一个接口
public interface ICanGetBool
{
bool GetBool();
}
然后有若干MonoBehaviour
public class TestMono : MonoBehaviour
{
public ICanGetBool canGetBool;
}
public class CanGetBoolMono : MonoBehaviour,ICanGetBool
{
public bool GetBool()
{
return true;
}
}
public class CanGetBoolFalseMono : MonoBehaviour,ICanGetBool
{
public bool GetBool()
{
return false;
}
}
目的是在编辑器中将一CanGetBoolMono
或者CanGetBoolFalseMono
的实例拖拽到TestMono
的canGetBool
属性上去。
OnValidate判断
思路是用一个通用的比如Component属性来接收,然后在OnValidate中判断是否是对应的接口,不是就重置为null。然后在使用的时候要手动转换一次。
public class TestMono : MonoBehaviour
{
public ICanGetBool canGetBool;
public Component canGetBoolComponent;
public void Start()
{
canGetBool = canGetBoolComponent as ICanGetBool;
Debug.Log(canGetBool.GetBool());
}
private void OnValidate()
{
if (canGetBoolComponent is ICanGetBool component)
{
canGetBool = component;
Debug.Log(canGetBool.GetBool());
}
else
{
Debug.LogError("interface not implemented");
canGetBoolComponent = null;
}
}
}
这种简单快捷,但缺点是要为每个类似需求的MonoBehaviour都写对应的OnValidate,还是不方便。
构造容器
也是大同小异,区别在于利用泛型生成一个新的类,用新的类型作为容器来存储真正的对象。也是可以放入任意类型,在PropertyDrawer中验证类型是否符合。
摘自
[Serializable]
public abstract class ATypedContainer
{
[CanBeNull]
[SerializeField]
protected MonoBehaviour obj = default;
public void Validate()
{
OnValidate();
}
protected abstract void OnValidate();
}
[Serializable]
public class TypedContainer<T> : ATypedContainer where T : class
{
[CanBeNull]
public T Value => obj as T;
protected override void OnValidate()
{
if (Value == null)
obj = null;
}
}
[CustomPropertyDrawer(typeof(ATypedContainer), true)]
public class TypedContainerClassDrawer : PropertyDrawer
{
const string OBJ_FIELD_NAME = "obj";
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var tc = fieldInfo.GetValue(property.serializedObject.targetObject) as ATypedContainer;
tc?.Validate();
var objProp = property.FindPropertyRelative(OBJ_FIELD_NAME);
if (objProp == null)
throw new InvalidCastException($"Can't find {OBJ_FIELD_NAME} field in {property.type}");
EditorGUI.PropertyField(position, objProp, label);
}
}
[Serializable]
public class CanGetBoolContainer: TypedContainer<ICanGetBool> {}
这种省去了在目标MonoBehaviour中写模板代码,可以为每个接口实现一个容器类,也可以在使用时直接声明TypedContainer<ICanGetBool>
。
SerializableInterface
最后介绍一个项目Unity3D-SerializableInterface
不仅实现了第二种的容器类的方式,还支持从资源文件,场景组件,甚至仅引用一个接口,一共三种方式来序列化接口。
而且提供了untiyPackage的安装方式。强烈推荐。
接口在编辑器中的拓展
https://www.kuanmi.top/2023/06/14/InterfaceInEditor/