我正在为Unity3D https://github.com/JAFS6/BoxStairsTool开发一个开源编辑器工具,并且正在编写CustomEditor。

我创建一个主GameObject,并将我的脚本BoxStairs附加到它。此脚本将BoxCollider附加到同一GameObject。

在我的CustomEditor代码上,我有一个方法负责在最终完成编辑之前删除所连接的两个组件。

这是代码:

    private void FinalizeStairs ()
    {
        Undo.SetCurrentGroupName("Finalize stairs");
        BoxStairs script = (BoxStairs)target;
        GameObject go = script.gameObject;
        BoxCollider bc = go.GetComponent<BoxCollider>();

        if (bc != null)
        {
            Undo.DestroyObjectImmediate(bc);
        }
        Undo.DestroyObjectImmediate(target);
    }


按下按钮后,在方法OnInspectorGUI上调用此方法

public override void OnInspectorGUI ()
{
    ...
    if (GUILayout.Button("Finalize stairs"))
    {
        FinalizeStairs();
    }
}


两种方法都在课堂上

[CustomEditor(typeof(BoxStairs))]
public sealed class BoxStairsEditor : Editor


它实际上删除了两个组件,但是,一旦删除BoxCollider,将出现以下错误:

MissingReferenceException: The object of type 'BoxCollider' has been
destroyed but you are still trying to access it.


我通过查看跟踪来查找错误发生的位置:

Your script should either check if it is null or you should not destroy the object.
UnityEditor.Editor.IsEnabled () (at C:/buildslave/unity/build/Editor/Mono/Inspector/Editor.cs:590)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor editor, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1154)
UnityEditor.InspectorWindow.DrawEditors (UnityEditor.Editor[] editors) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1030)
UnityEditor.InspectorWindow.OnGUI () (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:352)
System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:222)


但是我的脚本都没有出现。

我一直在查看引用BoxCollider的代码,唯一的地方是创建BoxCollider的地方,当创建楼梯时,一旦检查员发生更改,就会触发楼梯。

它在班上:

[ExecuteInEditMode]
[SelectionBase]
public sealed class BoxStairs : MonoBehaviour


这是代码:

    /*
     * This method creates a disabled BoxCollider which marks the volume defined by
     * StairsWidth, StairsHeight, StairsDepth.
     */
    private void AddSelectionBox ()
    {
        BoxCollider VolumeBox = Root.GetComponent<BoxCollider>();

        if (VolumeBox == null)
        {
            VolumeBox = Root.AddComponent<BoxCollider>();
        }

        if (Pivot == PivotType.Downstairs)
        {
            VolumeBox.center = new Vector3(0, StairsHeight * 0.5f, StairsDepth * 0.5f);
        }
        else
        {
            VolumeBox.center = new Vector3(0, -StairsHeight * 0.5f, -StairsDepth * 0.5f);
        }

        VolumeBox.size = new Vector3(StairsWidth, StairsHeight, StairsDepth);

        VolumeBox.enabled = false;
    }


我试图注释此方法的主体,以允许删除没有此“引用”的BoxCollider,并且错误仍然出现,因此,我想此方法不是问题。

另外,我手动删除了BoxCollider,而没有单击Finalize按钮来触发此代码,而是通过右键单击检查器上的“ Remove Component”选项上的组件,并且不会出现错误,然后单击Finalize楼梯就没有问题了。出现。

正如@JoeBlow在评论中提到的那样,我检查了FinalizeStairs方法仅被调用一次。

我还检查了单击Addizeion按钮时是否没有通过调用AddSelectionBox方法进行创建的过程。

所以,请我帮忙。这是开发分支https://github.com/JAFS6/BoxStairsTool/tree/feature/BoxStairsTool的链接,在这里您会发现上述方法FinalizeStairs具有仅删除BoxStairs脚本的代码,并且此刻不引发任何错误。

关于此的任何想法或建议将非常有帮助。提前致谢。

编辑:
最小,完整和可验证的示例:

资产/BoxStairs.cs

using UnityEngine;
using System.Collections.Generic;

namespace BoxStairsTool
{
    [ExecuteInEditMode]
    [SelectionBase]
    public sealed class BoxStairs : MonoBehaviour
    {
        private GameObject Root;

        private void Start ()
        {
            Root = this.gameObject;
            this.AddSelectionBox();
        }

        private void AddSelectionBox()
        {
            BoxCollider VolumeBox = Root.GetComponent<BoxCollider>();

            if (VolumeBox == null)
            {
                VolumeBox = Root.AddComponent<BoxCollider>();
            }

            VolumeBox.size = new Vector3(20, 20, 20);

            VolumeBox.enabled = false;
        }

    }
}


Asset \ Editor \ BoxStairsEditor.cs

using UnityEngine;
using UnityEditor;

namespace BoxStairsTool
{
    [CustomEditor(typeof(BoxStairs))]
    public sealed class BoxStairsEditor : Editor
    {
        private const string DefaultName = "BoxStairs";

