Unity3D 删除 BoxCollider 时出现 MissingReferenceException

问题描述 投票:0回答:1

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

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

在我的 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:“BoxCollider”类型的对象已被销毁,但您仍在尝试访问它。

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

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 在评论中提到的,我已经检查过 FinalizeStairs 方法仅被调用一次

我还检查了通过调用 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;
        }

    }
}

资产\编辑器\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);
        }
    }
}
c# exception unity-game-engine editor components
1个回答
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
组件。

结论,在您在

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

如果你深入研究检查器(

UnityEditor.InspectorWindow
)的反编译代码,你会发现重写的
OnInspectorGUI
函数是按UnityEditor.InspectorWindow.DrawEditors中的每个编辑器
按顺序
调用的,包括BoxCollider的内部编辑器和你的自定义编辑器
BoxStairsEditor
BoxStairs
.

解决方案

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

使用解决方案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; } } } }

	
© www.soinside.com 2019 - 2024. All rights reserved.