开始
先看一段代码:
using UnityEngine;
public class UnityEngineObjectCheck : MonoBehaviour {
void Start () {
GameObject go1 = new GameObject ("go1");
GameObject go2 = new GameObject ("go2");
DestroyImmediate (go1);
Debug.Log ("go1 == null : " + (go1 == null).ToString());
Debug.Log ("go2 == null : " + (go2 == null).ToString());
GameObject go = go1 ?? go2;
Debug.Log ("go == null : " + (go == null).ToString());
}
}
代码中,先创建了两个GameObject
:go1
和go2
;接下来立即将go1
销毁;然后将go1
和go2
分别与null
比较的结果打印出来;最后通过??
运算符得到返回值并赋值给go
,并输出go
与null
比较的结果。
不了解??
运算符的可以看看MSDN,这里是传送门。
直觉上:
1、go1
被销毁了,那么第一个输出将是go1 == null : True
;
2、没有对go2
进行操作,那么第二个输出应该是go2 == null : False
;
3、通过??
运算符、go1
既然是null
、那么go将会被赋值为go2
。因此第三个输出将是go == null : False
。
实验
测试之。将脚本挂在场景中任意物体,运行后发现输出结果与前面推测的不太一致:
问题主要表现在第三个输出上。通过该结果,可以断定,go
被赋值为go1
。也就是说,go1
实际的值是“非null
”的(虽然输出看起来是null
)。
分析
分析GameObject
的继承关系,GameObject
继承自UnityEngine.Object
。查看UnityEngine.Object
的类结构,发现UnityEngine.Object
重写了==
运算符和!=
运算符。因此可以猜测,当执行DestroyImmediate
后,go1
并不是真正就是null
了,只是使用==
运算符和!=
运算符时,Unity会把它当作null
值处理。Unity真正将go1
置空的时机就不得而知。
//
// Operators
//
public static bool operator == (Object x, Object y);
public static bool operator != (Object x, Object y);
这么做在Unity中是合理的,因为我们在Unity中做开发的时候,被Destroy掉的对象,就应当被认为它的值是null
。然而,在使用Lua对Unity对象进行空值判定的时候,就会出现问题了。
在Unity+XLua中的实验
接下来的实验在Unity+XLua的环境中进行。
当使用Unity执行如下的Lua代码时,会得到go == nil : false
的结果。
local go = CS.UnityEngine.GameObject('go')
CS.UnityEngine.GameObject.DestroyImmediate(go)
print('go == nil : ', go == nil)
这里的表现与直接在Unity中进行空值判定是不一致的,同时也是不合理的。因为我们很有可能会有如下类似的操作:
if go ~= nil then
--Some action like [go.name = 'go1'] or other
end
此时会报出以下错误:
这不是我们希望看到的结果。所以在Lua中,并不能用go ~= nil
来直接对UnityEngine.Object
的对象进行判空。
解决方案
为了统一Unity侧和Lua侧的判空行为,我们可以对UnityEngine.Object
进行扩展,使其可以提供一个方法来供Lua侧调用,进而保持Lua侧和Unity侧判空行为的一致。
在Unity中新建脚本UnityEngineObjectExtensionForXLua.cs
并写入以下代码:
using UnityEngine;
public static class UnityEngineObjectExtensionForXLua {
/// <summary>
/// 用于UnityEngine.Object及其子类对象的判空
/// 在使用DestroyImmediate销毁一个UnityEngine.Object对象时,该对象会被Unity认为已经是null
/// 但是C#并不认为它是null
/// 因此在与Lua交互时,不能直接在Lua侧判断对象是否为nil(这样判断走的是C#的判空),应该调用此方法(走的是Unity的判空)
/// </summary>
public static bool IsNull(this Object target)
{
return target == null;
}
}
在Lua侧提供一个工具函数IsNull,代码如下:
function IsNull(unityObject)
if unityObject == nil then return true end
if type(unityObject) == 'userdata' and unityObject.IsNull ~= nil then
return unityObject:IsNull()
end
return false
end
之后,在Lua侧需要对UnityEngine.Object
对象进行空值判定时,使用此函数即可。当然,该函数也兼容Lua侧对象。