先看一下官方对新增特性[SerializeReference]的描述

https://docs.unity3d.com/cn/2019.4/ScriptReference/SerializeReference.html

简而言之: 默认情况下,不支持多态字段,用 [SerializeReference] 修饰字段可指示 Unity“按引用”而非“按值”序列化字段。

借助这个特性可以实现字段的多态,达到改变具体子类的效果。

下面开始具体操作:

假设我们正在制作一个可以在编辑器内通过Inspector生成不同子类的物品类

创建一个代理类,继承MonoBehaviour,这个类放到场景中

public class ItemProxy : MonoBehaviour
 {
     [SerializeReference]
     public ItemObjectBase ItemObject;

     private void Start()
     {
     		///start
     }

     private void OnDestroy()
     {
        ///do something
     }
 }

该类负责持有SerializeReference引用的ItemObjectBase对象,并拥有MonoBehaviour生命周期,稍后对此类制作CustomEditor

创建ItemObjectBase类,不继承MonoBehaviour,这个类是真正的item对象

[Serializable]
public abstract class ItemObjectBase
{
    public string ItemID;
    protected ItemProxy Proxy;

    public void Start(ItemProxy Proxy)
    {
        this.Proxy = Proxy;
        mStart();
    }
    
    /// <summary>
    /// Item版Start方法
    /// </summary>
    protected virtual void mStart() { }

    public virtual void Use() { }

}

该类是我们真正自定义的物品基类,包含对物品的所有使用之类的逻辑。

创建CustomEditor来实现Inspector自定义

[CustomEditor(typeof(ItemProxy))]
public class ItemEditor : Editor
{
    private Type[] _implementations;
    private int _implementationTypeIndex;

    public override void OnInspectorGUI()
    {
        ItemProxy item = target as ItemProxy;
        if (item == null)
            return;

        if (_implementations == null || GUILayout.Button("刷新"))
        {
            //this is probably the most imporant part:
            //find all implementations of INode using System.Reflection.Module
            _implementations = GetImplementations<ItemObjectBase>().Where(impl => !impl.IsSubclassOf(typeof(UnityEngine.Object))).ToArray();
        }

        //select implementation from editor popup
        _implementationTypeIndex = EditorGUILayout.Popup(new GUIContent("选择子类"),
            _implementationTypeIndex, _implementations.Select(impl => impl.FullName).ToArray());

        if (GUILayout.Button("生成子类"))
        {
            bool shouldGenerate = true;
            if (item.ItemObject != null)
            {
                shouldGenerate = EditorUtility.DisplayDialog("确认", "现有对象将会被清除,确定要重新生成吗?", "是", "否");
            }


            if (shouldGenerate)
            {
                //set new value
                item.ItemObject = (ItemObjectBase)Activator.CreateInstance(_implementations[_implementationTypeIndex]);
            }
        }


        if (item.ItemObject == null)
        {
            EditorGUILayout.HelpBox("未生成对象,这个 GameObject 不会起任何作用。", MessageType.Warning);
        }
        else
        {
            EditorGUILayout.HelpBox($"当前类型: {item.ItemObject.GetType()}", );
        }

        base.OnInspectorGUI();
    }

    private static Type[] GetImplementations<T>()
    {
        var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes());

        var interfaceType = typeof(T);
        return types.Where(p => interfaceType.IsAssignableFrom(p) && !p.IsAbstract).ToArray();
    }
}

这个自定义类中,我们会查找所有的派生类,然后提供popup在Inspector中选择一个具体的子类并创建,使得代理类中持有的ItemObjectBase变为具体的子类对象,随后我们可以发现Inspector面板中的属性参数成功变为子类的参数。

创建一个子类TestItem来看看效果:

public class TestItem : ItemObjectBase
    {
        public string Audio;
        
        public override void Use()
        {
            base.Use();
            ///play this audio.
        }
    }

Inspector显示出子类的属性

Unity 特性[SerializeReference]搭配CustomEditor实现可生成具体子类并显示子类内容的Inspector面板_U3D

本篇内容结束,如果有任何问题请留言给我。感谢观看。