低级 API 用法
背景
虽然 JS 运行时提供了高级 API,允许快速将 Rive 集成到 Web 应用中,但运行时也允许使用更小的低级 API,可以在你自己的渲染循环中构建和控制 Rive。使用此低级 API 有几个理由和好处:
- 在一个
<canvas>元素中构建包含多个 Rive 文件、画板、线性动画和状态机的场景。这在构建游戏时非常有用! - 控制渲染循环,这涉及你如何随时间推进每个画板、动画和状态机(包括速度)
- 能够访问绘制层级中节点/骨骼上的各种变换属性值
- 更小的依赖大小
- ……以及更多!
前提
以下是使用低级 API 渲染 Rive 的基本渲染工作流:
加载 Rive Web Assembly (WASM) 文件
加载 Rive 文件
创建画板、线性动画和状态机的实例
构建渲染循环函数以操作上面创建的实例
推进任何动画实例并应用
推进任何状态机实例
推进画板
在 canvas 上渲染更新的画板
请求下一个动画帧
完成后清理创建的实例
开始使用
如果你决定应用需要低级 JS API,请阅读下方关于如何设置所有内容的指南,或者你可以跳到结尾查看一些运行中的示例。
加载 WASM
设置低级 Rive API 的第一步是从 @rive-app/canvas-advanced 或 @rive-app/webgl2-advanced 库加载 Rive WASM 文件(默认情况下,我们建议使用 @rive-app/canvas-advanced 以获得更小的依赖,除非你需要使用 WebGL2)。当 WASM 文件加载到你的应用中后,你将获得必要的 API,如 Canvas/WebGL 的渲染器,以及通过 rive-cpp 从底层 CPP 绑定生成的相关 JS 类,这是用作多个其他 Rive 运行时基础的核心 C++ 运行时。你将使用这些类在下面的 canvas 中构建渲染场景。
你可以通过 unpkg(托管我们的 JS 运行时的 NPM 模块)加载 Rive WASM 文件,这将向 CDN 发起网络调用,或者你可以选择在自己的服务器上托 管 WASM 文件。使用 unpkg 时,URL 看起来会像这样:
https://unpkg.com/@rive-app/[email protected]/rive.wasm
你需要确保 @rive-app/canvas-advanced@ 或 @rive-app/webgl2-advanced@ 末尾的版本与你在应用中安装的依赖版本匹配。例如,如果你在 package.json 中安装了 @rive-app/[email protected],那么你从 unpkg 请求的 Rive WASM 文件将是 https://unpkg.com/@rive-app/[email protected]/rive.wasm。
参见预加载 WASM 了解如何预加载 WASM。
首先,从库中导入默认模块,然后用一个对象调用它,你只需要设置一个参数 locateFile,这是一个返回 WASM 文件 URI 的函数。这可以是 unpkg URL 或指向你自托管版本的 URI。只需 await 调用解析,然后你将获得低级 Rive 运行时 API 的引用。
import RiveCanvas from '@rive-app/canvas-advanced';
async function main() {
const rive = await RiveCanvas({
locateFile: (_) => 'https://unpkg.com/@rive-app/[email protected]/rive.wasm'
});
}
main();
创建渲染器
一旦 WASM 加载完成,下一步是使用 makeRenderer() API 创建渲染器,并传入 Rive 应渲染的 canvas 元素。渲染器使用渲染上下文将 Rive 绘制到 <canvas> 元素上。如果你使用 @rive-app/canvas-advanced,它将创建 Canvas2D 渲染上下文。如果你使用 @rive-app/webgl2-advanced,它将创建 WebGL 渲染上下文。
const canvas = document.getElementById('your-canvas-element');
const renderer = rive.makeRenderer(canvas);
加载 Rive 文件
创建渲染器后,你也可以开始将 Rive 文件作为 ArrayBuffer 加载,并将其馈入运行时的 load() API。你可以从 URL 或项目中的某处获取。
const bytes = await (
await fetch(new Request('basketball.riv'))
).arrayBuffer();
// 从 Rive 依赖中以命名导入方式导入 File
const file = (await rive.load(new Uint8Array(bytes))) as File;
确保 await
.load()调用,因为它会同步尝试从File加载资源。此外,在将ArrayBuffer作为参数传递给.load()之前,先将其传递给Uint8Array视图。
设置实例
一旦你有了加载的 File 对象的引用,你可以开始从 Rive 文件中实例化所有画板、状态机和线性动画。实例化会创建一个底层 CPP 引用,并允许你控制每个实体如何随时间推进。更多内容见本指南下文。
你可能想要实例化的主要组件是:
Artboard- 从 Rive 文件中实例化 1 个或多个你想要绘制的画板StateMachineInstance- 从给定画板实例化一个状态机LinearAnimationInstance- 从给定画板实例化一个单个时间线动画
首先实例化一个画板,然后可以从画板引用创建状态机和线性动画实例,如下所示。
const artboard = file.artboardByName('New Artboard');
const animation = new rive.LinearAnimationInstnace(
artboard.animationByName('idle'),
artboard
);
const stateMachine = new rive.StateMachineInstance(
artboard.stateMachineByName('your-state-machine-name'),
artboard
);
这里的好处是,如果你想在 canvas 上显示多个画板甚至是同一画板的副本,你可以轻松做到(相对于高级 API,它一次只显示一个)。
除了实例化渲染循环的相关部分,你还可以提取绘制层级中的节点、目标和骨骼的引用。如果你需要跟踪给定节点上的任何变换属性值以进行任何计算,或者获取世界空间或父级变换(例如,跟踪动画生命周期中节点的 x、y 坐标或旋转值),这非常有用。参见指南底部的示例以查看此操作。
构建渲染循环
你可能熟悉使用 requestAnimationFrame (rAF) 构建渲染循环,以在浏览器的重绘周期之间逐帧构建动画。如果不熟悉,请查看此指南作为构建渲染循环的起点。
在 Rive 渲染循环的情况下,你将使用包装 rAF 的自定义 Rive API,因此你需要使用 rive.requestAnimationFrame() 以及 rive.cancelAnimationFrame()。结构应类似于你为其他动画构建的任何 rAF 循环,但你将推进上面创建的实例,并根据需要将画板与 canvas 对齐。
首先创建 rAF 循环的回调循环,并跟踪自上次 rAF 回调以来的最后时间以获取经过时间(秒)。然后,使用渲染器的 .clear() API 清除 canvas。
let lastTime = 0;
function renderLoop(time) {
if (!lastTime) {
lastTime = time;
}
const elapsedTimeMs = time - lastTime;
const elapsedTimeSec = elapsedTimeMs / 1000;
lastTime = time;
renderer.clear();
...
rive.requestAnimationFrame(renderLoop);
}
rive.requestAnimationFrame(renderLoop);
推进动画
LinearAnimationInstance 有一组关键帧应用于画板中的对象。在渲染循环中,你需要对创建的动画实例调用 .advance() 来获取这些关键帧,并如 API 名称所示,将动画推进一定的时间量(以秒为单位)。
通常,你需要按上文计算的经过时间来推进动画,以「正常」速度播放(或者更确切地说,以该时间线动画设置的任何速度播放)。使用低级 API,通过控制渲染循环,你可以以自定义时间值推进实例,例如一半的经过时间(以 0.5 倍速度播放动画)或甚至两倍的经过时间(以 2 倍速度播放动画)。你甚至可以将经过时间乘以 -1 来反向运行动画方向。
除了推进线性动画外,还需要使用 .apply() 调用将该动画的关键帧值应用于画板中相关对象的属性,并指定动画的混合值。当动画应用关键帧的插值值时,它会将这些值与画板对象上的当前值混合。这允许你「混合」到动画中,这在你有两个动画实例在对象的同一属性上应用关键帧值时很有帮助。替换旧属性值为新关键帧值的默认混合值应为 1。
在将动画值应用于画板后,推进画板(详见下文)以更新画板的对象并解决属性值更改。
总而言之,推进线性动画的操作顺序如下:
advance animation -> apply animation values -> advance artboard
参见下面的片段示例:
function renderLoop(time) {
if (!lastTime) {
lastTime = time;
}
const elapsedTimeMs = time - lastTime;
const elapsedTimeSec = elapsedTimeMs / 1000;
lastTime = time;
renderer.clear();
animation.advance(elapsedTimeSec);
animation.apply(1);
artboard.advance(elapsedTimeSec);
}