删除 BoxCollider 时的 Unity3D MissingReferenceException
Unity3D MissingReferenceException when removing BoxCollider
我正在为 Unity3D 开发一个开源编辑器工具 https://github.com/JAFS6/BoxStairsTool 我正在编写一个 CustomEditor。
我创建了一个主游戏对象,并将我的脚本 BoxStairs 附加到它上面。此脚本附加到相同的 GameObject a 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();
}
}
两种方法都在class
[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)
但是我的 none 个脚本出现在上面。
我一直在查看我引用 BoxCollider 的代码,唯一的地方是创建它的地方,当创建楼梯时触发一次检查员发生了变化。
它在class:
[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;
}
我试图评论此方法的主体以允许在没有此 "reference" 的情况下删除 BoxCollider 并且错误仍然出现,所以,我想这个方法不是问题。
此外,我手动删除了 BoxCollider,没有单击 Finalize 按钮来触发此代码,通过右键单击检查器 "Remove Component" 选项上的组件并且错误没有出现,之后,点击 finalize stairs 并且没有出现问题。
正如@JoeBlow 在评论中提到的,我检查过 FinalizeStairs 方法 只被调用一次 。
我还检查了调用 AddSelectionBox 方法的创建过程是否在单击完成按钮时没有发生。
所以,我需要帮忙。这是开发分支https://github.com/JAFS6/BoxStairsTool/tree/feature/BoxStairsTool的link,在这里你会发现上面提到的方法FinalizeStairs只有删除BoxStairs脚本的代码它不会抛出任何错误。
对此的任何想法或建议都会非常有帮助。提前致谢。
编辑:
一个最小的、完整的和可验证的例子:
Asset/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);
}
}
}
分析
我是一名程序员,所以我只是通过调试来发现问题(在我看来 :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;
}
我们无法知道具体的第 590 行是哪一行。但是,我们可以判断 MissingReferenceException
可能发生的位置:
// ↓↓↓↓↓↓
if ((@object.hideFlags & HideFlags.NotEditable) != HideFlags.None)
@object
从 Editor.targets 赋值,Editor.targets 是 所有被检查对象的数组 。在您的情况下,此数组中应该只有一个目标对象 - BoxCollider
组件。
总之,在BoxCollider
上调用Undo.DestroyObjectImmediate
后,检查器未能访问目标对象(我的意思是targets[0]
)组件。
如果你深入研究 inspector(UnityEditor.InspectorWindow
) 的反编译代码,你会看到覆盖的 OnInspectorGUI
函数是按编辑器顺序调用的 在 UnityEditor.InspectorWindow.DrawEditors
中,包括 BoxCollider 的内部编辑器和 BoxStairs
的自定义编辑器 BoxStairsEditor
。
解决方案
- 不要销毁 Inspector 在
OnInspectorGUI
中显示的组件。
也许您可以将委托实例添加到 EditorApplication.update 来代替。这样删除操作就不会破坏BoxCollider
. 的editor/inspector界面了
将创建的组件 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;
}
}
}
}
我正在为 Unity3D 开发一个开源编辑器工具 https://github.com/JAFS6/BoxStairsTool 我正在编写一个 CustomEditor。
我创建了一个主游戏对象,并将我的脚本 BoxStairs 附加到它上面。此脚本附加到相同的 GameObject a 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();
}
}
两种方法都在class
[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)
但是我的 none 个脚本出现在上面。
我一直在查看我引用 BoxCollider 的代码,唯一的地方是创建它的地方,当创建楼梯时触发一次检查员发生了变化。
它在class:
[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;
}
我试图评论此方法的主体以允许在没有此 "reference" 的情况下删除 BoxCollider 并且错误仍然出现,所以,我想这个方法不是问题。
此外,我手动删除了 BoxCollider,没有单击 Finalize 按钮来触发此代码,通过右键单击检查器 "Remove Component" 选项上的组件并且错误没有出现,之后,点击 finalize stairs 并且没有出现问题。
正如@JoeBlow 在评论中提到的,我检查过 FinalizeStairs 方法 只被调用一次 。
我还检查了调用 AddSelectionBox 方法的创建过程是否在单击完成按钮时没有发生。
所以,我需要帮忙。这是开发分支https://github.com/JAFS6/BoxStairsTool/tree/feature/BoxStairsTool的link,在这里你会发现上面提到的方法FinalizeStairs只有删除BoxStairs脚本的代码它不会抛出任何错误。
对此的任何想法或建议都会非常有帮助。提前致谢。
编辑: 一个最小的、完整的和可验证的例子:
Asset/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);
}
}
}
分析
我是一名程序员,所以我只是通过调试来发现问题(在我看来 :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;
}
我们无法知道具体的第 590 行是哪一行。但是,我们可以判断 MissingReferenceException
可能发生的位置:
// ↓↓↓↓↓↓
if ((@object.hideFlags & HideFlags.NotEditable) != HideFlags.None)
@object
从 Editor.targets 赋值,Editor.targets 是 所有被检查对象的数组 。在您的情况下,此数组中应该只有一个目标对象 - BoxCollider
组件。
总之,在BoxCollider
上调用Undo.DestroyObjectImmediate
后,检查器未能访问目标对象(我的意思是targets[0]
)组件。
如果你深入研究 inspector(UnityEditor.InspectorWindow
) 的反编译代码,你会看到覆盖的 OnInspectorGUI
函数是按编辑器顺序调用的 在 UnityEditor.InspectorWindow.DrawEditors
中,包括 BoxCollider 的内部编辑器和 BoxStairs
的自定义编辑器 BoxStairsEditor
。
解决方案
- 不要销毁 Inspector 在
OnInspectorGUI
中显示的组件。
也许您可以将委托实例添加到 EditorApplication.update 来代替。这样删除操作就不会破坏BoxCollider
. 的editor/inspector界面了
将创建的组件此解决方案在使用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;
}
}
}
}