xLua下調用GetComponent時返回值不是nil的坑

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個原因導致的:

  1. Unity對部分內置的Component(如rigidbody、animator等),在GetComponent時,如果組件不存在,Unity不會返回null,而是返回一個會判斷爲null的object(類似一個gameObject被Destroy後,會判斷爲null一樣,可以參考這篇文章:Custom == operator, should we keep it?)。這是因爲Unity重載了UnityEngine.Object的==運算符。
  2. 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
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章