c# - 删除 BoxCollider 时的 Unity3D MissingReferenceException

标签 c# exception unity3d editor components

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

我创建了一个主游戏对象,并将我的脚本 BoxStairs 附加到它。此脚本附加到相同的 GameObject BoxCollider

在我的 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 的代码,唯一的地方是创建它的地方,当创建楼梯时,一旦楼梯发生变化就会触发检查员。

在类里面:

[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 按钮来触发此代码,通过右键单击检查器“删除组件”选项上的组件,错误没有出现并且之后,点击 finalize stairs 就没有问题了。

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

我还检查了调用 AddSelectionBox 方法的创建过程是否在单击完成按钮时没有发生。

所以,请我帮忙解决这个问题。这是开发分支的链接https://github.com/JAFS6/BoxStairsTool/tree/feature/BoxStairsTool , 在这里你会发现上面提到的方法 FinalizeStairs 的代码只删除了 BoxStairs 脚本并且在那一刻它没有抛出任何错误。

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

编辑: 一个最小的、完整的和可验证的例子:

Assets /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;
        }

    }
}

Assets \编辑器\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: 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)

当代码尝试访问已被销毁的 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;
}

我们无法知 Prop 体的 590 行是哪一行。但是,我们可以知道 MissingReferenceException 在哪里发生:

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

@object 是从 Editor.targets 分配的这是所有被检查对象的数组。在您的情况下,此数组中应该只有一个目标对象 - BoxCollider 组件。

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

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

解决方案

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

解决方案代码

采用方案一后,在Win10的Unity3D 5.4上NRE没有了。

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;
            }
        }
    }
}

关于c# - 删除 BoxCollider 时的 Unity3D MissingReferenceException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40801369/

相关文章:

c# - 将 html 表格/图表元素转换为图像

java - org.openqa.selenium.StaleElementReferenceException : stale element reference: element is not attached to the page document

c++ - Bad_alloc 问题

c++ - 尝试在 C++ 中捕获二重奏

c# - Unity3D C# 无法从类创建对象

c# - app.config 或 web.config 中的配置与 WCF 中的代码之间的关系

c# - IE9 中带有 Repeater Control 的 Ghost Cell

c# - AppDomain 间通信问题

c# - 如何在 Unity3d、C# 中按下按键时播放 AnimationClip?

c# - 不序列化字段但显示在检查器中