删除 BoxCollider 时的 Unity3D MissingReferenceException

Posted

技术标签:

【中文标题】删除 BoxCollider 时的 Unity3D MissingReferenceException【英文标题】:Unity3D MissingReferenceException when removing BoxCollider 【发布时间】:2017-04-09 15:23:10 【问题描述】:

我正在为 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 楼梯就没有问题了。

正如@JoeBlow 在 cmets 中提到的,我检查了 FinalizeStairs 方法只被调用了一次

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

所以,请我帮忙。这是开发分支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);
        
    

【问题讨论】:

这个 FinalizeStairs 肯定被多次调用,或者类似的东西?只需添加一行简单的代码 Debug.Log("I am in FinalizeStairs " +gameObject.name);在该例程的顶部。 我不明白你为什么要销毁组件,如果你仍然销毁“目标”? 我已经添加了提到的Debug.Log @JoeBlow,结果是一样的,消息显示在错误之前。 @JoeBlow 我认为使用“目标”会破坏游戏对象,但它不会,它会删除 BoxStairs 脚本组件。 尝试if (bc != null &amp;&amp; Event.current.type == EventType.Layout) 并在之后添加else 语句 【参考方案1】:

分析

我是一名程序员,所以我只是调试以发现问题(在我看来:D)。

MissingReferenceException:“BoxCollider”类型的对象已被破坏,但您仍在尝试访问它。 您的脚本应该检查它是否为空,或者您不应该销毁该对象。 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 分配的,它是所有被检查对象的数组。在您的情况下,此数组中应该只有一个目标对象 - BoxCollider 组件。

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

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

解决方案

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

解决方案代码

使用方案1后,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;
            
        
    

【讨论】:

我已经测试了第二个选项。我在销毁之前将组件移动到脚本组件之前,组件被正确移动,但是当它被销毁时,异常再次上升。 UnityEditorInternal.ComponentUtility.MoveComponentUp(bc); if (bc != null) Undo.DestroyObjectImmediate(bc); 我已经测试了第一个选项,创建了一个销毁 BC 的方法 private void RemoveSelectionBox () BoxStairs script = (BoxStairs)target; GameObject go = script.gameObject; BoxCollider bc = go.GetComponent&lt;BoxCollider&gt;(); if (bc != null) Undo.DestroyObjectImmediate(bc); 我已经将它添加到 EditorApplication.update 这种方式 EditorApplication.update = RemoveSelectionBox; on FinalizeStairs Undo.DestroyObjectImmediate(target);之前的方法现在按下finalize按钮时,错误显示很多次并且没有停止,我需要重置Unity编辑器来停止它。 请记住在您销毁 BoxCollider 后从 EditorApplication.update删除委托实例 也许通过在RemoveSelectionBox 中销毁后将EditorApplication.update 设置为null 我在 RemoveSelectionBox 方法的末尾添加了`EditorApplication.update = null;`,没有任何改变,错误一直显示很多次。

以上是关于删除 BoxCollider 时的 Unity3D MissingReferenceException的主要内容,如果未能解决你的问题,请参考以下文章

3dMAX的模型导入unity中如何实现碰撞

(转) Unity3D常用代码收集总结

为啥我unity3d 用以下代码没有出现球体?

Unity3d - RPG项目学习笔记(二十二)

Unity3d - RPG项目学习笔记(二十八)

日常记录unity3d OnTriggerEnter 和 OnCollisionEnter (2D) 的区别