前置知识: Gode

Godot API 调用与对象生命周期

6 min入门

通过 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 模块时记住三条命名规则:

  1. API 以 Godot 原生 snake_case 暴露。add_child、position、_physics_process 等写法与 GDScript 完全一致,不要写成 JS 风格的驼峰。
  2. 内置类型带静态常量,例如 Vector3.ZERO,同样保持大写蛇形命名。
  3. 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() 提供字符串方法名的动态调用,松耦合但类型不安全。

参考链接