Ranim 是一个使用 Rust 编写的程序化动画引擎, 受 3b1b/manim 和 jkjkil4/JAnim 启发
- 矢量图形基于二阶贝塞尔曲线表示,使用 SDF 渲染
- 使用 wgpu,兼容多种后端图形 API
注:目前 ranim 的接口仍并不稳定,而本页面的说明文字可能更新的不是特别勤(但是带视频的部分即 example 是会同步更新的)
Getting Started⁋
在 Ranim 中,定义并渲染一段动画的方式基本长成下面这个样子:
use *;
;
HelloWorldScene 是一个 Scene,即下面两个 Trait 的组合:
-
SceneMetaTrait
实现了fn meta(&self) -> SceneMeta
方法。使用
#[scene]
会以结构体的 snake_case 命名(去掉Scene
后缀)作为 SceneMeta 的name
字段自动实现这个 Trait。也可以通过
#[scene(name = "<NAME>")]
来手动命名。 -
SceneConstructor
则是定义了动画的构造过程。
使用 render_scene
可以用一个 Scene 来构造一个 RanimTimeline 并对其进行渲染,渲染结果将被输出到 <output_dir>/<scene_name>/
目录下。
construct
方法有两个关键的参数:
timeline: &'t RanimTimeline
:Ranim API 的主要入口,几乎全部对动画的编码操作都发生在这个结构上camera: &'r Rabject<'t, CameraFrame>
:默认的相机 Rabject,也是 RanimTimeline 中被插入的第一个 Rabject
RanimTimeline 和 Rabject 这两个类型非常重要,将贯穿整个 Ranim 动画的编码。
1. RanimTimeline 和 Rabject⁋
Ranim 使用一个 RanimTimeline
结构来编码动画,首先介绍两个最基本的操作:
- 使用
timeline.forward(duration_secs)
来使时间线推进一段时间 - 使用
timeline.insert(item)
来将一个item: T
插入时间线,返回一个Rabject<T>
Rabject<T>
的结构很简单,如下:
当某个物件 T
被插入 RanimTimeline
中时,会被赋予一个 Id,以 Rabject<T>
的形式返回,同时在 RanimTimeline
内部会以 T
的值为初始状态创建一条 RabjectTimeline
。
使用 timeline.show(&rabject)
和 timeline.hide(&rabject)
可以控制接下来 forward
时的表现。
当一个 Rabject
被 drop
时,它会被 hide
掉:
下面的例子使用一个 VItem
物件和 timeline.insert
在时间线中创建了一个 Rabject<VItem>
并展示了 show
、hide
以及 drop
对其影响:
1 use ;
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
2. 播放动画⁋
Ranim 中的每一个动画都会为实现了对应 Trait 的物件添加对应的创建方法。
比如对于 FadingAnim
,凡是实现了 Opacity + Interpolatable
Trait 的物件都会拥有 fade_in
和 fade_out
方法。
对一个 Rabject<T>
调用创建动画的方法会返回一个 AnimSchedule<T>
,将它传入 timeline.play(anim_schedule)
即可将这段动画编码在 RanimTimeline
中。
let mut square = timeline.insert;
timeline.play;
timeline.play;
上面的动画也可以这样写:
let mut square = timeline.insert;
timeline.play;
AnimSchedule<T>
的 chain
方法,接受一个 impl FnOnce(T) -> Animation<T>
,会将两个动画拼接在一起。
而 T
与 &'r mut Rabject<'t, T>
相同,也有创建动画的方法,不过返回的是 Animation<T>
。
1 use 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
3. 动画参数⁋
AnimSchedule<T>
和 Animation<T>
都具有一些控制动画属性的参数,可以通过链式调用的方式来设置:
with_duration(duration_secs)
:设置动画持续时间with_rate_func(rate_func)
:设置动画速率函数
此外在这个例子中你会发现,在播放了 transform_to(circle)
之后,再播放 fade_out
时,播放的并不是圆形的淡出,而是方形。
这并不是一个 Bug,而是一种刻意的设计,请继续向下阅读 4. 向 Rabject 应用动画变更,了解更多。
1 use 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 37 38
39
4. 向 Rabject 应用动画变更(AnimSchedule 与 apply)⁋
使用 Rabject 创建动画时是基于 Rabject 当前的内部数据来创建的,创建与播放动画并不会修改其内部数据。
如果想要一个动画的效果实际应用到 Rabject 中,那么需要对 AnimSchedule
使用 apply
方法。
这样的好处是对于一些对数据有 损坏性变更 的动画(比如 unwrite 等),我们不需要提前对数据进行备份。
1 use 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 37 38 39 40 41
42
不过 chain
是会以第一个动画的结束状态为基础创建下一个动画的,但是要注意此时的 AnimSchedule
是整个被拼接后的动画,如果不调用 apply
是不会更新 Rabject
内部的数据的,而调用 apply
会应用整个被拼接后的动画的变更:
// <-- Rabject's data is a square
timeline.play;
// <-- Rabject's data is still a square
timeline.play; // This plays a square's unwrite, but not circle's
// <-- Rabject's data is a square
timeline.play;
timeline.play; // This plays nothing, because after the apply, the data is empty(unwrote circle)
简单来说 AnimSchedule
的作用就是将具有紧密关系的动画组合在一起,通过 apply
会应用整个动画(类似 Transaction 的感觉)。