Godot 引擎 Callable 类型完全指南:函数引用、信号回调与 RPC 调用
Godot 引擎 Callable 类型完全指南:函数引用、信号回调与 RPC 调用
Callable 是 Godot 引擎内置的 Variant 类型,用于表示一个"可调用的函数"——既可以指向某个 Object 实例上的方法,也可以是由绑定参数、lambda、GDExtension 或 C# 构造的自定义可调用对象。它贯穿于信号连接、Tween 回调、多线程任务分发与多人联机 RPC 等核心场景,是理解 Godot 脚本体系(GDScript 与 C#)的关键基础。读完本文,你将掌握 Callable 的构造、调用、参数绑定/解绑、延迟调用、lambda 语义、内省方法以及多人联机 RPC 的完整用法,并能依据 官方类参考 和仓库内 GDScript 基础教程 直接落地到实际项目中。
什么是 Callable
按照 class_callable.rst 的定义,Callable 是一种内置的 Variant 类型,表示一个函数。它有两种基本形态:
- 标准可调用(standard callable):指向某个
Object实例内的方法; - 自定义可调用(custom callable):用于各种特殊用途,例如参数绑定/解绑、内置
Variant类型的方法、GDScript 的全局函数与 lambda 等(可调用 is_custom() 判断)。
与所有 Variant 类型一样,Callable 可以存入变量、作为参数传递、作为返回值返回,最常见的用途是作为信号(Signal)的回调。
func print_args(arg1, arg2, arg3 = ""):
prints(arg1, arg2, arg3)
func test():
var callable = Callable(self, "print_args")
callable.call("hello", "world") # Prints "hello world ".
callable.call(Vector2.UP, 42, callable) # Prints "(0.0, -1.0) 42 Node(node.gd)::print_args"
callable.call("invalid") # Invalid call, should have at least 2 arguments.
C# 侧的对应写法(注意:C# 不支持默认参数值):
// Default parameter values are not supported.
public void PrintArgs(Variant arg1, Variant arg2, Variant arg3 = default)
{
GD.PrintS(arg1, arg2, arg3);
}
public void Test()
{
// Invalid calls fail silently.
Callable callable = new Callable(this, MethodName.PrintArgs);
callable.Call("hello", "world"); // Default parameter values are not supported, should have 3 arguments.
callable.Call(Vector2.Up, 42, callable); // Prints "(0, -1) 42 Node(Node.cs)::PrintArgs"
callable.Call("invalid"); // Invalid call, should have 3 arguments.
}
三个构造器:从空对象到方法绑定
Constructor Descriptions 定义了三个构造器:
| 构造器签名 | 说明 |
|---|---|
Callable() |
构造一个空 Callable,不绑定任何对象与方法(此时 is_null() 返回 true) |
Callable(from: Callable) |
从给定的 Callable 复制构造一份副本 |
Callable(object: Object, method: StringName) |
为 object 上名为 method 的方法创建新的 Callable |
需要注意:对于内置 Variant 类型的方法(如 Array、Dictionary 等),不能使用 Callable(object, method) 构造器,而必须使用静态方法 create()。
调用方式:call / callv / call_deferred
call():同步调用
call() 使用变长参数调用 Callable 所代表的方法,参数必须匹配方法签名:
callable.call("hello", "world")
GDScript 基础教程 中特别强调了一条规则:Callable 必须通过 call() 方法调用,不能直接使用 () 运算符。这一设计是为了避免直接函数调用带来的性能问题。
callv():用数组传参
callv() 与 call() 的唯一区别是所有参数必须装在 arguments: Array 中,适合参数数量动态变化的场景:
var args = ["hello", "world"]
callable.callv(args)
call_deferred():帧末延迟调用
call_deferred() 以延迟模式调用方法,即在当前帧结束时执行,常用来避免"在遍历/修改对象时被同步回调打断"的时序问题:
func _ready():
grab_focus.call_deferred()
public override void _Ready()
{
Callable.From(GrabFocus).CallDeferred();
}
官方文档对延迟调用给出了重要提醒:
- 延迟调用在**空闲时间(idle time)**处理,主要发生在 process 帧与 physics 帧的末尾;空闲时间内会持续执行延迟调用直到没有剩余,因此你可以从一个延迟调用里再派发延迟调用,它们仍会在当前空闲周期内被执行;
- 不要从方法自身(或其调用链)中再次延迟调用自己,否则会像直接调用一样造成无限递归;
- 也可参考
Object.call_deferred()的对应行为。
把方法引用当作 Callable:函数是一等公民
在 GDScript 中,按名称引用函数而不加括号调用,会自动生成对应的 Callable(gdscript_basics.rst)。这使得函数可以作为参数传递,实现 map/reduce 这类高阶函数模式:
func map(arr: Array, function: Callable) -> Array:
var result = []
for item in arr:
result.push_back(function.call(item))
return result
func add1(value: int) -> int:
return value + 1
func _ready() -> void:
var my_array = [1, 2, 3]
var plus_one = map(my_array, add1)
print(plus_one) # Prints `[2, 3, 4]`.
从对象成员上取得方法也能得到 Callable(gdscript_basics.rst):
var x = $Sprite2D.rotate # x 是一个 Callable:对象为 $Sprite2D,方法为 rotate
x.call(PI)
利用这一特性,可以把对象方法、内置类型方法、全局函数统一塞给 Tween 或信号:
tween.tween_callback(node.queue_free) # Object methods.
tween.tween_callback(array.clear) # Methods of built-in types.
tween.tween_callback(print.bind("Test")) # Global functions.
Dictionary 的特殊性:必须用 create()
注意 Dictionary 因为使用属性语法(dict.key)访问条目,存在与键名的歧义,不支持直接取方法作为 Callable:
var dictionary = { "hello": "world" }
# This will not work, `clear` is treated as a key.
tween.tween_callback(dictionary.clear)
# This will work.
tween.tween_callback(Callable.create(dictionary, "clear"))
create() 是一个静态方法,用于在指定 variant 上创建方法 Callable。当 variant 是内置 Variant 类型时使用自定义 Callable 表示,当 variant 是 Object 时创建标准 Callable。除 Dictionary 外,当 variant 的实际类型无法预先确定时(多态场景),也应使用 create()。
lambda:不绑定对象实例的自定义 Callable
在 GDScript 的方法内部可以创建 lambda 函数(gdscript_basics.rst)。lambda 是不与任何 Object 实例关联的自定义 Callable,可以命名也可以匿名;命名后名称会显示在调试器中,或通过 get_method() 获取(匿名 lambda 返回 "<anonymous lambda>")。
func _init():
var my_lambda = func (message):
print(message)
# Prints "Hello everyone!"
my_lambda.call("Hello everyone!")
# Prints "Attack!", when the button_pressed signal is emitted.
button_pressed.connect(func(): print("Attack!"))
lambda 的完整语法能力
- 匿名定义:
var lambda = func (x): print(x),调用lambda.call(42); - 命名定义(调试用):
var lambda = func my_lambda(x): print(x); - 类型标注:
var lambda := func (x: int) -> void: print(x); - 返回值必须显式 return:
var lambda = func (x): return x ** 2。
lambda 捕获语义(重要陷阱)
lambda 会捕获外层局部环境,但存在三个需要警惕的行为(gdscript_basics.rst):
- 局部变量按值捕获一次:lambda 创建时捕获的值不会因外层后续重新赋值而更新;
- lambda 内部对外层局部变量的重新赋值会被隐式遮蔽(触发
CONFUSABLE_CAPTURE_REASSIGNMENT警告),退出 lambda 后外层变量保持不变; - 引用类型(数组、字典、对象)例外:只要不重新赋值变量本身,内容修改会在 lambda 内外共享。
var x = 42
var lambda = func (): print(x)
lambda.call() # Prints `42`.
x = "Hello"
lambda.call() # Prints `42`(按值捕获,不更新)。
参数绑定与解绑:bind / bindv / unbind
bind():预绑定参数
bind() 返回一份"预绑定了一个或多个参数"的 Callable 副本。调用时,绑定的参数会追加在 call() 提供的参数之后:
var bound = print.bind("Test")
bound.call() # 相当于 print("Test")
bind(...)接受变长参数;bindv(arguments: Array)从数组读取要绑定的参数,二者行为一致;- 链式调用规则:当
bind/unbind等方法连续使用时,参数列表的修改顺序是从右往左读取的; bind与unbind会生成自定义 Callable(is_custom()返回true)。
unbind():忽略多余参数
unbind(argcount: int) 返回一份副本,调用时忽略用户提供的最后 argcount 个参数,其余参数照常传给原 Callable。典型用途:让参数较少的函数能够接入固定参数数量的信号回调。
func _ready():
foo.unbind(1).call(1, 2) # Calls foo(1).
foo.bind(3, 4).unbind(1).call(1, 2) # Calls foo(1, 3, 4), note that it does not change the arguments from bind.
注意第二个例子:bind 的结果再 unbind(1),被忽略的是调用方传入的最后 1 个参数(2),绑定的参数 3, 4 不受影响,最终调用 foo(1, 3, 4)。
参数数量内省
- get_argument_count():返回 Callable 应当接收的总参数数(含可选参数)。
bind绑定的参数会从结果中减去,unbind解绑的参数会加上; - get_bound_arguments():返回通过
bind/unbind累积绑定的参数数组(这些参数追加在调用参数之后,调用参数中右侧get_unbound_arguments_count()个已被排除); - get_bound_arguments_count():绑定参数总数,即
get_bound_arguments()数组的大小; - get_unbound_arguments_count():解绑参数总数。注意
get_bound_arguments_count()与get_unbound_arguments_count()可能同时为正数。
官方还给出了"计算有效调用参数"的完整参考实现:
func get_effective_arguments(callable, call_args):
assert(call_args.size() - callable.get_unbound_arguments_count() >= 0)
var result = call_args.slice(0, call_args.size() - callable.get_unbound_arguments_count())
result.append_array(callable.get_bound_arguments())
return result
状态查询:is_custom / is_standard / is_null / is_valid / get_object / get_method
| 方法 | 返回值 | 语义 |
|---|---|---|
| is_custom() | bool |
是否为自定义 Callable。用于:参数绑定/解绑(bind/unbind);表示内置 Variant 类型的方法(create());GDScript 的全局函数与 lambda;以及核心、GDExtension、C# 中的其他用途 |
| is_standard() | bool |
是否为标准 Callable,即 is_custom() 的反面;lambda 返回 false |
| is_null() | bool |
是否没有可调用的目标,等价于 callable == Callable() |
| is_valid() | bool |
对象存在且方法名有效,或是自定义 Callable |
| get_method() | StringName |
方法名;lambda 返回函数名或 "<anonymous lambda>" |
| get_object() | Object |
被调用的对象 |
| get_object_id() | int |
对象的实例 ID(对应 Object.get_instance_id()) |
| hash() | int |
对象的 32 位哈希值 |
两个极易混淆的语义需要特别注意(class_callable.rst):
is_null()≠not is_valid():not is_null()并不能保证 Callable 一定可以调用,判断可调用性应使用is_valid();- 布尔上下文:Callable 为 null 时在布尔上下文中求值为
false,否则恒为true; - hash 碰撞:内容相等的 Callable 哈希值一定相同,但哈希相同不代表 Callable 相等(可能碰撞);引擎对
hash()使用 32 位哈希算法。
相等性比较运算符
operator ==:两个 Callable 调用相同的自定义目标时返回true;operator !=:两个 Callable 调用不同的目标时返回true。
RPC:把 Callable 用于多人联机
Callable 还封装了多人联机的远程过程调用能力(high_level_multiplayer.rst):
- rpc():向所有已连接的对端执行 RPC;
- rpc_id(peer_id: int, ...):向指定 peer_id 执行 RPC。
调用前提是目标函数已标记为 RPC(通过 GDScript 的 @rpc 注解或 Node.rpc_config());对未标记的函数调用 rpc()/rpc_id() 会报错。典型示例:
func _ready():
if multiplayer.is_server():
print_once_per_client.rpc()
@rpc
func print_once_per_client():
print("I will be printed to the console once per each connected client.")
官方文档同时给出多个关键限制:RPC 不会序列化 Object 或 Callable 参数;收发两端的节点必须具有相同的 NodePath(即同名节点,add_child() 时建议设置 force_readable_name = true);客户端与服务端脚本中的 RPC 声明必须完全一致(校验和覆盖所有 RPC,即使当前未使用)。
C# 使用 Callable 的注意事项
C# 侧使用 Callable 存在若干差异(c_sharp_differences.rst):
- 部分方法被转换为同义属性,例如
get_method()→Method; - C# 目前仅支持以下两种 Callable:由 C#
Callable类型创建的;以及引擎 Callable 的基础版本。自定义 Callable 不受支持——带绑定信息(bind/unbind创建)或经 GDExtension API 从其他语言创建的 Callable 均属自定义; bind/unbind等方法在 C# 中未实现,官方建议改用 lambda 捕获:
string name = "John Doe";
Callable callable = Callable.From(() => SayHello(name));
void SayHello(string name)
{
// ...
}
- 跨语言连接 GDScript 信号时使用
Callable.From(...)(cross_language_scripting.rst):
myGDScriptNode.Connect("my_signal", Callable.From(MySignalHandler));
myGDScriptNode.Connect("my_signal_with_params", Callable.From<string, int>(MySignalWithParamsHandler));
小结与速查表
Callable 是 Godot 中"把函数当作值来传递"的核心载体,其能力可概括为四类:
- 构造:
Callable()、Callable(from)、Callable(object, method)、Callable.create(variant, method),以及 GDScript 的按名引用与 lambda; - 调用:
call()(同步)、callv()(数组传参)、call_deferred()(帧末延迟);注意不能使用()运算符直接调用; - 改造:
bind()/bindv()(预绑定参数,追加在调用参数之后)、unbind()(忽略调用方多余的尾部参数),链式使用时参数列表修改顺序从右往左; - 查询与远程调用:
get_method()/get_object()/get_object_id()/get_argument_count()等内省方法,以及rpc()/rpc_id()多人联机远程调用。
需要进一步掌握信号连接的完整流程,可参考 godot_interfaces.rst;想深入了解延迟调用与对象生命周期的关系,可阅读 godot_notifications.rst;多人联机场景可结合 high_level_multiplayer.rst 中的 @rpc 注解参数体系(any_peer、call_local、reliable、transfer_mode、channel)一并学习。