xLua下调用GetComponent时返回值不是nil的坑
https://blog.csdn.net/gouki04/article/details/84784795
xLua下调用GetComponent返回值不是nil的坑
问题
看下面代码:
-- gameObject没有Rigidbody,但返回值不等于nil
local old_rigidbody = self.Owner.gameObject:GetComponent(typeof(CS.UnityEngine.Rigidbody))
if old_rigidbody then
return
end
1
2
3
4
5
原因
主要原因可以参考文章: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自己的==判定
}
1
2
3
4
5
6
7
8
9
10
实际项目的情况如下,可以看到出问题的是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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
再深入理解一下,为什么多态没有生效呢?通过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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
可以发现,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,多态生效了
}
1
2
3
4
5
6
7
8
9
10
解决方案
上面提到的参考文章还提到,这个问题只在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
}
————————————————
版权声明:本文为CSDN博主「gouki04」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gouki04/article/details/84784795