Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

动画序列与并行组合

v0.3 不再使用 TimelineId 管理 Scene 内的可变时间线。动画先在用户代码中组合为完整的 Animation,再通过 RanimScene::play 加入场景。

当前有两个动态组合容器:

  • AnimSequence:子动画按 cursor 顺序排列,适合描述一个完整状态序列。
  • AnimStack:子动画共享同一个局部原点,适合叠加互不干扰的动画层。

RanimScene 自带一个根 AnimStack

pub fn play<A: Animation + 'static>(&mut self, animation: A) -> &mut Self {
    self.root.push(animation);
    self
}

因此,多次根级 play 默认都从 0 秒开始。它们是并行动画,不存在后一次调用覆盖前一次调用的隐含对象语义。

AnimSequence

AnimSequence::push 先将动画 build 为局部 AnimationCell,再把它移动到当前 cursor,并按 cell duration 推进 cursor:

let mut intro = AnimSequence::new();
intro
    .push(square.clone().fade_in())
    .hold(1.0)
    .push(square.fade_out());

r.play(intro);

Sequence 是动态类型擦除边界,但不会展开传入动画的组合树。每次 push 只将直接子动画转换为一个 AnimationCell;如果子动画是 Stack 或 Sequence,其内部层级会继续保留。

Sequence 自己通过 cursor 决定子动画的位置,因此 push 只接受尚未显式放置的 PlaceableAt<A> 已经固定父时间坐标,不能进入 Sequence。

Sequence 本身仍实现 Animation,所以可以先独立构造,再整体使用 at 放置或加入另一个组合:

r.play(intro.at(2.0));

forwardhold

两者都会推进 Sequence cursor,但输出语义不同:

  • forward(secs) 只推进 cursor,产生的空白区间没有输出。
  • hold(secs) 取得 cursor 处的 Sequence 状态,将它保存为持续 secs 的静态运行时节点。
  • forward_to(target)hold_to(target) 是对应的绝对 cursor 版本。

hold 没有额外的状态协议,它直接采用 Sequence 在 cursor 处的正常求值结果。Sequence 在同一时刻只求值最后一个适用的直接子动画;如果这个子动画是 Stack,则由 Stack 求值其中所有仍然适用的子动画。已经提前结束的 Stack 子动画不会被自动延长。

child A: [0, 1)
child B: [0, 2)
cursor:        2

hold at 2 -> 只保持 B 的左侧终态

连续 hold 会分别保存每次调用时的求值结果,形成相邻的静态区间。

showhide 与最终求值

show()hide() 都是普通的零时长动画:

  • show() 是 enabled 的静态动画,求值时输出对应物件;
  • hide() 是 disabled 的静态动画,求值时不输出内容。

它们不需要 hold 特判。因为 Sequence 在边界上选择最后一个适用的直接子动画,末尾的 show() 会成为最终求值结果,末尾的 hide() 则自然得到空结果;hold 只负责把这个结果保存为静态动画。

let mut content = AnimSequence::new();
content
    .push(square.show())
    .hold(1.0)
    .push(square.hide())
    .hold(1.0);

这里 hide 只改变 content 这条 Sequence 的状态。它不会查找或影响根 Stack 中另一个独立动画。

如果两个物件需要独立生命周期,应分别使用两个 Sequence:

r.play(square_sequence);
r.play(circle_sequence);

如果两个物件需要在同一时刻一起求值,应直接 push 一个 stack![...] 组合。

AnimStack 与根场景

AnimStack::push 不推进其他子动画;Stack duration 是所有子动画 duration 的最大值:

let animation = stack![
    background.show().with_duration(5.0),
    content.at(1.0),
    camera.show().with_duration(5.0),
];

r.play(animation);

Stack 接受普通 Placeable 动画和已经放置的 At<A>。普通动画从 Stack 局部 0 开始,At<A> 使用自己的显式 offset。参数必须在调用 at 之前设置。

运行时数量不固定时可以直接构造 AnimStack

let mut layers = AnimStack::new();
for animation in animations {
    layers.push(animation);
}
r.play(layers);

场景时长与显式生命周期

Scene 总时长是根 Stack 中最长子动画的 duration。新模型不会像旧 Timeline 那样在 seal 时自动把静态物件和相机延长到 Scene 结束。

需要全程存在的内容应显式指定生命周期:

let total_secs = content.cursor_sec();

let mut camera = AnimSequence::new();
camera
    .push(CameraFrame::default().show())
    .hold_to(total_secs);

r.play(camera);
r.play(content);

这种写法使空白和保持区间成为动画定义的一部分。后续可以增加默认相机或 through_scene_end 等辅助 API,但它们不改变 Sequence/Stack 的组合语义。

seq!stack!

固定写法可以使用宏简化:

let intro = seq![
    square.clone().fade_in(),
    square.fade_out(),
];

let scene = stack![intro, camera];
r.play(scene);

seq! 返回 AnimSequencestack! 返回 AnimStack。二者都只是构造辅助,最终 build 为保留子节点层级的运行时动画树。