1 个不稳定版本
0.1.0 | 2021年3月22日 |
---|
#17 在 #gd-native
在 gdrust 中使用
31KB
789 行
gdrust
一个库,使 gdnative-rust
更接近 GdScript。它包含两个主要部分
- A
#[gdrust]
宏,用于简化一些 Rust 代码并使其更接近 GdScript。 - 一组“不安全”函数,以牺牲崩溃的风险来使事物更简洁。
目标
最终,这个项目的目标是使 Rust 对 Godot 的开发在 90% 的情况下更简洁。可能存在一些只有“真正的”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.
}
你仍然可以在你的类中拥有自定义的派生和属性。结构体struct
上的任何属性都将被添加
#[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
块,以及各种unwrap操作。这非常不安全,但何时会失败?只有在你请求一个无效的节点,或者破坏内存模型时。Rust旨在让你能够恢复,但如何在运行时恢复一个缺失的节点?你可能只是 unwrap 以满足编译器的需求。
因此,这被称为 unsafe_functions
,因为在Rust眼中这是不安全的,但与GdScript相比,这相当正常且安全。
你应该一定了解每个方法可以产生的恐慌,并理解 gdnative-rust
的内存模型。一旦了解,你应该能够正确判断何时使用这些辅助函数。
兼容性
不幸的是,gdrust
需要依赖 gdnative
,并且由于 gdnative
宏的工作方式,它不能 pub use
。因此,你必须确保你有一个与 gdrust
和 gdnative
兼容的版本。此表将更新所有兼容版本。
gdrust |
gdnative-rust |
---|---|
0.1.0 |
0.9.+ |
阅读更多
许可:MIT
依赖
~2MB
~44K SLoC