RobinDevNotes头像
关注
用 godot-rust 给 Godot 写 Rust 扩展封面图

用 godot-rust 给 Godot 写 Rust 扩展

知识点总结

摘要

godot-rust(代号 gdext)是 Godot 4 的 Rust 绑定,通过官方的 GDExtension 接口,让你用 Rust 写游戏逻辑、编辑器插件和自定义节点,不用改引擎源码,也不用重新编译 Godot。今年 3 月发布的 v0.5 带来了类型化字典、非空对象参数和随处可用的 Rust 枚举,之后的 0.5.x 补丁一路更新到 8 月的 v0.5.5,并加入了 Godot 4.7 的 API 支持,已经可以用在真实项目里。

背景与问题

Godot 的 GDScript 上手快,但游戏做大之后,常见的痛点是这几个:

  • 寻路、物理、程序化生成这类热点代码,脚本语言跑不动
  • 项目变大后,动态类型让重构变得提心吊胆
  • 想复用 Rust 生态里现成的库,比如网络、序列化、ECS

以前的办法是改 C++ 模块,要自己编译整个引擎,维护成本很高。Godot 4 引入 GDExtension 之后,扩展被编译成独立的动态库,由编辑器在运行时加载,引擎本身不用动。

GDExtension 本身是一套 C API,直接写并不舒服。gdext 做的事,就是把它包成符合 Rust 习惯的类型安全接口。它是社区驱动的开源项目,许可证是 MPL-2.0,可以用于商业和闭源游戏,只有改动 godot-rust 本身时才需要公开修改。

注意区分:老的 gdnative 面向 Godot 3,已经不再积极维护,新项目直接用 gdext。

核心思路与优势

1. 类型安全,贴近 Rust 写法

用派生宏声明一个类,用属性宏暴露方法和信号,继承关系通过 Base<T> 表达。覆盖引擎虚函数时,实现对应的 trait 即可,比如 ISprite2D。

2. 和 GDScript 无缝互通

Rust 写的类注册后,在编辑器里和内置节点一样出现,可以直接拖进场景,GDScript 也能调用它的方法、连接它的信号。所以不必整个项目重写,只把热点部分换成 Rust。

3. 热重载

官方在持续集成里测试热重载,改完 Rust 代码重新编译,编辑器不必重启就能加载新库。这项能力从 Godot 4.2 开始才有,正好也是当前 gdext 的最低版本要求。

4. v0.5 这一轮的关键更新

下面是官方 v0.5 发布说明里集中介绍的内容,其中安全防护分档和自定义单例其实在 0.4.3、0.4.4 补丁里就已先行上线:

  • 安全防护分三档:Strict(Debug 构建默认,尽可能多地抓错)、Balanced(Release 构建默认,快且安全)、Disengaged(牺牲安全换极致性能,出错即未定义行为,官方不建议轻易使用)
  • 类型化字典 Dictionary<K, V>,新增 dict! 和 idict! 宏
  • 去掉了 Gd<T> 内部的互斥锁,对象访问更快;Callable::from_fn() 也做了明显优化
  • 引擎 API 中标注为非空的对象参数和返回值直接用 Gd<T>,不再是 Option<Gd<T>>
  • Rust 枚举可以用在 #[func]、#[signal] 和 #[var] 上
  • 用 #[class(singleton)] 定义自己的单例
  • 一个 Rust 扩展可以作为 rlib 依赖另一个 Rust 扩展
  • 对齐 Godot 4.6 的 API,并支持 Rust 2024 Edition
  • 破坏性变更:#[var] 生成的 getter/setter 默认不再能在 Rust 里直接调用,需要写成 #[var(pub)]

v0.5 之后的补丁同样值得关注:v0.5.4(6 月)新增 api-4-7 特性以支持 Godot 4.7,v0.5.5(8 月)让 #[func(virtual)] 支持 async fn,并缓存了单例、降低了引用计数开销。

5. 版本兼容规则清晰

扩展可以被任意 Godot 版本加载,前提是运行时版本不低于编译时选定的 API 版本。用 4.2 编译的扩展能在 4.2.1 和 4.3 上跑,用 4.3 编译的则不能在 4.2.1 上跑。所以想兼容更多用户,就选较低的 API 版本编译。gdext 0.4 及之后的版本,最低要求 Godot 4.2。

面向人群

  • 已经在用 Godot,但被 GDScript 性能卡住的开发者
  • 熟悉 Rust,想用它做 2D、3D 游戏,又不想从零搭引擎的人
  • 想做编辑器插件、自动化工具或程序化生成器的开发者
  • 看过 Bevy 与 Godot 的路线之争,想要"Godot 成熟工具链加 Rust 类型系统"折中方案的人

