<阅读并理解本文需要一定GDscript基础>
事关紧急,咱们就直接步入正题:
!!!但虽然紧急,可本文如有错误,欢迎指出!!!
各位请看一下这个代码:
很简单,不是吗?上面这段代码就是在_ready函数被加载时,调用test函数。
可在Godot3中,我们试着像其它语言那样,引用函数时就会发现:
这是错误的!!!
因为Godot3 GDscript中没有用于存储函数的类型!
我们可以这样简单地理解,当GDscript编译器处理到函数,会把它们单独处理,加载到当前节点的函数缓冲区
(注:实际上是gdscript_parser,gdscript_analyzer,gdscript_compiler三个模块分步处理GDscript,为便于理解,统称为GDscript编译器;实际上脚本中的函数编译后存储在脚本实例中,因为可以与节点注册的函数一起使用,这里称“节点的函数缓冲区”)
而调用函数时,例如test(),就被编译器视为运行节点函数缓冲区中的名为test的函数!而且,例如这里的test(),其中的test会被编译器认定为标识符,而不是变量数据!
顺便提一下,所以Godot3变量与函数同名是被允许的
所以在Godot3中,GDscript的变量是无法引用函数的.....
但是可以用call函数近似实现函数引用:
Object.call()函数
像上面这样,我们可以动态给a赋值为某个函数名,然后通过call就可以调用了。
而call的本质就是在节点的函数缓冲区中搜索指定名称的函数,搜索到后调用
可是,相比来说,call的效率很低,因为使用call时,会先在函数缓冲区中搜索函数!!!而且反复调用,会造成反复搜索,就浪费了性能!!!
所以在Godot4中官方在内置类型(基本数据类型)中添加了Callable类!!!
正如它的名字:“能调用”
它解决了Godot GDscript的引用函数方面的问题:
以上a段和b段代码,效果相同。
Callable本质也就是在节点的函数缓冲区中搜寻指定函数:
所以,手动构造一个Callable类型时,
首先传入一个节点,这里我们我们传入self(当前节点自身)
然后传入一个字符串,表示搜索的函数名
这样,Callable就会在当前节点中,搜索名为“test”的函数
是不是感觉Callable的用法和原来call也没多大差别?确实,传参也是相似的:
以上a段和b段代码,效果相同。
但是效率上,Callable就快的多了:
当然,以上都为Callable与Call的对比,Callable最大的优势在于,GDscript编译器可以直接解析注册的函数,并包装为Callable:
(有关GDscript编译器直接解析注册的函数的深入内容,详见下文)
注意,这里再强调一下:
你还记得test()的含义吗?不记得的话,到前面回顾一下吧。
看一下,下面这段代码:
正如我们之前提到的,平时,我们直接调用一个函数,例如test(),test不会被当做变量数据,所以test()就直接调用缓冲区中的test函数,而不是Callable.... Callable通过内置的call函数调用的
Godot4 GDscript与Godot3 GDscript有一点不同:
全局变量不能和函数同名!!!!!!!!!!
.....
当然Callable还有很多内置函数,这里就不叙述了,有兴趣的话,可以自己去翻文档
有关Callable类型的内容大致就这么多了,按道理就该结束了,但在结束之前,不妨来看一下这段代码:
还记得我提到的Callable的优势吗?
以上代码运行时,GDscript编译器可以直接解析注册的函数,并包装为Callable。
简化来说,当编译器编译 print(test) 时,因为没有发现test这个变量,但编译器发现了有名称为test的函数,所以编译器就会把变量test替换为Callable:
(你不必担心中间的性能消耗,以上只是通俗的解释,实际编译器做的更加精妙)
当然,Callable替换的前提是找不到你写的变量,才去在函数中查找,并转为Callable,请看下面的这个例子:
因为编译器找到了变量test的定义,所以是不会转换的!!!!
但这里有一个问题:
下面的print(text),是在var test=12之前执行的,
按道理,这时候因为变量text没有定义,
所以编译器应该会把print(text)替换为print(Callable(self,"test"))
但实际的输出却是:
print(text)中的text被当做null来处理了!!!!而且没有报错!!!!
这应该是一个BUG,或者是什么特殊语法,本人也不明白为什么这样变更,所以我在github godot项目中提交了这个问题:
等后续官方工作人员的解释吧......
好的,Callable的内容到这里就结束了,本文按道理就应该结束了,但是,作为小吧主,鸽了这么久,一直声称自己研究Godot源码去了,总要拿出点研究成果。现在,Now,我要分享一个重大的发现,所以:
正文开始:
最近在研究Godot源码时,发现Godot4的GDscript处理模块中,有一个LambdaNode,也就是用于存储Lambda信息的,我一看,啊?!Godot4 GDscript支持Lambda表达式了?
并且,还有专门的处理类:
但我在网上根本就没有找到有关Godot Lambda的相关资料!!!
所以我凭着源码,最终了解了Godot的Lambda表达式
首先,明确一点,Godot4 GDscript是支持Lambda表达式的!
所以,就让我们来学习Godot4 GDscript中的Lambda表达式吧:
什么是Lambda表达式?
Lambda表达式也就是匿名函数:
在Godot3 GDscript中我们是无法在函数中声明函数
但在Godot4中,我们可以通过匿名函数解决:
直观上来看,匿名函数就是普通函数,去掉函数名,
匿名函数会被包装为Callable类型:
但实际上匿名函数与普通的函数有很大不同,普通的函数会被加载到当前节点的函数缓冲区,而匿名函数不会,请看这段代码:
get_node是Node类上定义的一个方法,Node.get_node,它不是静态函数,这意味着要调用get_node,你必须基于一个Node类或其它扩展自Node的类,使用XXX.get_node才行!!
在平时的函数中,因为函数最终加载到当前节点的函数缓冲区,
get_node(XXX)会被默认转换为self.get_node(XXX)
所以平时写的get_node,都是基于self(当前节点)调用的,其它节点非静态函数同理...
而匿名函数,不会储存在当前节点的函数缓冲区,所以是无法直接调用get_node等节点的非静态函数的
但我们依旧可以手动传入节点类:
这里我就把当前节点的实例传入,再在它的基础上调用get_node
当然,如果匿名函数只是执行单语句,一行写也可以:
通过匿名函数,我们还可以实现快速的回调函数传递:
大致上,匿名函数的基本内容就这些了,毕竟和普通函数也没差多少,
现在,大家一起去探索吧
至于更多的内容,休息休息再发(待我研究研究Godot中的闭包)
事关紧急,咱们就直接步入正题:
!!!但虽然紧急,可本文如有错误,欢迎指出!!!
各位请看一下这个代码:
很简单,不是吗?上面这段代码就是在_ready函数被加载时,调用test函数。
可在Godot3中,我们试着像其它语言那样,引用函数时就会发现:
这是错误的!!!
因为Godot3 GDscript中没有用于存储函数的类型!
我们可以这样简单地理解,当GDscript编译器处理到函数,会把它们单独处理,加载到当前节点的函数缓冲区
(注:实际上是gdscript_parser,gdscript_analyzer,gdscript_compiler三个模块分步处理GDscript,为便于理解,统称为GDscript编译器;实际上脚本中的函数编译后存储在脚本实例中,因为可以与节点注册的函数一起使用,这里称“节点的函数缓冲区”)
而调用函数时,例如test(),就被编译器视为运行节点函数缓冲区中的名为test的函数!而且,例如这里的test(),其中的test会被编译器认定为标识符,而不是变量数据!
顺便提一下,所以Godot3变量与函数同名是被允许的
所以在Godot3中,GDscript的变量是无法引用函数的.....
但是可以用call函数近似实现函数引用:
Object.call()函数
像上面这样,我们可以动态给a赋值为某个函数名,然后通过call就可以调用了。
而call的本质就是在节点的函数缓冲区中搜索指定名称的函数,搜索到后调用
可是,相比来说,call的效率很低,因为使用call时,会先在函数缓冲区中搜索函数!!!而且反复调用,会造成反复搜索,就浪费了性能!!!
所以在Godot4中官方在内置类型(基本数据类型)中添加了Callable类!!!
正如它的名字:“能调用”
它解决了Godot GDscript的引用函数方面的问题:
以上a段和b段代码,效果相同。
Callable本质也就是在节点的函数缓冲区中搜寻指定函数:
所以,手动构造一个Callable类型时,
首先传入一个节点,这里我们我们传入self(当前节点自身)
然后传入一个字符串,表示搜索的函数名
这样,Callable就会在当前节点中,搜索名为“test”的函数
是不是感觉Callable的用法和原来call也没多大差别?确实,传参也是相似的:
以上a段和b段代码,效果相同。
但是效率上,Callable就快的多了:
当然,以上都为Callable与Call的对比,Callable最大的优势在于,GDscript编译器可以直接解析注册的函数,并包装为Callable:
(有关GDscript编译器直接解析注册的函数的深入内容,详见下文)
注意,这里再强调一下:
你还记得test()的含义吗?不记得的话,到前面回顾一下吧。
看一下,下面这段代码:
正如我们之前提到的,平时,我们直接调用一个函数,例如test(),test不会被当做变量数据,所以test()就直接调用缓冲区中的test函数,而不是Callable.... Callable通过内置的call函数调用的
Godot4 GDscript与Godot3 GDscript有一点不同:
全局变量不能和函数同名!!!!!!!!!!
.....
当然Callable还有很多内置函数,这里就不叙述了,有兴趣的话,可以自己去翻文档
有关Callable类型的内容大致就这么多了,按道理就该结束了,但在结束之前,不妨来看一下这段代码:
还记得我提到的Callable的优势吗?
以上代码运行时,GDscript编译器可以直接解析注册的函数,并包装为Callable。
简化来说,当编译器编译 print(test) 时,因为没有发现test这个变量,但编译器发现了有名称为test的函数,所以编译器就会把变量test替换为Callable:
(你不必担心中间的性能消耗,以上只是通俗的解释,实际编译器做的更加精妙)
当然,Callable替换的前提是找不到你写的变量,才去在函数中查找,并转为Callable,请看下面的这个例子:
因为编译器找到了变量test的定义,所以是不会转换的!!!!
但这里有一个问题:
下面的print(text),是在var test=12之前执行的,
按道理,这时候因为变量text没有定义,
所以编译器应该会把print(text)替换为print(Callable(self,"test"))
但实际的输出却是:
print(text)中的text被当做null来处理了!!!!而且没有报错!!!!
这应该是一个BUG,或者是什么特殊语法,本人也不明白为什么这样变更,所以我在github godot项目中提交了这个问题:
等后续官方工作人员的解释吧......
好的,Callable的内容到这里就结束了,本文按道理就应该结束了,但是,作为小吧主,鸽了这么久,一直声称自己研究Godot源码去了,总要拿出点研究成果。现在,Now,我要分享一个重大的发现,所以:
正文开始:
最近在研究Godot源码时,发现Godot4的GDscript处理模块中,有一个LambdaNode,也就是用于存储Lambda信息的,我一看,啊?!Godot4 GDscript支持Lambda表达式了?
并且,还有专门的处理类:
但我在网上根本就没有找到有关Godot Lambda的相关资料!!!
所以我凭着源码,最终了解了Godot的Lambda表达式
首先,明确一点,Godot4 GDscript是支持Lambda表达式的!
所以,就让我们来学习Godot4 GDscript中的Lambda表达式吧:
什么是Lambda表达式?
Lambda表达式也就是匿名函数:
在Godot3 GDscript中我们是无法在函数中声明函数
但在Godot4中,我们可以通过匿名函数解决:
直观上来看,匿名函数就是普通函数,去掉函数名,
匿名函数会被包装为Callable类型:
但实际上匿名函数与普通的函数有很大不同,普通的函数会被加载到当前节点的函数缓冲区,而匿名函数不会,请看这段代码:
get_node是Node类上定义的一个方法,Node.get_node,它不是静态函数,这意味着要调用get_node,你必须基于一个Node类或其它扩展自Node的类,使用XXX.get_node才行!!
在平时的函数中,因为函数最终加载到当前节点的函数缓冲区,
get_node(XXX)会被默认转换为self.get_node(XXX)
所以平时写的get_node,都是基于self(当前节点)调用的,其它节点非静态函数同理...
而匿名函数,不会储存在当前节点的函数缓冲区,所以是无法直接调用get_node等节点的非静态函数的
但我们依旧可以手动传入节点类:
这里我就把当前节点的实例传入,再在它的基础上调用get_node
当然,如果匿名函数只是执行单语句,一行写也可以:
通过匿名函数,我们还可以实现快速的回调函数传递:
大致上,匿名函数的基本内容就这些了,毕竟和普通函数也没差多少,
现在,大家一起去探索吧
至于更多的内容,休息休息再发(待我研究研究Godot中的闭包)