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
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章