Featured image of post 记一次Godot调用Rust

记一次Godot调用Rust

前言

注意到Godot这款游戏引擎是之前用rust的bevy游戏引擎写游戏时太过于生理不适,让我在网上去寻找别的方式来开发游戏,经过一番Google,我最后得到了Cocos2d,unity,unreal engine,Godot这几个比较成熟又比较有人气及有成功商业化例子的引擎,一开始我选择了Unity,但是我不会C#,这就比较难受了,unreal engine对于我的用途有点过于庞大了,Cocos2d虽然合适,但后面对3D支持貌似不太好,于是最后将目光投向了Godot。

插曲

但是Godot又不是完全不用学东西,GDScript是Godot自己创造的一套编程语言,轻量且完全对于游戏开发量身定制,不过好在比较好学,但正巧在考虑Godot时他正在Preview 4.0版本,对于4.0版本有着很多的优化和改动,所以待到4.0推出后我正式入坑了Godot。

为什么要选择Godot + Rust混合开发?

1. 将游戏逻辑与关键算法分离

GDScript我现在一般会用于编写游戏逻辑(如控制,画面刷新等),将一些不太适合GDScript的算法分离开,做到分开维护,这样让工作变得井井有条。

2. 最大化利用机器性能

将关键算法利用编译型语言编译成原生库可以最大程度利用性能,避免脚本语言边运行边解释的性能损耗。

3. 利用编程语言现有的生态

Rust目前有很多性能强的优秀crate,开箱即用,可以避免二次开发,减轻开发负担。

4. 作为rustacean的执念

