Godot API 调用与对象生命周期
通过 godot 模块使用引擎类与单例,掌握 Variant 桥接规则与被释放对象的引用检查
上一篇你已经在 godot 模块里导入过 GD、Node、Vector3,本篇把这个”TS 与 Godot 之间的唯一边界”讲透:它提供什么、命名规则是什么、值在两种世界之间如何桥接,以及最重要的一课——Godot 对象的生命周期由谁掌管。掌握这些,你才能写出既正确又高性能的 TypeScript 游戏代码。
学习目标
- 说出
godot模块的构成:Godot 类、运行时单例、内置 Variant 类型、集合类型与工具函数。 - 理解绑定由 generator 从 extension_api.json 自动生成,types/ 目录存放声明文件。
- 记住命名规则:Godot 原生 snake_case、内置类型静态常量、Packed 数组可从 JS 数组构造。
- 熟练使用 GD 命名空间的 print、printerr、is_instance_valid、var_to_str。
- 掌握类型桥接的基本规则与性能注意事项。
- 理解”Godot 拥有 Godot 对象”,学会跨帧持有引用时做有效性检查。
- 会用
call()对任意对象做动态方法调用。
godot 模块是什么
godot 模块是 TypeScript 与 Godot 引擎之间的唯一边界。它提供五类内容,全部是生成绑定(generated bindings):Godot 类(Node、Node3D、CharacterBody3D 等)、运行时单例(Engine、DisplayServer、ResourceLoader 等)、内置 Variant 类型(Vector3、Color 等)、集合类型,以及工具函数(GD 命名空间)。
这些绑定并非手写,而是由仓库 generator/ 目录的代码生成框架(Python + Jinja2)从 godot-cpp 的 extension_api.json 自动生成,TypeScript 声明文件发布在插件的 types/ 目录。这也解释了为什么补全信息与 Godot 官方文档高度一致——它们同源。
命名规则
使用 godot 模块时记住三条命名规则:
- API 以 Godot 原生 snake_case 暴露。
add_child、position、_physics_process等写法与 GDScript 完全一致,不要写成 JS 风格的驼峰。 - 内置类型带静态常量,例如
Vector3.ZERO,同样保持大写蛇形命名。 - Packed 数组(打包数组)的构造函数可以直接传入 JS 数组,例如
new PackedVector3Array([v1, v2, v3])。
官方示例:PathProbe 逐行讲解
import { Engine, GD, Node3D, PackedVector3Array, Vector3 } from "godot";
export default class PathProbe extends Node3D {
_ready(): void {
const points = new PackedVector3Array([
Vector3.ZERO,
new Vector3(0, 2, 0),
new Vector3(2, 2, 0),
]);
console.log(Engine.get_version_info());
console.log(GD.var_to_str(points));
}
}
- 导入清单里出现了四种角色:单例
Engine、工具命名空间GD、基类Node3D、内置类型PackedVector3Array与Vector3。 new PackedVector3Array([...])演示了第三条命名规则:直接用 JS 数组初始化 Packed 数组,元素混用了静态常量Vector3.ZERO与显式构造的new Vector3(...)。Engine.get_version_info()获取引擎版本信息,属于运行时单例调用。GD.var_to_str(points)把任意 Godot 值转换为 Godot 风格的字符串表示,便于日志排查。- 这里用了
console.log,因为它只是探测性输出;正式日志仍建议GD.print。
GD 命名空间的四个常用函数
GD.print(...):输出到 Godot 输出面板。GD.printerr(...):以错误形式输出,同样保证进入 Godot 输出面板。GD.is_instance_valid(ref):检查一个对象引用是否仍然有效,是处理对象生命周期的关键工具,下文详述。GD.var_to_str(value):把值转换为 Godot 风格字符串,打印复杂对象时非常直观。
类型桥接规则
TypeScript 与 Godot 是两个类型世界,中间靠 Variant(Godot 的通用动态值类型)转换。Gode 的规则是:生成绑定在转换明确时,接受并返回 Godot 数组、TypedArray、PackedArray、boxed 内置值(Vector3 这类值对象)以及部分普通 JS 值。
由此得出两条实践准则。第一,“当 API 需要 Godot 类型时,显式构造 Godot 类型”。需要 Vector3 就写 new Vector3(0, 1, 0),需要 Packed 数组就用对应构造函数,不要指望裸 JS 对象被自动理解。第二,性能敏感的高频代码要避免在紧密循环(tight loop)中频繁做 Variant 转换——每次转换都有成本,能复用就复用,能在 Godot 类型体系内完成就在体系内完成。移动平台对这类开销尤其敏感。
对象生命周期:Godot 拥有 Godot 对象
这是本篇最重要的一节。官方文档的表述是:“Godot owns Godot objects. TypeScript wrappers provide access to those objects, but they do not make Godot nodes immortal.”——Godot 拥有 Godot 对象,TypeScript 包装对象只是访问通道,不会让节点永生。
实际后果是:当 Godot 侧释放(free)了某个节点,你手里持有的 TypeScript 引用并不会让它复活,此时再调用它的方法就会出问题。因此,跨帧持有引用时要遵守 GDScript 式的规则——使用前检查有效性。官方 TargetTracker 示例:
import { GD, Node } from "godot";
export default class TargetTracker extends Node {
target?: Node;
processTarget(): void {
if (this.target && GD.is_instance_valid(this.target)) {
this.target.call("refresh");
}
}
}
逐行看:字段 target?: Node 是可选的跨帧引用;processTarget() 在使用前先判断 this.target 存在,再用 GD.is_instance_valid(this.target) 确认对象未被释放,两关都过才调用 this.target.call("refresh")。把这个模式背下来,它能替你挡住一大类”对象已被释放”的运行时崩溃。
动态调用:call()
任何 Godot 对象都支持 call("方法名", ...) 形式的动态调用,如上例的 this.target.call("refresh")。它的特点是字符串方法名、动态派发、松耦合、类型不安全——编辑器无法校验方法名是否拼写正确、参数是否匹配。适合边界场景(跨语言、跨脚本、配置驱动的调用),不适合作为日常首选。
与 GDScript/C# 生态的位置关系
站内 Godot 模块的脚本生态篇章(见本篇 frontmatter 的 related)对比过三条路线:GDScript 是内置语言、零配置、与引擎结合最紧密;C# 走 .NET 运行时;Gode 则把 TypeScript 与 npm 生态带入 Godot。选型时可参考的判断维度是团队既有经验与依赖需求:前端背景团队、需要 npm 库的项目适合 Gode;纯引擎向小项目 GDScript 上手最快。无论选哪条,本篇讲的”显式构造 Godot 类型、检查对象有效性”在 Gode 里都是必修课。
小结
godot模块是 TS 与 Godot 的唯一边界,提供 Godot 类、运行时单例、内置 Variant 类型、集合类型与工具函数的生成绑定,由 generator 从 extension_api.json 生成,声明文件在 types/ 目录。- 命名三规则:snake_case 原生命名、内置类型静态常量(如
Vector3.ZERO)、Packed 数组可从 JS 数组构造。 - GD 四件套:
print、printerr、is_instance_valid、var_to_str。 - 类型桥接:转换明确时才接受 Godot 数组、TypedArray、PackedArray、boxed 内置值与部分 JS 值;需要 Godot 类型时显式构造;高频代码避免循环内频繁 Variant 转换。
- Godot 拥有 Godot 对象,TS 包装不延长存活;跨帧持有引用必须用
GD.is_instance_valid检查后再使用。 call()提供字符串方法名的动态调用,松耦合但类型不安全。