Godot 引擎 Callable 类型完全指南:函数引用、信号回调与 RPC 调用

原创2026-10-03 21:18:52738 阅读
文章标签:文档教程

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):

  1. 局部变量按值捕获一次:lambda 创建时捕获的值不会因外层后续重新赋值而更新;
  2. lambda 内部对外层局部变量的重新赋值会被隐式遮蔽(触发 CONFUSABLE_CAPTURE_REASSIGNMENT 警告),退出 lambda 后外层变量保持不变;
  3. 引用类型(数组、字典、对象)例外:只要不重新赋值变量本身,内容修改会在 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(通过 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)
{
    // ...
}
myGDScriptNode.Connect("my_signal", Callable.From(MySignalHandler));
myGDScriptNode.Connect("my_signal_with_params", Callable.From<string, int>(MySignalWithParamsHandler));

小结与速查表

Callable 是 Godot 中"把函数当作值来传递"的核心载体,其能力可概括为四类:

  1. 构造:Callable()、Callable(from)、Callable(object, method)、Callable.create(variant, method),以及 GDScript 的按名引用与 lambda;
  2. 调用:call()(同步)、callv()(数组传参)、call_deferred()(帧末延迟);注意不能使用 () 运算符直接调用;
  3. 改造:bind()/bindv()(预绑定参数,追加在调用参数之后)、unbind()(忽略调用方多余的尾部参数),链式使用时参数列表修改顺序从右往左;
  4. 查询与远程调用: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)一并学习。

登录后查看全文
godot-docs