赞
踩
原文:
[Unity3D]调用函数时出现NullReferenceException的一个可能原因
今天遇到一个报错,如下图:
NullReferenceException
UnityEngine.MonoBehaviour.StartCoroutine (IEnumerator routine) (at C:/BuildAgent/work/d3d49558e4d408f4/artifacts/EditorGenerated/UnityEngineMonoBehaviour.cs:63)
...
使用场景是这样的:
我在一个脚本中需要调用一个协程,该协程被我封装在一个新的脚本里(MyCoroutines.cs),该脚本是一个单例:
// MyCoroutines.cs
class MyCoroutines : MonoBehavior {
public static MyCoroutines Instance {
get {return sInstance ?? new MyCoroutines() : sInstance;}
}
private MyCoroutines sInstance;
public IEnumerator MethodA() {
// ...
}
}
是这样调用的:
...
// 在此处调用
StartCoroutine(MyCoroutines.Instance.MethodA());
...
然后运行,程序报错。
百思不得其解。
明明是个单例,不应该出现NullReference。
网上查了一下,才恍然,其实是我对Unity的运行机制没有理解,先看看别人的解释:
Well, i just guess that the script (and / or the gameobject it's attached to) has been destroyed, or you created the Wait-instance with "new" which would result in the same behaviour since a MonoBehaviour can't live on it's own.
这段就解释的很清楚了:使用new创建实例或该脚本依附的gameObject被销毁了,都会报这种错误,因为MonoBehaviour脚本不能独自存在,必须依附于某个GameObject。
所以解决的办法就是让这个脚本依附于某个GameObject就可以了,于是我修改了MyCoroutines类的实现:
public class MyCoroutines : MonoBehaviour {
private MyCoroutines() { }
static public MyCoroutines GetOrCreate(GameObject gameObject) {
if (gameObject == null) { return new MyCoroutines(); }
var existed = gameObject.GetComponent <MyCoroutines>();
return existed ?? gameObject.AddComponent <MyCoroutines>();
}
}
// 调用:
StartCoroutine(MyCoroutines.GetOrCreate(gameObject).MethodA());
这可能不是最好的解决办法,不过可以正常运行了。
下面是更详细的解释,有兴趣的童鞋可以继续看:
In C# / .NET / Mono instances actually can't be destroyed since they live in a managed memory environment. Objects are destroyed when all references to the object are gone, no longer valid. After that the garbage collector eventually kicks in and removes the object.
However in Unity, since it's core is written in C++, (native) objects can be destroyed on command (with Destroy to be more precise). The Destroy method actually only destroys the object on the c++ side. The managed representation of the object (your MonoBehaviour script) will still be there since the GC can only collect the object when there are no references anymore. That's why Unity actually "fakes" that the reference is null when the object has lost it's native counterpart.
If you use the "new" keyword to create an instance of a MonoBehaviour derived class the instance doesn't have a native counterpart and will always pretend to be null. If you want to create an instance at runtime it has to be attached to a gameobject. This is done with AddComponent
本人haobaworenle翻译:在 C# / .NET / Mono中,实例实际上不能被销毁,因为它们运行在一个托管的内存环境。当对象上所有引用都消失了,这个对象才被销毁,而且不再生效。之后垃圾回收站才踢掉并移除这个对象。然而在unity中,因为它的内核是用c++写的,(本地)对象可以根据命令执行销毁(更精确地销毁)。销毁方法实际上只能销毁处于c++那一端的对象。对象的托管表示(你的MonoBehaviour脚本)仍会存在,因为GC只能回收没有引用的对象。那就是为什么unity在对象丢失了本地配对物后事实上报错称引用为空。如果使用“new”关键字来创建一个MonoBehaviour派生类的实例,这个实例没有一个本地配对物,会总是谎称为空。如果你想在执行期间创建一个实例,不得不把它绑定到一个gameobject游戏对象上。这可以利用AddComponent方法完成。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。