4 个版本
新版本 0.2.1 | 2024年8月18日 |
---|---|
0.2.0 | 2024年8月9日 |
0.1.2 | 2024年7月5日 |
0.1.1 | 2024年7月2日 |
0.1.0 |
|
#447 in 游戏开发
119 个月下载
135KB
4K SLoC
SpireTween - Godot 4.2+ 的替代缓动库
警告!
- 此包未经彻底测试,可能包含破坏游戏的功能。尽管如此,我每天都在所有项目中使用它,我承诺会尽快修复任何报告的错误,至少到2026年。
- 此包基本没有文档,这个readme就是所有内容,并且它可能会保持这样,因为作者是唯一的已知用户。接受添加文档的PR。
- 此包需要Rust nightly,并使用以下不稳定功能
#![feature(inline_const_pat)]
#![feature(trait_alias)]
#![feature(hash_extract_if)]
#![feature(let_chains)]
#![feature(is_none_or)]
摘要
为什么
Spire Tween 与 Godot Tween 的比较
主要原因:人体工程学
比较 1:基本缓动
目标
- 使用缓动使
CanvasItem
的颜色 - 打印消息
Godot 缓动
fn basic_godot(mut node: Gd<Node2D>) {
let mut godot_tween =
node.create_tween()
.unwrap(); // unwrap 1
let property_tweener =
godot_tween
.tween_property(
node.clone().upcast(), // boilerplate
"modulate".into(), // Requires manually typing property name
Color::from_rgb(1.0, 0.0, 0.0).to_variant(), // No type safety
2.0
)
.unwrap() // unwrap 2
.set_ease(EaseType::OUT)
.unwrap(); // unwrap 3
let err = // Will error if you make any typos
godot_tween.connect(
"finished".into(), // boilerplate
Callable::from_fn(
"print", // boilerplate
|_| {
godot_print!("Tween finished");
Ok(Variant::nil()) // boilerplate
},
));
}
Spire 缓动
fn basic_spire(node: &Gd<Node2D>) {
let spire_handle = // No errors, internally calls `bind_mut` in Singleton, will panic if you violate Rust's mutability rules, more info at the end of this README
node.do_color(Color::from_rgb(1.0, 0.0, 0.0), 2.0) // Type safe, no variants
.with_ease(Ease::Out)
.on_finish(|| { // Rust compiler doesn't allow making typos
godot_print!("Tween finished");
}).register();
}
比较 2:执行一系列缓动
目标:(使 CanvasItem "闪烁")
- 使用缓动使
CanvasItem
的颜色从黑色 => 白色 - 使用缓动使
CanvasItem
的颜色从白色 => 黑色
Godot 缓动
fn sequence_godot(mut node: Gd<Node2D>) {
let mut godot_tween =
node.create_tween()
.unwrap(); // unwrap 1
// 1st tween
godot_tween
.tween_property(
node.clone().upcast(), // boilerplate
"modulate".into(), // No type safety, typos can happen
Color::WHITE.to_variant(), // No type safety
2.0
)
.unwrap() // unwrap 2
.set_ease(EaseType::IN_OUT)
.unwrap() // unwrap 3
.from(Color::BLACK.to_variant()); // No type safety
// 2nd tween
godot_tween
.tween_property(
node.clone().upcast(), // boilerplate
"modulate".into(), // No type safety, typos can happen
Color::BLACK.to_variant(), // No type safety
2.0,
)
.unwrap() // No type safety
.set_ease(EaseType::OUT_IN);
}
Spire 缓动
fn sequence_spire(node: &Gd<Node2D>) {
let mut sequence = SpireSequence::new();
sequence.append(
node.do_color(Color::WHITE, 2.0) // Type safe
.starting_at(Color::BLACK) // Type safe
);
sequence.append(node.do_color(Color::BLACK, 2.0)); // Type safe
let handle = sequence.register(); // No unwrap
}
比较 3:并行执行缓动
目标
- 同时使用缓动调整
CanvasItem
的缩放和颜色 - 使用缓动将
CanvasItem
的缩放恢复到正常
Godot 缓动
fn parallel_godot(mut node: Gd<Node2D>) {
let mut godot_tween =
node.create_tween()
.unwrap(); // unwrap 1
godot_tween.set_parallel();
godot_tween
.tween_property(
node.clone().upcast(), // boilerplate
"modulate".into(), // No type safety, typos can happen
Color::RED.to_variant(), // No type safety
2.0,
)
.unwrap() // unwrap 2
.set_ease(EaseType::OUT);
godot_tween
.tween_property(
node.clone().upcast(), // boilerplate
"scale".into(), // No type safety, typos can happen
Vector2::new(5.0, 5.0).to_variant(), // No type safety
2.0,
)
.unwrap() // unwrap 3
.set_ease(EaseType::OUT);
godot_tween.chain();
godot_tween
.tween_property(
node.clone().upcast(), // boilerplate
"scale".into(), // No type safety, typos can happen
Vector2::new(1.0, 1.0).to_variant(), // No type safety
2.0,
)
.unwrap() // unwrap 4
.set_ease(EaseType::IN);
}
Spire 缓动
fn parallel_spire(node: &Gd<Node2D>) {
let mut sequence = SpireSequence::new();
sequence.append(
node.do_color(Color::RED, 2.0) // Type safe, typos don't compile
.with_ease(Ease::Out)
);
sequence.join(
node.do_scale(Vector2::new(5.0, 5.0), 2.0) // Type safe, typos don't compile
.with_ease(Ease::Out)
);
sequence.append(
node.do_scale(Vector2::new(1.0, 1.0), 2.0) // Type safe, typos don't compile
.with_ease(Ease::In)
);
let handle = sequence.register();
}
比较 4:配置缓动
Godot 缓动
fn using_godot_tween(mut node: Gd<Node2D>) {
let mut godot_tween =
node.create_tween()
.unwrap() // unwrap 1
.set_loops_ex() // Many settings require boilerplate builder patterns
.loops(5)
.done()
.unwrap() // unwrap 2
.set_process_mode(TweenProcessMode::IDLE)
.unwrap() // unwrap 3
.set_speed_scale(2.0)
.unwrap() // unwrap 4
.set_pause_mode(TweenPauseMode::BOUND)
.unwrap(); // unwrap 5
// Tweening a property
let mut property_tweener =
godot_tween
.tween_property(
node.clone().upcast(), // boilerplate
"modulate".into(), // Requires manually typing property name
Color::from_rgb(1.0, 0.0, 0.0).to_variant(), // No type safety
2.0,
).unwrap(); // unwrap 6
property_tweener
.as_relative()
.unwrap() // unwrap 7
.set_ease(EaseType::IN)// limited to Godot's Ease modes
.unwrap() // unwrap 8
.set_delay(5.0)
.unwrap(); // unwrap 9
godot_tween
.connect_ex(
"finished".into(), // boilerplate
Callable::from_object_method(&node, "on_finished"),
)
.flags(object::ConnectFlags::DEFERRED.ord() as u32)
.done();
// Creating a sequence
godot_tween
.tween_method(
Callable::from_object_method(&node, "tweenable_method"),
2.to_variant(), // No type safety
10.to_variant(), // No type safety
5.0
).unwrap(); // unwrap 10
}
Spire 缓动
fn configuring_spire(node: &Gd<Node2D>) {
let spire_tween =
node.do_color(Color::from_rgb(1.0, 0.0, 0.0), 2.0) // Type safe, no variants
// Just like in Godot, you don't have to manually set these if you want to use defaults
// this is just to show that you can
.looped(5)
.with_process_mode(TweenProcessMode::IDLE)
.with_pause_mode(TweenPauseMode::BOUND)
.with_delay(5.0)
.with_speed_scale(2.0)
.with_ease(Ease::In) // use `keyframe` crate for easing, providing commonly-used easing as well as allowing custom ones
.as_relative(Color::BLACK) // relative allows setting a custom origin
.as_speed_based(20.0)
// do_color automatically binds its lifetime to `node`, the object you called this on, but you can also
// bind it to other godot objects instead of just `node`
.bound_to(other_object)
.on_finish(|| { godot_print!("Finished"); }) // direct closures
.on_finish(Callable::from_object_method(&node, "on_finish")); // callables work too
let mut handle
: SpireHandle<Property<Color>> // Handle knows exact tween type, providing type safety
= spire_tween.register(); // Same as `done()`, only needs to be called once, no unwrapping
// You can use the handle to read/mutate the tween on the fly
// Handles don't guarantee that what they point at exists
let fetch_result: Result<(), FetchError> =
handle.on_finish(|| {
godot_print!("Managed to connect on_finish on the fly!")
});
match fetch_result {
Ok(()) => {}
Err(fetch_err) => {
match fetch_err { // Error explaining exactly what went wrong
FetchError::NotFound => {}
FetchError::TypeMismatch { expected, found } => {
godot_error!("Could not fetch tween expected type {expected:?}, found {found:?}");
}
}
}
}
// Handles allow you to perform almost all the actions you can perform as if you're building the tween
// Everything returns a result though
let _ = handle.stop();
// If you want to access the tween directly, you can call map
let fetch_result: Result<TweenState, FetchError> =
handle.map(|tween: &mut SpireTween<Property<Color>>| {
tween.t.ease = Ease::In;
tween.delay = 2.0;
// ..
// Need to read something on the tween? Return it on map:
tween.state()
});
}
它是如何工作的
register()
尝试访问 TweensController 自加载,将其缓动插入其内部列表。它还返回一个 SpireHandle<T>
,您可以使用它稍后访问缓动,允许您读取其状态,实时更改设置等。
如果无法访问 cTweensController,操作将 panic,这可能会发生如果它不存在,或者如果它在其他地方已被借用。
但是,只要遵循README文件末尾提到的某些规则,这种情况不应该发生。
SpireTween
提供了几种方法来配置你的缓动行为。尽管该库没有文档说明,但名称相当直观,遵循模式:“do_”+ godot_property_name。
在注册缓动之前,你应该完成所有配置。在注册后修改缓动可能导致不可预测的行为。我不建议尝试在Godot主线程之外注册/访问缓动。
更多示例
let node: Gd<Node2D> = ..;
let mut tween = owner.do_move_x(2000.0, 5.0);
let origin = 0.0; // since relative tweens don't have a starting point, the origin parameter is necessary for the tween to compare how much it needs to move between each frame
tween = tween.as_relative(origin);
// If you want to set a specific starting point: (if you don't, the starting point will be evaluated when the tween starts.
tween = tween.starting_at(500.0);
tween = tween.with_delay(10.0);
let speed = 100.0; // Speed is always in units per second, 100. speed means position:x increases by 100 each second.
tween = tween.as_speed_based(speed);
你可以将这些方法串联起来
let handle =
node.do_global_position(Vector2::new(1920., 1080.), 5.0)
.with_delay(10.0)
.starting_at(Vector2::new(0., 0.))
.looped(10)
.on_finish(|| { godot_print!("Tween Finished"); })
.register();
属性缓动器仅仅是[DoProperty](https://github.com/Houtamelo/spire_tween/blob/main/src/extensions/property/mod.rs)特征的包装器。 do_property
自动将节点绑定到缓动上,这意味着当节点被删除时,缓动将自动“死亡”。
DoProperty使用set_indexed()
,因此属性路径也是有效的
// Tween only the Z position
let tween = node.do_property("position:z", 20., 10.);
如果您想使用库原生不支持的价值,您可以使用DoVarProperty/DoVarMethod
。
let my_custom_property = ..;
let my_custom_end_val = ..;
let duration = 5.0;
// the only difference is that the method is called do_property_var()
let tween = node.do_var_property(my_custom_property, my_custom_end_val, duration);
您还可以缓动方法
// Imagine you have a method like this...
#[func]
fn _set_fill(&mut self, value: f64) {
self.base_mut().set_value(value);
}
// You can tween it like this
let start = 0.;
let end = 1.;
let duration = 8.;
let tween = node.do_method("_set_fill", start, end, duration);
如果您只需延迟调用一次方法,您可以
#[func]
fn _start_game(&mut self, player_name: String, difficulty: Difficulty) {
...
}
let player_name = "Houtamelo";
let difficulty = Difficulty::GameDeveloper;
let delay = 24.;
let args = Array::from(&[player_name.to_variant(), difficulty.to_variant()]);
let callable = Callable::from_object_method(node, "_start_game").bindv(args);
let tween = node.do_delayed_call(callable, delay);
// Closures are also accepted:
let tween = node.do_delayed_call(|| node._start_game(player_name, difficulty), delay);
永远不要忘记注册您的缓动!否则它们将被丢弃
let handle = tween.register();
我们还有序列,它们的工作方式非常类似于DoTween的
let mut sequence = SpireSequence::new();
sequence.append(owner.do_move_x(640.0, 5.0));
sequence.join(owner.do_fade(1.0, 4.0));
sequence.append(owner.do_global_move(Vector2::new(124., 256.));
sequence.insert(8.0, owner.do_scale(Vector2::new(10., 10.));
// SpireSequence is just an alias for SpireTween<Sequence>, which means you can also nest them
let nested_sequence = {
let mut nested = SpireSequence::new();
nested.append(owner.do_color(Color::BLUE, 2.0));
nested.append(owner.do_move_y(Vector2::new(0, 69));
nested
};
sequence.append(nested_sequence);
let handle = sequence.register();
需要终止一个缓动吗?
let handle: SpireHandle<DelayedCall> = ..;
handle.kill();
// As you may have guessed from the types, sequences are also tweens:
let sequence_handle: SpireHandle<Sequence> = ..;
sequence_handle.kill();
需要立即完成一个缓动吗?
handle.complete();
知道...
spire_tween
不会以任何方式与Godot的内置缓动交互,它在其_process(f64)
和_physics_process(f64)
虚拟方法上运行的独立自动加载中处理注册的缓动。
设置
设置过程假设您已经熟悉GDExtension的工作方式。
步骤 1
将spire_tween
添加到您项目的依赖项中:运行cargo add spire_tween
或将其添加到您的Cargo.toml中:[dependencies] spire_tween = "0.1"
您很可能会想要使用最新版本,因为它将包含最新的错误修复。
步骤 2
一旦您构建了您的动态库,类TweensController
应该在下一次您打开编辑器时自动与Godot注册。创建一个根名为“tweens_controller”的空场景,输入TweensController
,然后将其添加为您的自动加载。
警告!节点名称必须为“tweens_controller”,否则每当尝试注册缓动时,Rust都会崩溃,导致您的游戏崩溃。
规则
不要尝试同时从两个地方借用TweensController
,如果以下情况发生,则会出现这种情况:
- 从以下地方注册缓动:在
on_finish
提供的回调内部或触发在您的do_property
或do_method
调用之一中 - 使用句柄从以下地方访问缓动:在
on_finish
提供的回调内部或触发在您的do_property
或do_method
调用之一中 - 您从不同的线程注册/访问缓动
帮助
在GitHub上打开一个问题或给我发消息:houtamelo
依赖关系
~7.5MB
~184K SLoC