Gode:用 JavaScript 与 TypeScript 写 Godot
安装 Gode 插件,用 TypeScript 与 npm 生态开发 Godot 节点脚本,掌握互操作元数据调试与导出
上一篇我们了解了 Godot 的官方脚本路线:GDScript、C# 与 C++ GDExtension。如果你来自前端世界,还有第四条路:Gode——一个为 Godot 提供 JavaScript 与 TypeScript 脚本支持的开源项目。它让你用现代 TypeScript 编写 Godot 节点脚本,从 godot 模块导入引擎类,并直接调用 npm 上庞大的包生态。
本篇是 Gode 的完整上手指南:从项目定位与实现原理讲起,依次覆盖安装、第一个脚本、场景实例化、与 GDScript 的互操作、元数据声明(导出属性、信号、RPC)、Godot API 调用细节、npm 工程化、断点调试与项目导出,最后汇总当前已知限制。
学习目标
- 理解 Gode 的定位:内嵌 Node.js 的 GDExtension 插件,为什么常规开发不需要安装 Node.js。
- 完成安装四步,并能在 TypeScript 未出现在语言列表时自行排查。
- 编写第一个 TypeScript 脚本,分清
GD.print与console.log的用途。 - 在 TypeScript 中加载场景、实例化节点,并让 GDScript 直接调用你的方法。
- 使用
static exports/static signals/static rpc_config与@GlobalClass声明元数据。 - 配置 gode.json 打开 V8 Inspector 断点调试,用 VS Code 附加调试器。
- 理解导出时对 Node.js/npm 的要求差异,以及
.gode/目录的正确处理方式。
Gode 是什么
Gode 由 GodotHub 组织开发,仓库描述为 “Godot with JavaScript / TypeScript & NodeJS”。官方文档站首页的标语概括了它的能力:用现代 TypeScript 写 Godot 节点脚本、从 godot 模块导入 Godot 类、使用强大的 npm 生态。
几个基本事实:
- 主语言是 C++,许可证为 MIT,属于完全开源的项目;
- 定位是”Godot 引擎的 JavaScript/TypeScript 脚本支持,运行在所有原生平台”;
- 官方中文文档站:https://godothub.com/oss/gode/zh/ ;
- 官方演示项目 gode-tps-demo 是 Godot 官方第三人称射击示例 tps-demo 的 TypeScript 移植,适合作为”别人怎么写”的参考;
- 撰写本文时插件最新版本为 2.4.4(2026-09-09 发布)。
实现原理:内嵌 Node.js 与 GDExtension
理解 Gode 的工作方式,能帮你预判它的能力边界:
- 内嵌的是 Node.js,不是裸 V8。Gode 集成 libnode(Node.js 的库形态),V8 引擎随 Node.js 间接引入。这意味着
console、事件循环等 Node 能力都是可用的。 - 以 GDExtension 形式接入。插件二进制清单中的关键配置是
compatibility_minimum = "4.6.2",即最低要求 Godot 4.6.2;官方示例工程按 Godot 4.7 制作。
[configuration]
entry_symbol = "gode_runtime_library_init"
compatibility_minimum = "4.6.2"
- 编译模型:
.ts源码在编辑、运行、导出时自动编译为 ESM(ECMAScript Module)JavaScript,输出到res://.gode/build/typescript/。场景文件始终引用原始.ts文件——生成的 JavaScript 是内部细节,不要手动引用它。 - 内置 TypeScript 编译器:插件自带编译器(位于
addons/gode/tsc/),因此常规开发不需要安装 nodejs、npm 等任何工具。只有当项目根目录存在package.json或node_modules、你想使用 npm 包时,才需要 Node.js 工具链。
平台支持
| 平台 | 最低版本 |
|---|---|
| Windows 10+ | 额外依赖 node.dll(Node-API forwarder) |
| Android 9+ | 支持 |
| macOS 10.15+ | 支持 |
| iOS 16+ | 支持 |
| Linux(Ubuntu 22+) | 支持 |
Gode 覆盖 Godot 的全部原生平台,但不包含 Web 导出——这与 C# 的限制一致,内嵌运行时无法在浏览器里工作。Windows 导出包会额外携带 node.dll,移动平台则对依赖体积更敏感。
安装与项目结构
Gode 以 Godot addon(插件)形式分发,压缩包内同时包含运行时与 TypeScript 编译器。安装四步:
- 从 GitHub 仓库的 releases 最新页下载插件包
gode.zip; - 把压缩包中的
gode目录解压到项目的addons/目录(不存在则先创建); - 打开 Godot,进入
项目 > 项目设置 > 插件,启用gode; - 启用后,新建 Godot 脚本时语言列表里选择
TypeScript。
安装完成后,插件目录结构如下:
my_project/
addons/
gode/
binary/
config/
gode.gd
gode.gd.uid
plugin.cfg
runtime/
tsc/
types/
各目录用途:
binary/:运行时 GDExtension 清单与各平台原生库;config/:内置模板(tsconfig.json、gode.json 等);gode.gd:插件入口脚本;runtime/:Godot 侧运行时辅助(插件还会注入一个名为 EventLoop 的 autoload);tsc/:内置的 TypeScript 编译器;types/:生成的 Godot API TypeScript 声明文件,提供编辑器补全与类型检查。
故障排查:语言列表里没有 TypeScript
依次检查:
- 插件路径是否严格为
res://addons/gode(目录名、层级都不能变); addons/gode/plugin.cfg是否存在;- 启用插件后是否重启了编辑器;
binary/下是否有你当前操作平台的二进制文件。
第一个 TypeScript 脚本
启用插件后,新建脚本选择 TypeScript 语言,然后像普通 Godot 脚本一样把它挂到节点上。官方的 Hello 示例只有五行:
import { GD, Node } from "godot";
export default class Hello extends Node {
_ready(): void {
GD.print("Hello from Gode");
}
}
逐行拆解:
import { GD, Node } from "godot":Godot 的类、单例与工具函数不会注入全局作用域,必须从godot模块显式导入。这是与 GDScript 最大的书写差异之一。export default class Hello extends Node:每个脚本必须默认导出一个类,且该类必须继承某个 Godot 基类(这里是Node)。不满足这两点,脚本无法被 Godot 识别。_ready(): void:Godot 标准生命周期回调,节点进入场景树时调用;_physics_process(delta: number)等其他回调命名与 GDScript 一致,只是换成了 TypeScript 签名。GD.print("Hello from Gode"):走 Godot 的输出 API,保证出现在 Godot 编辑器的输出面板。
GD.print 与 console.log 的区别
Gode 环境里有两个”打印”:
GD.print(...)/GD.printerr(...):Godot 输出 API,日志一定进入编辑器输出面板,适合游戏运行日志;console.log(...):Node 的 console API,属于终端诊断手段,Gode 不保证它们会镜像到 Godot 输出面板。
调游戏逻辑看输出面板用前者;调 npm 包、异步任务等 Node 侧问题用后者。
加载场景与实例化
下面的 MarkerSpawner 示例展示了 TypeScript 版”加载 PackedScene 并实例化”的完整流程:
import { DisplayServer, GD, Node3D, ResourceLoader, Vector3 } from "godot";
export default class MarkerSpawner extends Node3D {
_ready(): void {
GD.print(DisplayServer.get_name());
const scene = ResourceLoader.load("res://scenes/marker.tscn");
const marker = scene.instantiate();
marker.position = new Vector3(0, 1, 0);
this.add_child(marker);
}
}
要点:
ResourceLoader.load(...)对应 GDScript 的load(),返回场景资源;scene.instantiate()实例化为节点。marker.position = new Vector3(0, 1, 0):内置 Variant 类型用new构造;Vector3.ZERO这类静态常量则直接挂在类上,不需要实例化。this.add_child(marker):Godot API 保持原生 snake_case 命名(add_child、position),不会转成驼峰。- 实例化的节点必须
add_child挂进场景树才会参与运行,这与 GDScript 的规则完全相同。
公开方法与跨脚本调用
TypeScript 类上的公开实例方法会自动对 Godot 脚本系统可见,不需要任何注册动作:
import { Node } from "godot";
export default class Health extends Node {
value = 100;
damage(amount: number): void {
this.value = Math.max(0, this.value - amount);
}
}
GDScript 侧可以像调用普通节点脚本一样直接调用:
$Health.damage(25)
本地模块的导入遵循 ESM 习惯,不写文件后缀:
import { clampHealth } from "./combat/math";
注意:项目脚本只允许 TypeScript 一种语言;.cjs 仅作为 CommonJS 运行时的 sidecar 格式用于互操作场景。静态方法则不同——自 2.4.0 起(破坏性变更),静态方法不再暴露在实例上,只能通过脚本资源 API 调用:
var script = preload("res://scripts/damage_table.ts")
if script.has_static_method("baseDamage"):
var amount = script.call_static("baseDamage", 3)
与 GDScript 互操作
混合语言项目里,两个方向的成本不对称:
- GDScript 调 TypeScript:公开实例方法直接调用,体验与调用 GDScript 无异;
- TypeScript 调 GDScript:使用 Godot 通用的
call(),传字符串方法名,属于动态调用——松耦合但类型不安全。
TypeScript 侧:
import { Node } from "godot";
export default class PlayerLogic extends Node {
say_hello(name: string): string {
return `hi ${name}`;
}
call_gd_target(): unknown {
const target = this.get_node("../GdTarget");
return target.call("some_method", "from TypeScript");
}
}
GDScript 侧:
extends Node
func _ready() -> void:
var ts_result = $"../TsPlayer".say_hello("Godot")
print(ts_result) # hi Godot
var gd_result = $"../TsPlayer".call_gd_target()
print(gd_result) # gd received from TypeScript
func some_method(message: String) -> String:
return "gd received " + message
官方文档的建议是:对 UI、场景调度、多人事件和跨团队的 gameplay 系统,优先用信号(Signal)作为跨语言边界,代替跨语言直接调用,降低耦合。
TypeScript 脚本直接作 Autoload
默认导出类继承 Godot 基类后,.ts 脚本可以像 GDScript 一样直接注册为 Autoload 单例。在 project.godot 中:
[autoload]
Settings="*res://menu/settings.ts"
其他脚本访问它与访问普通 Autoload 没有区别:
const settings = this.get_node("/root/Settings");
settings.load_settings();
元数据:导出属性、信号、RPC、全局类与 Tool
GDScript 用注解(@export、@signal、@rpc)声明编辑器元数据;TypeScript 没有等价注解语法,Gode 的方案是静态类成员。
导出属性(static exports)
import { Node3D, Vector3 } from "godot";
export default class Spawner extends Node3D {
static exports = {
spawn_count: { type: "int", default: 3 },
spawn_offset: { type: "Vector3" },
enabled: { type: "bool" },
};
spawn_count = 3;
spawn_offset = new Vector3(0, 1, 0);
enabled = true;
}
type使用 Godot Variant 类型名(“String”、“int”、“float”、“bool”、“Vector3”、“Object” 等),可附加hint、hint_string与default;- 声明后属性会出现在 Inspector 中并可序列化;
- 支持集合类型:
Array<T>、T[]、GDArray<T>、Dictionary<K, V>、Map<K, V>、Record<K, V>与 type alias。
自定义信号(static signals)
import { Node } from "godot";
export default class Menu extends Node {
static signals = {
replace_main_scene: [{ name: "resource", type: "Object" }],
quit: [],
};
_on_start_pressed(): void {
this.emit_signal("replace_main_scene", this.next_scene);
}
}
连接 Godot 已有信号则用 connect:
button.connect("pressed", () => {
console.log("button pressed");
});
RPC 配置(static rpc_config)
import { CharacterBody3D } from "godot";
export default class Robot extends CharacterBody3D {
static rpc_config = {
hit: { rpc_mode: "authority", call_local: true },
play_effect: {
rpc_mode: "any_peer",
call_local: true,
transfer_mode: "reliable",
channel: 0,
},
} satisfies RpcConfig;
hit(): void { this.health -= 1; }
play_effect(): void { this.effect.restart(); }
}
rpc_mode 取值:“authority”、“any_peer”、“disabled”;transfer_mode 取值:“reliable”、“unreliable”、“unreliable_ordered”。
全局类与 Tool 脚本
import { Node3D } from "godot";
@GlobalClass
export default class EnemySpawner extends Node3D {
}
@GlobalClass 装饰器(2.4.3 新增)把类注册为全局类,仅对 default export 生效。编辑器内运行的 Tool 脚本则用静态字段声明:
export default class PreviewRig extends Node3D {
static tool = true;
}
Godot API 调用细节
godot 模块是 TypeScript 与 Godot 之间唯一的边界,包含 Godot 类、运行时单例、内置 Variant 类型、集合类型与工具函数的生成绑定。综合示例:
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));
}
}
三条必须内化的规则:
- API 保持 snake_case:方法与属性(
add_child、position)与文档中的 GDScript 名称一致,只有 TypeScript 语法不同。 - 静态常量挂在类上:如
Vector3.ZERO。 - 对象生命周期由 Godot 拥有:“Godot owns Godot objects. TypeScript wrappers provide access to those objects, but they do not make Godot nodes immortal.”——TypeScript 包装对象不会让节点免于释放。跨帧持有引用前,用
GD.is_instance_valid()检查有效性:
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");
}
}
}
性能方面:高频 gameplay 代码要避免在紧密循环(tight loop)中频繁做 Variant 转换,必要时参考上一篇 C# 的做法——把跨边界读写挪出循环。
npm 工程化
Gode 让 npm 生态触手可及,但有明确的启用条件:只有项目根目录存在 package.json 或 node_modules 时,才启用 npm 解析,也才要求系统安装 Node.js/npm 工具链。初始化命令:
npm init -y && npm install lodash
或使用 pnpm:
pnpm init && pnpm add lodash
在脚本中使用 npm 包:
import { Node } from "godot";
import lodash from "lodash";
export default class PackageDemo extends Node {
_ready(): void {
console.log(lodash.camelCase("hello gode"));
}
}
加载规则:
- ESM 包按 ESM 方式加载;
- CommonJS 包会被桥接,同时支持 default import 与 named import;
- pnpm 用户必须在
.npmrc中配置node-linker=hoisted(pnpm 默认的符号链接结构不被运行时识别);pnpm 10 及以上还需在 pnpm-workspace.yaml 中用onlyBuiltDependencies批准构建脚本; - 原生
.node模块会在运行时按需物化(materialize)到user://.gode/npm/node_modules;Gode 不审计包的内部内容,引入前请自行评估。
官方的克制建议同样值得记住:不要为一个很小的 gameplay 辅助函数引入大包;每个目标平台都要实测,并审查依赖体积——移动平台对此尤其敏感。更多细节见官方 npm 指南:https://godothub.com/oss/gode/zh/guides/npm-packages/ 。
TypeScript 配置与编译
Gode 自动读取项目根目录的 res://tsconfig.json;不存在时会从内置模板自动创建。默认模板如下:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"isolatedModules": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"types": []
},
"include": ["**/*.ts", "**/*.tsx", "**/*.d.ts"],
"exclude": ["node_modules", ".godot", ".gode", "addons/gode/tsc"]
}
编译时机:编辑、运行、导出时都会自动编译为 ESM JavaScript,输出到 res://.gode/build/typescript/。编译失败时,TypeScript 诊断信息会出现在 Godot 输出面板——报错后优先去那里看第一条错误。
调试
日志之外,Gode 支持真正的断点调试:通过 Node/V8 Inspector 协议,VS Code 或 Chrome DevTools 可以附加到运行中的游戏。该功能默认关闭,需要在项目根目录的 res://gode.json 中开启:
{
"debug": {
"inspector": {
"enabled": false,
"host": "127.0.0.1",
"port": 9229,
"waitForDebugger": false,
"breakOnStart": false,
"sourceMaps": true,
"logUrl": true,
"autoIncrementPort": true,
"maxPortRetries": 20,
"allowInRelease": false
}
}
}
关键字段的语义:
enabled:总开关,调试时改为true;port:Inspector 监听端口,默认 9229;端口被占用时按autoIncrementPort与maxPortRetries自动顺延;sourceMaps:启用后使用内联 source map,断点能映射回.ts源码行;breakOnStart/waitForDebugger:分别在启动即中断、等待调试器附加,用于捕捉初始化阶段的问题;allowInRelease:默认false,release 导出中 Inspector 保持禁用。
VS Code 侧在 .vscode/launch.json 中添加 attach 配置:
{
"type": "node",
"request": "attach",
"name": "Attach to Gode",
"address": "127.0.0.1",
"port": 9229,
"protocol": "inspector",
"sourceMaps": true,
"sourceMapPathOverrides": {
"res://*": "${workspaceFolder}/*"
},
"skipFiles": [
"<node_internals>/**",
"**/addons/gode/**",
"**/.gode/build/**"
]
}
注意:release 导出会移除内联 source map;异步错误建议在关键入口用 try/catch 记录后再重抛,避免丢失上下文。
导出项目
Gode 项目走 Godot 标准导出流程(见前置篇),插件会自动插入两步:
- 先编译 TypeScript:生成的 ESM JavaScript 注入
res://.gode/build/typescript/,Debug 导出附带 source map,Release 只含运行时 JavaScript; - 按平台保留运行时原生文件(例如 Windows 的
node.dll);有 npm 依赖的项目会同时打包 node_modules 快照。
对工具链的要求因项目而异:
- 无 npm 依赖的项目:导出不需要系统安装 Node.js 或 npm,插件自带的编译器即可完成全部工作;
- 有 npm 依赖的项目:要求
node与npm在 PATH 中可用。
依赖项目的导出行为可在根目录 gode.json 中配置:
{
"export": {
"npm": {
"exportDependencies": true,
"requireTools": true,
"includeManifests": true,
"includeNodeModules": true,
"excludePaths": ["node_modules/.cache", "node_modules/.bin"],
"extraIncludePaths": []
}
}
}
各字段语义:exportDependencies 控制是否导出依赖;requireTools 控制是否强制要求本机工具链;includeManifests/includeNodeModules 分别决定是否打包清单文件与 node_modules;excludePaths 排除指定路径;extraIncludePaths 追加额外包含路径。
最后是版本控制约定:提交 TypeScript 源码、addons/gode 发布文件、tsconfig.json、需要的 gode.json、package.json 与 lockfile(建议提交);忽略 .godot/ 与 .gode/。永远不要把生成的 JavaScript 挂到场景上,也不要把 res://.gode/build/typescript/ 提交进仓库——场景引用的一直是 .ts 原文件。
已知限制汇总
使用 Gode 前请把这些约束纳入技术选型:
- TypeScript 调 GDScript 只能走字符串方法名的动态
call(),松耦合但类型不安全; - 高频 gameplay 代码要避免在紧密循环中频繁做 Variant 转换;
- Windows 导出额外包含
node.dll;移动平台对依赖体积与运行时资源敏感; - 断点调试默认关闭;release 导出没有 source map;
console.log不保证进入 Godot 输出面板; - 项目脚本只能是 TypeScript;Godot API 符号需要显式 import;Godot 对象的生命周期由 Godot 拥有,跨帧引用需有效性检查;
- 不支持 Web 导出(Gode 运行在原生平台)。
小结
- Gode 是 GodotHub 出品的开源项目(MIT、C++),通过内嵌 Node.js(libnode)为 Godot 提供 JavaScript/TypeScript 脚本支持,以 GDExtension 接入,最低要求 Godot 4.6.2,覆盖全部原生平台。
- 插件内置 TypeScript 编译器,
.ts在编辑、运行、导出时自动编译为 ESM JavaScript 输出到res://.gode/build/typescript/;场景始终引用.ts原文件;常规开发不需要安装 Node.js/npm。 - 脚本必须默认导出继承 Godot 基类的类;Godot 符号需从
godot模块显式导入;GD.print保证进输出面板,console.log是 Node 侧诊断。 - 公开实例方法自动对 Godot 可见;跨语言调用建议以信号为边界;TS 脚本可直接作 Autoload。
- 元数据通过静态成员声明:
static exports(Variant 类型名 + default/hint_string)、static signals、static rpc_config、static tool = true,以及仅对 default export 生效的@GlobalClass。 - npm 解析仅在根目录存在
package.json/node_modules时启用;pnpm 必须配node-linker=hoisted;不要为小功能引入大包。 - 调试用 gode.json 开启 V8 Inspector(默认端口 9229),VS Code 以 attach 方式连接;release 导出无 source map。
- 导出:无依赖项目不需要 Node.js/npm,有依赖项目要求工具链在 PATH;
.gode/生成输出不入库、不挂场景。