        [MenuItem("GameObject/3D Object/BoxStairs")]
        private static void CreateBoxStairsGO ()
        {
            GameObject BoxStairs = new GameObject(DefaultName);
            BoxStairs.AddComponent<BoxStairs>();

            if (Selection.transforms.Length == 1)
            {
                BoxStairs.transform.SetParent(Selection.transforms[0]);
                BoxStairs.transform.localPosition = new Vector3(0,0,0);
            }

            Selection.activeGameObject = BoxStairs;
            Undo.RegisterCreatedObjectUndo(BoxStairs, "Create BoxStairs");
        }

        public override void OnInspectorGUI ()
        {
            if (GUILayout.Button("Finalize stairs"))
            {
                FinalizeStairs();
            }
        }

        private void FinalizeStairs ()
        {
            Undo.SetCurrentGroupName("Finalize stairs");
            BoxStairs script = (BoxStairs)target;
            GameObject go = script.gameObject;
            BoxCollider bc = go.GetComponent<BoxCollider>();

            if (bc != null)
            {
                Undo.DestroyObjectImmediate(bc);
            }
            Undo.DestroyObjectImmediate(target);
        }
    }
}

最佳答案

分析

我是一名程序员,所以我只是调试才能找到问题(在我看来:D)。


  MissingReferenceException:类型为'BoxCollider'的对象已被破坏,但您仍在尝试访问它。
  您的脚本应检查其是否为null或不破坏该对象。
  UnityEditor.Editor.IsEnabled()(在C:/buildslave/unity/build/Editor/Mono/Inspector/Editor.cs:590)


当代码在销毁Unity3D.Object之后尝试访问它时,会发生MissingReferenceException。

让我们看一下UnityEditor.Editor.IsEnabled()的反编译代码。

internal virtual bool IsEnabled()
{
    UnityEngine.Object[] targets = this.targets;
    for (int i = 0; i < targets.Length; i++)
    {
        UnityEngine.Object @object = targets[i];
        if ((@object.hideFlags & HideFlags.NotEditable) != HideFlags.None)
        {
            return false;
        }
        if (EditorUtility.IsPersistent(@object) && !AssetDatabase.IsOpenForEdit(@object))
        {
            return false;
        }
    }
    return true;
}


我们将无法知道哪一行是特定行590。但是,我们可以知道MissingReferenceException会在哪里发生:

//    ↓↓↓↓↓↓
if ((@object.hideFlags & HideFlags.NotEditable) != HideFlags.None)


@object是从Editor.targets分配的,而EditorApplication.update是要检查的所有对象的数组。根据您的情况,该数组中应该只有一个目标对象-BoxCollider组件。

总之,在targets[0]组件上调用Undo.DestroyObjectImmediate后,检查器无法访问目标对象(我的意思是BoxCollider)。

如果深入研究检查器(UnityEditor.InspectorWindow)的反编译代码,您会看到按覆盖范围的OnInspectorGUI函数在UnityEditor.InspectorWindow.DrawEditors中按每个编辑器顺序调用,包括BoxCollider的内部编辑器和自定义编辑器BoxStairsEditorBoxStairs

解决方案


请勿破坏检查器在OnInspectorGUI中显示的组件。
也许您可以将委托实例添加到来代替。这样,删除操作不会破坏BoxCollider的编辑器/检查器GUI。
在销毁创建的组件BoxCollider之前,将其移到高于BoxStairs组件的位置。这可能有效,但是我不确定其他内部编辑器是否会访问BoxCollider。使用UnityEditorInternal.ComponentUtility.MoveComponentUp时,此解决方案不起作用。但是,如果用户手动向上移动BoxCollider组件,则它无需更改任何代码即可工作。


解决方案代码

使用解决方案1后,NRE在Win10的Unity3D 5.4上消失了。

using UnityEngine;
using UnityEditor;

namespace BoxStairsTool
{
    [CustomEditor(typeof(BoxStairs))]
    public sealed class BoxStairsEditor : Editor
    {
        private const string DefaultName = "BoxStairs";

        [MenuItem("GameObject/3D Object/BoxStairs")]
        private static void CreateBoxStairsGO ()
        {
            GameObject BoxStairs = new GameObject(DefaultName);
            BoxStairs.AddComponent<BoxStairs>();

            if (Selection.transforms.Length == 1)
            {
                BoxStairs.transform.SetParent(Selection.transforms[0]);
                BoxStairs.transform.localPosition = new Vector3(0,0,0);
            }

            Selection.activeGameObject = BoxStairs;
            Undo.RegisterCreatedObjectUndo(BoxStairs, "Create BoxStairs");
        }

        private void OnEnable ()
        {
            EditorApplication.update -= Update;
            EditorApplication.update += Update;
        }

        public override void OnInspectorGUI ()
        {
            if (GUILayout.Button("Finalize stairs"))
            {
                needFinalize = true;
            }
        }

        private void FinalizeStairs ()
        {
            Undo.SetCurrentGroupName("Finalize stairs");
            BoxStairs script = (BoxStairs)target;
            GameObject go = script.gameObject;
            BoxCollider bc = go.GetComponent<BoxCollider>();

            if (bc != null)
            {
                Undo.DestroyObjectImmediate(bc);
            }
            Undo.DestroyObjectImmediate(target);
        }

        bool needFinalize;
        void Update()
        {
            if(needFinalize)
            {
                FinalizeStairs();
                needFinalize = false;
                EditorApplication.update -= Update;
            }
        }
    }
}

09-10 12:32