需要提前知道的是:目前 Linux、Windows、macOS 这三个桌面平台是主力支持的,Wasm(Web 导出)、Android、iOS 还属于实验性,文档和工具链都不完善。以 Web 导出为例,目前还没有预构建产物,需要 nightly Rust、Emscripten 和自定义 API 配置,门槛不低。项目也会不定期引入破坏性变更,通常改动不大,并附有迁移指南。

实践步骤

下面走一遍最小可运行的流程。

第一步:创建 Rust 库

在 Godot 项目旁边新建一个 Rust 工程,Cargo.toml 里把库类型设为 cdylib,并添加 godot 依赖:

[package]
name = "rust_project"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
godot = "0.5"

cdylib 表示编译成 C 风格的动态库,Godot 才能加载它。

第二步:写入口

src/lib.rs 里只需要注册一个扩展入口:

use godot::prelude::*;

struct MyExtension;

#[gdextension]
unsafe impl ExtensionLibrary for MyExtension {}

第三步:写一个自定义节点

下面这个 Player 继承自 Sprite2D,每个物理帧旋转一点,并提供一个能加速的方法和一个信号:

use godot::prelude::*;
use godot::classes::{ISprite2D, Sprite2D};

#[derive(GodotClass)]
#[class(base=Sprite2D)]
struct Player {
    speed: f64,
    angular_speed: f64,
    base: Base<Sprite2D>,
}

#[godot_api]
impl ISprite2D for Player {
    fn init(base: Base<Sprite2D>) -> Self {
        Self {
            speed: 400.0,
            angular_speed: std::f64::consts::PI,
            base,
        }
    }

    fn physics_process(&mut self, delta: f64) {
        let radians = (self.angular_speed * delta) as f32;
        self.base_mut().rotate(radians);
    }
}

#[godot_api]
impl Player {
    #[func]
    fn increase_speed(&mut self, amount: f64) {
        self.speed += amount;
        self.signals().speed_increased().emit();
    }

    #[signal]
    fn speed_increased();
}

几个要点:

  • #[derive(GodotClass)] 加 #[class(base=Sprite2D)] 声明这是一个 Godot 类及其基类
  • 字段 base: Base<Sprite2D> 是访问基类的入口,通过 base_mut() 调用父类方法
  • impl ISprite2D 是覆盖引擎虚函数的地方,这里实现了 init 和 physics_process
  • #[func] 把方法暴露给 GDScript 和编辑器,#[signal] 声明信号,用 self.signals() 发射

第四步:写 .gdextension 文件

在 Godot 项目里新建一个 .gdextension 文件,告诉引擎去哪里找动态库:

[configuration]
entry_symbol = "gdext_rust_init"
compatibility_minimum = 4.2
reloadable = true

[libraries]
linux.debug.x86_64 = "res://../rust/target/debug/librust_project.so"
linux.release.x86_64 = "res://../rust/target/release/librust_project.so"
windows.debug.x86_64 = "res://../rust/target/debug/rust_project.dll"
windows.release.x86_64 = "res://../rust/target/release/rust_project.dll"
macos.debug = "res://../rust/target/debug/librust_project.dylib"
macos.release = "res://../rust/target/release/librust_project.dylib"

库名要和 Cargo.toml 里的包名一致。reloadable = true 打开热重载,路径按你的目录结构调整。

第五步:编译并在编辑器里使用

cargo build

编译完成后打开 Godot 项目,在"创建节点"对话框里就能搜到 Player,和内置节点用法完全一样。以后每次修改 Rust 代码,重新执行 cargo build,切回编辑器窗口时它会自动重新加载扩展。

什么时候该用,什么时候不必

适合用 Rust 的部分:寻路、碰撞、程序化地图、大量单位的逻辑更新、需要长期维护的核心系统。

继续用 GDScript 的部分:UI 逻辑、关卡脚本、一次性的小玩法、需要快速试错的原型。

比较务实的做法是混合使用:GDScript 负责胶水和内容迭代,Rust 负责性能和稳定性要求高的核心。两者可以在同一个项目里自由互调,不必二选一。

我的看法

在 Godot 和 Bevy 的选择里,godot-rust 给出了第三条路:保留 Godot 的编辑器、场景系统和导出流程,同时把最吃性能、最需要类型约束的部分交给 Rust。

不过也要认清现状:它还没有到 1.0,API 会继续调整,移动端和 Web 端还在完善。如果你的项目主要面向桌面平台,并且团队愿意投入学习 Rust,现在就是很好的时机。如果目标是尽快上手做原型,先用 GDScript,等真正遇到性能瓶颈再把那一块换成 Rust 也不迟。

项目地址

github.com/godot-rust/gdext

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/yaoge1234/article/details/166571463

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--