1 个不稳定版本
0.1.0 | 2021年3月23日 |
---|
#962 在 游戏开发
27KB
234 行
gdrust
一个库,使gdnative-rust更接近GdScript。这包含两个主要部分
- 一个用于简化一些Rust代码并使其更接近GdScript的宏
#[gdrust]
。 - 一组“不安全”的函数,在牺牲崩溃风险的同时使事情更简洁。
目标
最终,这个项目的目标是使90%的情况下Godot的Rust开发更简洁。可能存在一些只有“真正的”Rust才能解决的边缘情况,这个项目不应为了覆盖所有情况而牺牲其简洁性。
当前状态
目前,这个项目处于早期alpha阶段。已记录的部分应该按预期工作,但API可能会发生变化。
入门
gdrust
基于gdnative-rust
,因此您必须在查看gdrust
之前设置gdnative-rust
。遵循他们的入门指南。
一旦安装了gdnative-rust
,您可以通过将其添加为依赖项来安装gdrust
。遗憾的是,由于gdnative-rust
宏的工作方式,您必须同时将gdnative-rust
和gdrust
添加为依赖项,并且必须选择兼容的版本。请参阅下面的“兼容性”部分。
[dependencies]
gdnative = "0.9.3"
gdrust = { git = "https://github.com/wyattjsmith1/gdrust.git" }
安装后,只需使用gdrust
宏
use gdrust::macros::gdrust;
use gdnative::api::Node;
#[gdrust(extends = Node)]
struct HelloWorld {
#[export]
#[default(10)]
test: u64,
}
就是这样!
下面将详细介绍导出属性和信号以及一个深入的全面示例。
#[gdrust]
宏
导出类
#[gdrust]
宏中的任何内容都可以导出。
#[gdrust(extends = Node)]
pub struct ClassName {
// Same as `class_name ClassName extends Node` in GdScript.
}
extends = {classname}
是可选的,如果您只是扩展Object
,则可以省略。
#[gdrust::macros::gdrust]
struct ClassName {
// Same as `class_name ClassName extends Object` in GdScript.
}
您仍然可以在类上拥有自定义的derives和attributes。任何在struct
上的attributes将被添加
#[gdrust::macros::gdrust]
#[derive(Debug)]
struct ClassName {
// `ClassName` will derive `Debug`
}
创建类并导出属性和信号后,像往常一样创建您的 impl
块。注意,您不应创建 new
函数。这是由宏提供的。
#[gdnative::methods]
impl ClassName {
#[export]
fn _ready(&self, _owner: TRef<KinematicBody>) {
gdnative::godot_print!("Hello World!")
}
}
导出属性
导出属性的语法旨在尽可能接近 GdScript。由于即将发布的 4.0 版本,gdrust
使用了 4.0 导出。您可以在那里了解所有不同类型的导出。
通常,使用属性语法(#[export_...
),并从 GdScript 导出中移除起始的 @
。例如
@export_range(1, 10, 2, "or_greater") var my_range: int
变为
#[export_range(1, 10, 2, "or_greater")]
my_range: i32 // or i64 if you want
所有内容都应该按照 Godot 文档中的定义实现,除了以下内容
#[no_export]
可以用来不导出一个变量。这应该用于所有 Rust 本地类型(不实现Export
)或如果您希望变量为“私有”。- 4.0 文档将
@export_node_path(Type1, Type2)
定义为导出NodePath
的方法,该路径仅匹配具有给定类型的节点。这已部分实现,但不会在 4.0 中完成,因为目前没有 NodePaths 的导出提示。您目前可以在代码中包含此导出,但它将允许任何类型的NodePath
。 - 可空性使用
Option
处理。 - 每个导出属性都需要一个类型和一个默认值。如果没有提供默认值,将使用
Default::default()
。如果您引用的是 Godot 对象而不是“原始”对象,则必须将其包装在Ref
中。 - 目前,不支持数组。这仅仅是因为我还没有信心语法已经最终确定。在 Godot 的网站上,它显示传统的
export(Array, int) var ints = [1, 2, 3]
。我猜它们将切换到某种@export_array
风格。一旦这确定,添加它应该很容易。
默认值
您可以使用 #[default(value)]
注解设置自定义默认值。如果没有定义,将使用 Default::default()
。
导出信号
导出信号的语法也旨在尽可能接近 GdScript。语法是
#[gdrust]
#[signal(signal_name(arg_name: I64, arg2_name: F64 = 10.0))]
#[signal(other_signal(arg_name: Bool = true, arg2_name: GodotString = "default"))]
struct Class;
与属性类似,信号也有一些需要注意的地方
- 与属性一样,每个信号都必须有一个类型。与属性不同,类型必须是以下之一
VariantType
- 没有
Ref
(例如KinematicBody
)的Godot对象。
我知道这有点奇怪,我想让它更加平滑。欢迎提出建议。
- 与GdScript不同,
gdrust
信号参数可以有可选的默认值。
当信号导出时,将会有一个具有其名称的const
。看看下面的例子中的simple_signal
信号,看看它是如何使用的。
综合示例
此示例应包含导出属性和信号的所有可能性。它也用于测试。
use gdnative::api::{KinematicBody, Node, RigidBody, Texture};
use gdnative::prelude::{Color, InitHandle, NodePath, ToVariant};
use gdnative::{godot_init, Ref, TRef};
use gdrust::macros::gdrust;
#[gdrust(extends = Node)]
#[signal(my_signal(arg1: F64, arg2: GodotString = "test".to_string()))]
#[signal(simple_signal(arg:I64))]
#[derive(Debug)]
struct HelloWorld {
#[export]
#[default(10)]
test_a: u8,
#[no_export]
test_failure: u8,
#[default(10.0)]
test_c: f32,
#[export_range(0.0, 10.0)]
simple_range: f32,
#[export_range(0, 10, 2)]
#[default(2)]
step_range: u8,
#[export_range(0, 10, "or_lesser")]
#[default(10)]
simple_range_or_lesser: u64,
#[export_range(0.0, 10.0, 1.5, "or_lesser")]
#[default(10.0)]
simple_range_step_or_lesser: f64,
#[export_range(0, 10, "or_greater")]
#[default(10)]
simple_range_or_greater: u64,
#[export_range(0, 10, 10, "or_greater")]
#[default(10)]
simple_range_step_or_greater: u64,
#[export_range(0, 10, 10, "or_lesser", "or_greater")]
#[default(10)]
range_with_all: u64,
#[export]
texture: Option<Ref<Texture>>,
#[export_enum("This", "is", "a", "test")]
#[default("This".to_string())]
string_enum: String,
#[export_enum("This", "will", "be", "enum", "ordinals")]
int_enum: u32,
#[export_file]
file: String,
#[export_file("*.png")]
png_file: String,
#[export_dir]
dir: String,
#[export_global_file("*.png")]
glob_file: String,
#[export_global_dir]
glob_dir: String,
#[export_multiline]
#[default("This\nis\nmultiline\ntext".to_string())]
multiline: String,
#[export_exp_range(0.0, 10.0)]
simple_exp_range: f32,
#[export_exp_range(0, 10, 2)]
#[default(2)]
step_exp_range: u8,
#[export_exp_range(0, 10, "or_lesser")]
#[default(10)]
simple_exp_range_or_lesser: u64,
#[export_exp_range(0.0, 10.0, 1.5, "or_lesser")]
#[default(10.0)]
simple_exp_range_step_or_lesser: f64,
#[export_exp_range(0, 10, "or_greater")]
#[default(10)]
simple_exp_range_or_greater: u64,
#[export_exp_range(0, 10, 10, "or_greater")]
#[default(10)]
simple_exp_range_step_or_greater: u64,
#[export_exp_range(0, 10, 10, "or_lesser", "or_greater")]
#[default(10)]
exp_range_with_all: u64,
#[export]
#[default(Color::rgba(0.0, 0.0, 0.0, 0.5))]
color: Color,
#[export_color_no_alpha]
#[default(Color::rgb(0.0, 0.0, 0.0))]
color_no_alpha: Color,
#[export_flags("Fire", "Water", "Earth", "Wind")]
spell_elements: u32,
// TODO: NodePath types are only supported in 4.0
#[export_node_path(KinematicBody, RigidBody)]
physics_body: NodePath,
#[export_flags_2d_physics]
layers_2d_physics: u32,
#[export_flags_2d_render]
layers_2d_render: u32,
#[export_flags_3d_physics]
layers_3d_physics: u32,
#[export_flags_3d_render]
layers_3d_render: u32,
}
#[gdnative::methods]
impl HelloWorld {
#[export]
fn _ready(&self, owner: TRef<Node>) {
gdnative::godot_print!("Hello World!");
gdnative::godot_dbg!(self);
owner
.upcast::<Node>()
.emit_signal(Self::SIMPLE_SIGNAL, &[0.to_variant()]);
}
}
优缺点
像任何软件一样,它也并非没有问题。这个列表旨在准确记录优缺点,以帮助人们决定这是否是适合他们的项目。
优点
- 简化了
ClassBuilder
链,使代码看起来更像GdScript - 生成一个新的
new
- 将属性默认值与
new
的默认值同步。不再更改默认属性值而代码中没有反映。
缺点
- 像许多宏一样,当输入正确时,它们工作得很好。当输入无效时,它们会给出模糊的错误信息。我正在尝试用清晰的错误信息覆盖大多数常见的错误情况。如果您看到奇怪的消息,请提交问题,我会帮助您解决问题。一般来说,
#[export*
应始终使用相同类型的文字(所有整数或所有浮点数)。
不安全函数
Rust的其中一个优点是它迫使你处理每一个可能的案例,以确保运行时顺利。一个问题是在游戏开发中充满了“希望这能行”的情况,直到运行时才忽略错误处理。
例如,假设你想获取一个子节点并调用set_emitting()
。在gdnative-rust
中,你会这样做
unsafe {
owner.get_node("Particles")
.unwrap()
.assume_safe()
.cast::<Particles>()
.unwrap()
.set_emitting(true);
}
与GdStript(没有$糖)比较
get_node("Particles").start_emitting()
是的,静态类型确实在Rust示例中引起了一些冗余,但这仍然很多。gdrust
提供了一个更干净的方法
owner.expect_node::<Particles, _>("Particles").set_emitting(true)
并不像GdScript那样简洁,但仍然比gdnative-rust
简洁。要注意的一点是:这个函数几乎就是上面代码的字面翻译。有一个显式的unsafe
块和各种unwraps。这是非常不安全的,但这个函数什么时候会失败?只有在你请求一个无效的节点或破坏内存模型时。Rust被设计成让你恢复,但你怎么在运行时恢复丢失的节点?你可能只是为了安抚编译器而unwrap
。
因此,这被称为unsafe_functions
,因为在Rust看来这是不安全的,但与GdScript相比,这相当正常且安全。
你绝对应该了解每个方法可以产生的恐慌,并理解gdnative-rust
的内存模型。一旦你做到了,你应该能够正确地判断何时使用这些辅助函数。
兼容性
遗憾的是,gdrust
需要依赖gdnative
,由于gdnative
宏的工作方式,它不能被pub use
。因此,您必须确保您拥有兼容的gdrust
和gdnative
版本。此表将更新为所有兼容版本
gdrust |
gdnative-rust |
---|---|
0.1.0 |
0.9.+ |
阅读更多
许可:MIT
依赖项
~4–7MB
~128K SLoC