xLua下調用GetComponent返回值不是nil的坑
問題
看下面代碼:
-- gameObject沒有Rigidbody,但返回值不等於nil
local old_rigidbody = self.Owner.gameObject:GetComponent(typeof(CS.UnityEngine.Rigidbody))
if old_rigidbody then
return
end
原因
主要原因可以參考文章:Why does component.GetComponent() return “null”, when a rigid body is NOT attached?
基本是由2個原因導致的:
- Unity對部分內置的Component(如rigidbody、animator等),在
GetComponent
時,如果組件不存在,Unity不會返回null,而是返回一個會判斷爲null的object(類似一個gameObject被Destroy後,會判斷爲null一樣,可以參考這篇文章:Custom == operator, should we keep it?)。這是因爲Unity重載了UnityEngine.Object的==
運算符。 - xLua將引用類型壓棧時會統一將對象改爲object,這就導致了Unity重載的
==
運算符失效。
簡單來說就是下面的情況:
// 假設gameObject沒有Rigidbody組件
var com = gameObject.GetComponent<Rigidbody>();
if (com == null) {
// ok,因爲Unity重載了運算符
}
object obj = com;
if (obj == null) {
// 失敗,此時會採用object自己的==判定
}
實際項目的情況如下,可以看到出問題的是ObjectTranslator.Push
函數。
// UnityEngineGameObjectWrap._m_GetComponent函數
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_GetComponent(RealStatePtr L)
{
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
UnityEngine.GameObject __cl_gen_to_be_invoked = (UnityEngine.GameObject)translator.FastGetCSObj(L, 1);
int __gen_param_count = LuaAPI.lua_gettop(L);
try {
if(__gen_param_count == 2&& translator.Assignable<System.Type>(L, 2))
{
System.Type type = (System.Type)translator.GetObject(L, 2, typeof(System.Type));
UnityEngine.Component __cl_gen_ret = __cl_gen_to_be_invoked.GetComponent( type );
translator.Push(L, __cl_gen_ret);
return 1;
}
// ... some code
}
// ... some code
}
// ObjectTranslator.Push函數
// 注意Push函數接收的是object類型的參數
public void Push(RealStatePtr L, object o)
{
// 這裏的判定失敗了。
if (o == null)
{
LuaAPI.lua_pushnil(L);
return;
}
// ... some code
}
再深入理解一下,爲什麼多態沒有生效呢?通過ILSpy查看UnityEngine.Object的IL代碼,可以發現
public override bool Equals(object other)
{
Object @object = other as Object;
return (!(@object == null) || other == null || other is Object) && Object.CompareBaseObjects(this, @object);
}
public static implicit operator bool(Object exists)
{
return !Object.CompareBaseObjects(exists, null);
}
public static bool operator ==(Object x, Object y)
{
return Object.CompareBaseObjects(x, y);
}
public static bool operator !=(Object x, Object y)
{
return !Object.CompareBaseObjects(x, y);
}
可以發現,Equals是override的,而==
和!=
是static的,所以Equals函數是可以正常判定null的,而對於==
和!=
運算符,則2側必須至少有一個是UnityEngine.Object類型才能生效,否則會直接採用原生的==
和!=
運算符。
// 假設gameObject沒有Rigidbody組件
var com = gameObject.GetComponent<Rigidbody>();
if (com == null) {
// ok,因爲Unity重載了運算符
}
object obj = com;
if (obj.Equals(null)) {
// ok,多態生效了
}
解決方案
上面提到的參考文章還提到,這個問題只在Editor下會有,在Release版肯定會返回null的(不過這點本人並沒有測試過)。
那解決辦法主要就是修改ObjectTranslator.Push
函數
// ObjectTranslator.Push函數
public void Push(RealStatePtr L, object o)
{
// 加上Equals,讓UnityObject的多態可以生效
if (o == null || o.Equals(null))
{
LuaAPI.lua_pushnil(L);
return;
}
// ... some code
}