开始

先看一段代码:

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());
	}
}

代码中,先创建了两个GameObjectgo1go2;接下来立即将go1销毁;然后将go1go2分别与null比较的结果打印出来;最后通过??运算符得到返回值并赋值给go,并输出gonull比较的结果。

不了解??运算符的可以看看MSDN,这里是传送门

直觉上:
1、go1被销毁了,那么第一个输出将是go1 == null : True
2、没有对go2进行操作,那么第二个输出应该是go2 == null : False
3、通过??运算符、go1既然是null、那么go将会被赋值为go2。因此第三个输出将是go == null : False

实验

测试之。将脚本挂在场景中任意物体,运行后发现输出结果与前面推测的不太一致:

unity添加空物体_运算符


问题主要表现在第三个输出上。通过该结果,可以断定,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

此时会报出以下错误:

unity添加空物体_Lua_02


这不是我们希望看到的结果。所以在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侧对象。