删除 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 && 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
时不起作用。但是,如果用户手动上移@987654343@ 组件,它无需任何代码更改即可工作。
解决方案代码
使用方案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<BoxCollider>(); 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的主要内容,如果未能解决你的问题,请参考以下文章