简体   繁体   English

删除BoxCollider时Unity3D MissingReferenceException

[英]Unity3D MissingReferenceException when removing BoxCollider

I'm developing an open source editor tool for Unity3D https://github.com/JAFS6/BoxStairsTool and I'm writting a CustomEditor. 我正在为Unity3D开发一个开源编辑器工具https://github.com/JAFS6/BoxStairsTool ,我正在编写一个CustomEditor。

I create a main GameObject and I attach my script BoxStairs to it. 我创建了一个主GameObject ,并将我的脚本BoxStairs附加到它上面。 This script attachs to the same GameObject a BoxCollider . 这个脚本attachs相同的游戏对象一个BoxCollider。

On my CustomEditor code, I have a method which is in charge of removing both two components attached before to finalize editing. 在我的CustomEditor代码上,我有一个方法,负责删除之前附加的两个组件,以完成编辑。

This is the code: 这是代码:

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

This method is called on the method OnInspectorGUI after a button has been pressed 按下按钮后,在方法OnInspectorGUI上调用此方法

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

Both two methods are on the class 这两种方法都在课堂上

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

It actually removes the two components but, once the BoxCollider has been removed the following error appears: 它实际上删除了两个组件,但是一旦删除了BoxCollider,就会出现以下错误:

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

I try to locate where the error is ocurring by looking at the trace: 我试着通过查看跟踪找到错误发生的位置:

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)

But none of my scripts appears on it. 但我的脚本都没有出现在它上面。

I've been looking on the code where I'm referencing the BoxCollider and the only place is where it is created, when the stairs are created which is triggered once a change has happened on the inspector. 我一直在寻找我引用BoxCollider的代码,唯一的地方就是它创建的地方,当创建楼梯时,一旦检查员发生了变化就会触发。

It is in the class: 它在课堂上:

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

This is the code: 这是代码:

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

I've tried to comment this method's body to allow removing the BoxCollider without this "reference" and the error still appears, so, I guess this method is not the problem. 我试图评论这个方法的主体,以允许删除没有这个“参考”的BoxCollider ,但错误仍然出现,所以,我想这个方法不是问题。

Also, I've removed the BoxCollider manually, without clicking the Finalize button to trigger this code, via right click on the component on the inspector "Remove Component" option and the error not appears and after that, click over finalize stairs and no problem shows up. 另外,我手动删除了BoxCollider ,没有单击Finalize按钮来触发此代码,通过右键单击检查器“删除组件”选项上的组件并且错误没有出现,之后,单击结束楼梯并没有问题出现。

As @JoeBlow mentioned in the comments I've checked that the FinalizeStairs method is called just once . 正如@JoeBlow在评论中提到的,我已经检查过FinalizeStairs方法只被调用一次

Also I've checked that the process of creation with the call to AddSelectionBox method it is not happening on the moment of clicking finalize button. 此外,我已经通过调用AddSelectionBox方法检查了创建过程,它在点击结束按钮时没有发生。

So, please I need a hand on this. 所以,请我亲自动手。 This is the link to the development branch https://github.com/JAFS6/BoxStairsTool/tree/feature/BoxStairsTool , here you will find that the above mentioned method FinalizeStairs has the code wich removes the BoxStairs script only and on that moment it throws no errors. 这是开发分支的链接https://github.com/JAFS6/BoxStairsTool/tree/feature/BoxStairsTool ,在这里你会发现上面提到的方法FinalizeStairs只有代码才能删除BoxStairs脚本,就在那一刻它没有错误。

Any idea or advice on this will be very helpful. 对此有任何想法或建议将非常有帮助。 Thanks in advance. 提前致谢。

Edit: a Minimal, Complete, and Verifiable example: 编辑:最小,完整和可验证的示例:

Asset/BoxStairs.cs 资产/ 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 资产\\编辑\\ 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);
        }
    }
}

Analysis 分析

I'm a programmer, so I just debug to find the problem (in my mind :D). 我是程序员,所以我只是调试找到问题(在我看来:D)。

MissingReferenceException: The object of type 'BoxCollider' has been destroyed but you are still trying to access it. MissingReferenceException:类型'BoxCollider'的对象已被销毁,但您仍在尝试访问它。
Your script should either check if it is null or you should not destroy the object. 您的脚本应该检查它是否为null或者您不应该销毁该对象。
UnityEditor.Editor.IsEnabled () (at C:/buildslave/unity/build/Editor/Mono/Inspector/Editor.cs:590) UnityEditor.Editor.IsEnabled()(在C:/buildslave/unity/build/Editor/Mono/Inspector/Editor.cs:590)

A MissingReferenceException occurs when the code trys to access a Unity3D.Object after it has been destroyed. 当代码在销毁Unity3D.Object后尝试访问它时,会发生MissingReferenceException。

Let's look into the decompiled code of UnityEditor.Editor.IsEnabled() . 让我们看一下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;
}

We won't be able to know which line is the specific line 590. But, we can tell where can a MissingReferenceException happen: 我们将无法知道哪一行是特定行590.但是,我们可以告诉MissingReferenceException可以发生在哪里:

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

@object is assigned from Editor.targets which is an array of all the object being inspected . @object从分配Editor.targets是被检查的所有对象的数组 There should be only one target object in this array in your case - the BoxCollider component. 在你的情况下,这个数组中应该只有一个目标对象 - BoxCollider组件。

In conclusion , the inspector failed to access the target object (I mean targets[0] ) after you calls Undo.DestroyObjectImmediate on the BoxCollider component. 总之 ,在BoxCollider组件上调用Undo.DestroyObjectImmediate之后,检查器无法访问目标对象(我的意思是targets[0] )。

If you dig into the decompiled code of the inspector( UnityEditor.InspectorWindow ), you will see that the overridden OnInspectorGUI function is called per Editor in order in UnityEditor.InspectorWindow.DrawEditors , including the internal editor of BoxCollider and your custom editor BoxStairsEditor of BoxStairs . 如果你深入到检查(的反编译的代码UnityEditor.InspectorWindow ),你会看到被覆盖的OnInspectorGUI功能是每个编辑器调用顺序 UnityEditor.InspectorWindow.DrawEditors ,包括BoxCollider的内部编辑器和您的自定义编辑BoxStairsEditorBoxStairs

Solutions 解决方案

  1. Do not destory a component that is being shown by the Inspector in OnInspectorGUI . 不要OnInspectorGUI Inspector显示的组件。
    Maybe you can add a delegate instance to EditorApplication.update to do that instead. 也许您可以将一个委托实例添加到EditorApplication.update来代替。 In this way, the deleting operation will not break the editor/inspector GUI of BoxCollider . 这样,删除操作不会破坏BoxCollider的编辑器/检查器GUI。
  2. Move the created component BoxCollider upper than your BoxStairs component before you destroy it. 在销毁之前,将创建的组件BoxCollider移动到BoxStairs组件的上方。 This may work but I'm not sure if other internal editor will access the BoxCollider or not. 这可能有效但我不确定其他内部编辑器是否会访问BoxCollider This solution not works when using UnityEditorInternal.ComponentUtility.MoveComponentUp . 使用UnityEditorInternal.ComponentUtility.MoveComponentUp时,此解决方案UnityEditorInternal.ComponentUtility.MoveComponentUp But, if the user manually move up the BoxCollider component, it works without any code changes. 但是,如果用户手动向上移动BoxCollider组件,它可以在没有任何代码更改的情况下工作。

Code of solution 解决方案代码

After using solution 1, the NRE is gone on Unity3D 5.4 on Win10. 使用解决方案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;
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM