如何使用自定义检查器公开要编辑的多态对象数组?

时间:2015-02-09 22:50:52

标签: c# arrays unity3d polymorphism inspector

序言

我试图找到一种同步动画的方法& sfx(可能是后来的粒子fx,相机移动等)要求我们的艺术家在我们的关卡中放置艺术时使用。我想出的方法是让一个具有基类类型FXEvent的数组的触发器,其子类类型为AnimEventSFXEvent。 C#通常可以很好地处理多态性,但Unity的Inspector却没有。我担心这是我问题的根源。

4年前我解决了这个问题,所以我知道可以做到;但是,唉,我再也无法访问该代码了。我这次尝试使用ScriptableObjectSerilizableObject来构建解决方案,并且我有一些几乎的工作。

我的问题如下:

我将FXTriggerTimer MonoBehavior(继承自FXTriggerBase)应用于测试对象,然后向触发器添加AnimEventSFXEvent。然后我保存场景,并重新加载场景,我的测试对象有一个由两个空元素组成的数组(显然)不会在检查器中绘制。 但是,如果我编辑脚本(例如点击空间并保存),脚本刷新将神奇地重新填充正确的两个项目。所以我知道所有的部件都在引擎盖下,它们只需要在场景负载下恢复。

注意:我没有使用ScriptableObject和SerializableObject结合我当前的解决方案。对我来说感觉有点复杂,但它让我最接近真正的解决方案。

以下代码。

另外,there's an example scene downloadable here,如果有人想以这种方式看待问题。

我试图开始工作的基本多态性内容:

//Scripts/FXTriggerBase.cs
using UnityEngine;
using System.Collections;

public enum FXEventType {kNone, kAnim,  kSFX,}

/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class FXEvent: UnityEngine.ScriptableObject
{
    [SerializeField]
    public FXEventType  type;

    [SerializeField]
    public GameObject   go;

    virtual public void Fire()  {   }
};

/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class AnimEvent : FXEvent
{       
    [SerializeField]
    public string       animName = "";

    public void Fire()  {go.animation.Play(animName);   }
}

/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class SFXEvent: FXEvent
{
    [SerializeField]
    public AudioClip    clip;

    public void Fire()  {go.audio.PlayOneShot(clip);    }
}

/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[AddComponentMenu("")]  // prevents users from using this directly
public class FXTriggerBase : MonoBehaviour 
{
    [SerializeField]
    public FXEvent[] events = new FXEvent[]{};

    public void Fire()
    {
        for( int e = 0; e < events.Length; e++)
        {
            events[e].go = gameObject;
            events[e].Fire();
        }   
    }
}

使用它的示例MonoBehavior:

//Scripts/FXTriggerTimer.cs
using UnityEngine;
using System.Collections;

/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
public class FXTriggerTimer: FXTriggerBase
{
    public float timeTrigger;
    float timeCurrent;

    public void Start()
    {
        timeCurrent = 0;
    }

    public void Update()
    {
        float frame = 1.0f/60.0f;
        if(timeCurrent >= 0)
            timeCurrent += frame;

        if(timeCurrent > timeTrigger)
        {
            Fire();
            timeCurrent = -1.0f;
        }
    }
}

使用以下命令编辑自定义检查器实用程序类:

//Editor/FXTriggerBaseUtil.cs
using UnityEngine;
using System.Collections;
using UnityEditor;

/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
public class FXEditorUtil
{
    SerializedObject[] serial;  
    public bool dirtied = false;

    /*-------------------------------------------------------------------------
    *///-----------------------------------------------------------------------
    public void OnSubGUI(this FXEvent fx)
    {
        //having trouble getting polymorphism to work right in the inspector,
        // so this is what works.
        if(fx.type == FXEventType.kAnim)
            OnSubGUI((AnimEvent)fx);
        else if(fx.type == FXEventType.kSFX)
            OnSubGUI((SFXEvent)fx);
    }

    /*-------------------------------------------------------------------------
    *///-----------------------------------------------------------------------
    public void OnSubGUI(this AnimEvent anim)
    {       
        string newAnimName = GUILayout.TextField(anim.animName);
        if(newAnimName != anim.animName)
        {
            anim.animName = newAnimName;
            dirtied = true;
        }
    }

    /*-------------------------------------------------------------------------
    *///-----------------------------------------------------------------------
    public void OnSubGUI(this SFXEvent sfx)
    {
        AudioClip newSFX = (AudioClip)EditorGUILayout.ObjectField(sfx.clip, typeof(AudioClip));
        if(newSFX != sfx.clip)
        {
            sfx.clip =newSFX;
            dirtied = true;
        }
    }

    /*-------------------------------------------------------------------------
    *///----------------------------------------------------------------------- 
    public FXEvent OnGUI(FXEvent fx)
    {
        if(fx == null)
            return fx;

        GUILayout.BeginHorizontal();
        FXEvent ret = null;

        //if the enum changes, create new FXEvent sub type corresponding to the
        // new enum, and return it
        FXEventType newType = (FXEventType) EditorGUILayout.EnumPopup(fx.type, GUILayout.Width(60));
        if(newType != fx.type)
        {
            switch(newType)
            {
                case FXEventType.kNone:
                    ret = ScriptableObject.CreateInstance<FXEvent>();
                    break;

                case FXEventType.kSFX:
                    ret = ScriptableObject.CreateInstance<SFXEvent>();
                    break;

                case FXEventType.kAnim:
                    ret = ScriptableObject.CreateInstance<AnimEvent>();
                    break;

                default:
                    Debug.LogError("FXEvent.OnGUI(): Magic Error");
                    break;              
            }

            ret.type = newType;
            dirtied = true;
        }

        //handle element GUI, if that applies
        if(fx.type != FXEventType.kNone)
        {
            OnSubGUI(fx);
        }
        else
            GUILayout.Label("");

        GUILayout.EndHorizontal();
        return ret;
    }