作为一个rust程序员,用rust编写程序的执念相当强,所以也算一点私心(

准备工作

  1. Godot Engine 4.x
  2. VSCode + rust-analyzer

由于4.0发生了许多的变动,例如跨语言调用由GDNative变成了GDExtension,本文章主 要探讨GDExtension,之后的Godot相关也会注重于4.X版本

开始编码

首先建立一个我们要开发的Godot游戏的工程,笔者这里已经提前建立好了,我将文件夹结构组织如下

  • HelloWorld/
    • assets/
    • class/
    • rust/
    • scene/

其中我们将把rust工程存放于rust文件夹中

然后我们进入rust文件夹,使用命令建立一个lib工程,并使用vscode打开他

1
2
3
cargo new hello --lib
cd hello
code .

编辑cargo.toml,将该工程设置为编译为c动态库,在[dependencies]上加入

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

并在[dependencies]下加入godot crate

1
2
[dependencies]
godot = { git = "https://github.com/godot-rust/gdext", branch = "master" }

打开lib.rs,可以看到cargo已经帮你提前写好的库模板,将其删除,加入我们自己的代码。

首先godot现在使用GDExtension加载外部库,所以我首先需要在rust中给godot暴露出一个进入点。

1
2
3
4
5
6
use godot::prelude::*;

struct HelloWorld;

#[gdextension]
unsafe impl ExtensionLibrary for HelloWorld {}

godot-rust团队通过方便的宏为我们隐去了大部分繁琐的操作,只需要建立一个空结构体,将ExtensionLibrary trait附加在该结构体上,并标注#[gdextension]即可

在GDScript中,每一个脚本都是一个类,一个个类在游戏中起着自己的作用,相应的,rust也需要遵守这样的游戏规则,但是rust中并没有类,类是数据和方法的结合体,在rust中我们可以用结构体去替代,在这里我们新建一个结构体,来假装我们有一个GD脚本。

1
2
3
4
5
6
7
8
#[derive(GodotClass)]
#[class(base=Node2D)]
struct ImAClass {
    #[base]
    any_name_you_like: Base<Node2D>,

    this_is_your_variable: isize
}

通过godot crate的宏,我们能将结构体以GD类的方式暴露给Godot,上两行属性标注表明了这个结构体是一个继承了类类型Node2D的GD类,成员中我们必须要声明一个表明基类的成员变量,这样Godot才可以通过这个结构体访问这个基类,之后就可以随性所欲的声明自己需要的变量,例如,速度等。

在Godot中,类具有许多虚方法,如_init,_process等需要用户自己去实现,那么我们的结构体也不例外,所以我们需要对其实现相关的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#[godot_api]
impl Node2DVirtual for ImAClass {
    fn init(node: Base<Node2D>) -> Self {
        godot_print!("Hello World!");

        Self {
            this_is_your_variable: 0,
            any_name_you_like: node
        }
    }
}

在godot中,普通的println!()无法使用,需要使用godot crate提供的godot_print!()宏。除了godot的内置函数外,我们如果还想实现自己的方法该怎么办呢?答案是其实和上面一样

1
2
3
4
5
6
7
8
9
use godot::buitin::*;

#[godot_api]
impl ImAClass {
    #[func]
    fn say_your_name(name: GodotString) -> GodotString {
        return GodotString::from(format!("Hello {} from rust", name.to_string()));
    }
}

注意,我们这里看到了新东西!这个GodotString是什么呀?嘿嘿,你需要意识到rust和GDScript是有着不同类型系统的两套语言,所以你需要做一定的类型转换才能让两套语言“畅聊无阻”,在这里我们将godot中的string通过to_string()方法转换为了rust中的字符串类型,然后通过rust的format!()宏将rust字符串转变为了Godot中的string类型。#[func]将我们的say_your_name函数暴露给了godot,以供我们调用

最后总代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use godot::builtin::*;
use godot::prelude::*;

struct HelloWorld;

#[gdextension]
unsafe impl ExtensionLibrary for HelloWorld {}

#[derive(GodotClass)]
#[class(base=Node2D)]
struct ImAClass {
    #[base]
    any_name_you_like: Base<Node2D>,

    this_is_your_variable: isize,
}

#[godot_api]
impl Node2DVirtual for ImAClass {
    fn init(base: Base<Self::Base>) -> Self {
        godot_print!("Hello World!");

        Self {
            any_name_you_like: base,
            this_is_your_variable: 0,
        }
    }
}

#[godot_api]
impl ImAClass {
    #[func]
    fn say_your_name(name: GodotString) -> GodotString {
        return GodotString::from(format!("Hello {} from rust", name.to_string()));
    }
}

最后Cargo,启动!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
PS > cargo build
   Compiling glam v0.23.0
   Compiling godot-ffi v0.1.0 (https://github.com/godot-rust/gdext?branch=master#09324179)
   Compiling godot-core v0.1.0 (https://github.com/godot-rust/gdext?branch=master#09324179)
   Compiling godot v0.1.0 (https://github.com/godot-rust/gdext?branch=master#09324179)
   Compiling hello v0.1.0 (D:\CodeDir\HelloWorld\rust\hello)
warning: fields `any_name_you_like` and `this_is_your_variable` are never read
  --> src\lib.rs:13:5
   |
11 | struct ImAClass {
   |        -------- fields in this struct
12 |     #[base]
13 |     any_name_you_like: Base<Node2D>,
   |     ^^^^^^^^^^^^^^^^^
14 |
15 |     this_is_your_variable: isize,
   |     ^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: associated function `say_your_name` is never used
  --> src\lib.rs:32:8
   |
31 | impl ImAClass {
   | ------------- associated function in this implementation
32 |     fn say_your_name(name: GodotString) -> GodotString {
   |        ^^^^^^^^^^^^^

warning: `hello` (lib) generated 2 warnings
    Finished dev [unoptimized + debuginfo] target(s) in 31.59s

接下来,我们需要在Godot里调用这个库,首先我们打开godot工程,在工程根目录下创建hello.gdextension。

这里推荐以 模块名.extension 的方式创建文件,便于管理

输入以下内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[configuration]
entry_symbol = "gdext_rust_init"
compatibility_minimum = 4.1

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

然后看一看工程根目录下.godot/extension_list.cfg中有没有我们的gdextension文件,有即导入成功。

最后我们来到了最激动的实验验收阶段!新建场景添加一个Node2D场景,一气呵成!

1
2
3
4
5
func _ready():
    var rust = ImAClass.new()
    var message_from_rust = rust.say_your_name("KZNR")
    print(message_from_rust)
    pass

结果验收

最后输出:

1
2
3
4
5
Godot Engine v4.1.1.stable.mono.official.bd6af8e0e - https://godotengine.org
Vulkan API 1.3.205 - Forward+ - Using Vulkan Device #0: NVIDIA - NVIDIA GeForce RTX 3050 Ti Laptop GPU
 
Hello World!
Hello KZNR from rust

至此,godot调用rust大功告成。

comments powered by Disqus
Built with Hugo
主题 StackJimmy 设计