    /*-------------------------------------------------------------------------
    *///-----------------------------------------------------------------------
    public void OnGUI(FXTriggerBase trigger)
    {
        FXTriggerBase triggerBase = trigger as FXTriggerBase;
        if(triggerBase == null)
        {
            Debug.LogError("FXTriggerBaseInspector.OnGUI() : null item to inspect?");
            return;
        }

        //build an array of serialized versions of our array elements, so that they
        // save out properly
        int numEvents = triggerBase.events.Length;
        serial = new SerializedObject[numEvents];
        for(int e = 0; e < numEvents; e++)
        {
            serial[e] = new SerializedObject(triggerBase.events[e]);
        }

        dirtied = false;

        //Do inspector GUI of our array elements
        int ret = ArrayGUI("events", trigger.events, trigger.gameObject);

        //handle deletion or addition of elements
        if(ret >= 0 && ret < trigger.events.Length)
            ArrayHandleDelete(ref trigger.events, ret);
        else if (ret == trigger.events.Length)
            ArrayHandleAdd(ref trigger.events);

        //if any changes have been registered, save our serialized versions out
        if(dirtied)
        {
            numEvents = serial.Length;
            for(int e = 0; e < numEvents; e++)
            {
                serial[e].ApplyModifiedProperties();
            }

            EditorUtility.SetDirty(trigger);
        }
    }

    /*-------------------------------------------------------------------------
    *///-----------------------------------------------------------------------
    int ArrayGUI(string label, FXEvent[] arr, GameObject go)
    {
        int ret = -1;
        Color defColor = GUI.color;
        GUILayout.BeginVertical(GUILayout.Width(250));  
        {
            GUILayout.Label(label);

            int numObjects = arr.Length;
            for (int o = 0; o < numObjects; o++)
            {
                GUILayout.BeginHorizontal();
                {
                    //Draw a red '-' button that will remove this element
                    GUI.color = Color.red;
                    bool hit = GUILayout.Button("-", GUILayout.Width(20));
                    if (hit)
                        ret = o;

                    GUI.color = defColor;

                    //spacer
                    GUILayout.Label("", GUILayout.Width(10));

                    //draw custom GUI for each of the polymorphic fx event types
                    FXEvent fx = arr[o];
                    FXEvent newFX = OnGUI(fx);
                    if(newFX != null)
                    {
                        arr[o] = newFX;
                        dirtied = true;

                        serial[o] = new SerializedObject(arr[o]);
                        ScriptableObject.DestroyImmediate(fx);                      
                    }
                }
                GUILayout.EndHorizontal();
            }

            //finally, at the bottom draw a blue plus button that will 
            GUI.color = Color.cyan;
            bool add = GUILayout.Button("+", GUILayout.Width(20));
            if (add)
                ret = arr.Length;

            GUI.color = defColor;
        }
        GUILayout.EndVertical();
        return ret;
    }

    /*-------------------------------------------------------------------------
    *///-----------------------------------------------------------------------
    void ArrayHandleDelete(ref FXEvent[]  arr, int index)
    {   
        FXEvent fx = arr[index];
        ScriptableObject.DestroyImmediate(fx);

        //copy over the element we are deleting, and continue compacting the array
        for (int i = index; i < arr.Length - 1; i++)
        {
            arr[i] = arr[i + 1];
            serial[i] = serial[i+1];
        }

        //remove last element to finalize deletion
        System.Array.Resize(ref arr, arr.Length - 1);
        System.Array.Resize(ref serial, serial.Length - 1);
        dirtied = true;
    }

    /*-------------------------------------------------------------------------
    *///-----------------------------------------------------------------------
    void ArrayHandleAdd(ref FXEvent[]  arr)
    {
        int size = arr.Length;

        //add new element to the end
        System.Array.Resize(ref arr, size + 1);
        System.Array.Resize(ref serial, size + 1);

        arr[size] = ScriptableObject.CreateInstance<FXEvent>();
        serial[size] = new SerializedObject(arr[size]);

        dirtied = true;
    }
}

并使用该示例的自定义检查器示例:

//Editor/FXTriggerTimerInspector.cs
using UnityEngine;
using System.Collections;
using UnityEditor;

/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[CustomEditor(typeof(FXTriggerTimer))]
public class FXTriggerTimerInspector : Editor
{
    FXEditorUtil util;

    public override void OnInspectorGUI()
    {
        FXTriggerTimer trigger = (FXTriggerTimer)target;
        if(util == null)
        {
            util = new FXEditorUtil();
        }

        util.OnGUI(trigger);

        GUILayout.BeginHorizontal();
        GUILayout.Label("Timer");
        trigger.timeTrigger = EditorGUILayout.FloatField(trigger.timeTrigger);
        GUILayout.EndHorizontal();


        if(util.dirtied)
        {
            EditorUtility.SetDirty(target);
            //serializedObject.ApplyModifiedProperties();
        }
    }
}

0 个答案:

没有答案