spine-canvaskit 运行时文档

Licensing

将官方的Spine运行时整合到你的应用程序之前, 请仔细阅读 Spine运行时许可页面.

简介

spine-canvaskit是一个基于TypeScript的运行时, 用于在浏览器和Node.js环境中使用 CanvasKit 加载、操作和渲染Spine skeleton. 因此spine-canvaskit既可以用在前端(渲染UI元素)也可以用在后端(无头渲染skeleton).

spine-canvaskit要求使用CanvasKit 0.39.1及以上版本, 并支持除 双色tinting 外的全部Spine功能.

spine-canvaskit构建于spine-core之上, spine-core是Spine运行时核心API的TypeScript实现. 有关核心API的更多信息, 请参见 Spine运行时指南 一文.

安装

请注意: spine-canvaskit的 major.minor 版本必须与导出skeleton的Spine编辑器的 major.minor 版本匹配. 更多详情请见 Spine编辑器和运行时版本管理指南.

NPM和Yarn

使用NPM或Yarn都能将spine-canvaskit添加到你的项目中:

npm install @esotericsoftware/spine-canvaskit@^4.2.0
yarn add @esotericsoftware/spine-canvaskit@^4.2.0

spine-canvaskit是一个 ECMAScript模块, 可原生地在Node.js和所有现代浏览器中使用, 也能通过webpack、rollup或esbuild等工具打包. 它包含了源码映射(source maps)以便调试.

请注意: 要访问spine-canvaskit模块中的类、枚举或函数, 只需导入它们, 例如 import { loadTextureAtlas } from "@esotericsoftware/spine-canvaskit"

原生JavaScript

可以通过script标签从 unpkg CDN 将spine-canvaskit添加到原生JavaScript项目中:

<script src="https://unpkg.com/canvaskit-wasm@latest/bin/canvaskit.js"></script>
<script src="https://unpkg.com/@esotericsoftware/spine-canvaskit@4.2.*/dist/iife/spine-canvaskit.js"></script>

启用源码映射可以调试运行时的原始TypeScript源代码.

我们还提供了spine-canvaskit的压缩版本, 可以通过将unpkg URL中的 .js 替换为 .min.js 来使用.

<script src="https://unpkg.com/canvaskit-wasm@latest/bin/canvaskit.js"></script>
<script src="https://unpkg.com/@esotericsoftware/spine-canvaskit@4.2.*/dist/iife/spine-canvaskit.js"></script>

请注意: 如果在原生JavaScript项目中包含spine-canvaskit, 必须通过全局的 spine 对象访问所有类、枚举和函数, 例如 spine.loadTextureAtlas()spine.SkeletonData. 下文中的代码示例已省略 spine 对象.

示例

spine-canvaskit运行时包含多个示例来展示其功能集.

要运行示例:

  1. 安装 Node.js
  2. 克隆 spine-runtimes仓库
  3. 在终端中:
cd path/to/spine-runtimes/spine-ts
npm run dev

这将打开浏览器窗口并显示所有spine-ts运行时示例. 可以在 CanvasKit 标题下找到spine-canvaskit相关示例.

包含以下示例:

更新spine-canvaskit运行时

在更新项目中的spine-canvaskit运行时前, 请先阅读 Spine 编辑器和运行时版本管理指南.

要更新spine-canvaskit运行时, 只需修改 package.json 文件中 spine-canvaskit 包的版本字符串, 然后再次运行 npm install. 对于原生JavaScript, 更新script标签中的unpkg URL.

请注意: 如果更改了 spine-canvaskit 包的 major.minor 版本, 必须使用相同Spine编辑器 major.minor 版本重新导出Spine skeleton!

使用spine-canvaskit

资产管理

导出适用于spine-canvaskit的Spine资产

请按照Spine用户指南中的说明:

  1. 导出skeleton和动画数据
  2. 导出包含skeleton图像的texture atlases

请注意: spine-canvaskit会在atlas图片中自动使用premultiplied alpha, 因此导出atlases时请勿使用premultiplied alpha!

导出的skeleton数据和texture atlas将产生以下文件:

  1. skeleton-name.jsonskeleton-name.skel 文件, 包含了skeleton和动画数据.
  2. skeleton-name.atlas, 包含了texture atlas的相关信息.
  3. 单个或数个 .png 文件, texture atlas中的一页就对应着这样一个文件, 而texture atlas则保存着skeleton所需的图片包.

请注意: 你应该将skeleton优先导出为二进制格式而非JSON格式, 因为二进制格式尺寸更小且加载速度更快..

这些是随应用程序一起分发的文件.

更新Spine资产

在开发过程中, 你可能需要经常更新Spine skeleton数据和texture atlas文件. 只需从Spine编辑器重新导出并替换掉项目中的资产文件即可更新这些源文件(.json, .skel, .atlas, .png).

确保spine-canvaskit的 major.minor 版本与导出骨架的Spine编辑器 major.minor 版本匹配. 更多详情请见 Spine版本管理.

初始化CanvasKit

spine-canvaskit依赖CanvasKit来加载和渲染Spine skeleton. 在使用spine-canvaskit之前, 必须初始化CanvasKit.

在NodeJS或支持ES6的浏览器项目中:

import CanvasKitInit from "canvaskit-wasm";

const ck = await CanvasKitInit();

在浏览器中使用原生JavaScript的script标签:

<script src="https://unpkg.com/canvaskit-wasm@latest/bin/canvaskit.js"></script>
<script type="module">
const ck = await CanvasKitInit();
</script>

在下面的代码片段中, 我们假设 ck 持有已初始化的 CanvasKit 对象的引用.

核心类

spine-canvaskit API构建在基于TypeScript的通用 spine-core 运行时之上, 该运行时提供了平台无关的核心类和算法, 用于加载、查询、修改和动画化Spine skeleton. 核心类也是spine-canvaskit的一部分.

这里, 我们将简要讨论日常使用spine-canvaskit时会遇到的最重要的核心类. 请查阅 Spine运行时指南 详细了解Spine运行时的架构、核心类和API用法.

Atlas 类存储从 .atlas 文件及其对应的 .png 图像文件加载的数据.

SkeletonData 类存储从 .json.skel skeleton文件加载的数据. skeleton数据包含关于骨骼层次结构、槽位、附件、约束、皮肤和动画的信息. SkeletonData 实例通常通过提供 TextureAtlas 来加载, 它从中获取skeleton所需的图像. 它作为创建 Skeleton 实例的蓝图. 可以从相同的atlas和skeleton数据实例化多个skeleton, 这些skeleton共享加载的数据, 以便最小化加载时间和运行时内存消耗.

Skeleton 类存储从 SkeletonData 实例创建的skeleton实例. skeleton存储其当前姿势, 即骨骼的位置和槽位、附件及活动皮肤的当前配置. 可以通过手动修改骨骼层次结构来计算当前姿势, 或者更常见的是通过 AnimationState 应用动画.

AnimationState 类负责跟踪应应用于skeleton的动画, 基于上一帧和当前渲染帧之间经过的时间推进和mix这些动画, 并将动画应用于skeleton实例, 从而设置其当前姿势. AnimationState 查询 AnimationStateData 实例以获取动画之间的mix时间, 或者如果动画对没有可用的mix时间, 则获取默认mix时间.

spine-canvaskit在spine-core之上添加了功能, 使加载、修改和渲染Spine skeleton变得简单.

加载资产

Spine atlas和skeleton数据文件可以通过 loadTextureAtlas()loadSkeletonData() 函数分别加载.

spine-canvaskit可在CanvasKit可用的任何JavaScript环境中工作, 例如NodeJS或浏览器. 因此, 加载器函数是平台无关的, 需要提供一个函数, 该函数接受绝对或相对路径并返回包含文件原始二进制内容的 Buffer (NodeJS)或 ArrayBuffer (浏览器).

此函数的 TypeScript 签名如下:

readFile(path: string): Promise<any>

对于NodeJS, 此函数可以实现如下:

import * as fs from "fs"

async function readFile(path) {
return fs.readFileSync(path)
}

对于浏览器环境, 该函数可以这样实现:

async function readFile(path) {
const response = await fetch(path);
if (!response.ok) throw new Error("Could not load file " + path);
return await response.arrayBuffer();
}

使用 loadTextureAtlas() 函数加载 .atlas 文件及其 .png 页面图像文件:

const atlas = await loadTextureAtlas(ck, "myatlas.atlas", readFile);

atlas的 .png 文件将相对于 .atlas 文件所在的目录解析.

类似地, 使用 loadSkeletonData() 函数加载skeleton数据 .json.skel 文件:

const skeletonData = await loadSkeletonData("myskeleton.skel", atlas, readFile);

loadSkeletonData() 可以加载 .json.skel 文件, 基于文件名中的相应扩展名. atlas 用于获取从骨架数据派生的skeleton渲染所需的图像.

SkeletonDrawable

SkeletonDrawable 封装了一个 Skeleton (存储skeleton的当前姿势和皮肤) 和一个 AnimationState (负责跟踪和应用动画).

加载skeleton的atlas和skeleton数据文件后, 可以用它们创建一个或多个 SkeletonDrawable 实例. 注意 SkeletonData 隐式地引用了 TextureAtlas.

调用 SkeletonDrawable 构造函数创建新实例:

const drawable = new SkeletonDrawable(skeletonData);

可以通过相应字段访问 SkeletonDrawable 内的 SkeletonAnimationState:

// Position and scale the skeleton
const skeleton = drawable.skeleton
skeleton.x = 300;
skeleton.y = 380;
skeleton.scaleX = skeleton.scaleY = 0.5;

// Queue an animation on the animation state
const animationState = drawable.animationState;
animationState.setAnimation(0, "walk", true);

有关 SkeletonAnimationState API 的全面讨论, 请参阅 Spine运行时指南. 下面你将找到最基本的API用法示例来帮助你入门.

应用动画

请注意: 更多深入信息请参阅Spine运行时指南中的 应用动画.

AnimationState 允许你在多个轨道上排队一个或多个动画. 轨道从索引0开始. 较高轨道上的动画覆盖较低轨道动画中设置了关键帧的任何属性. 这个轨道概念允许你同时播放和混合多个动画.

要在轨道0上设置特定动画, 调用 AnimationState.setAnimation():

drawable.animationState.setAnimation(0, "walk", true);

第一个参数指定轨道, 第二个参数是动画名称, 第三个参数定义是否循环动画.

也可以队列多个动画:

const animationState = drawable.animationState;
animationState.setAnimation(0, "walk", true);
animationState.addAnimation(0, "jump", false, 2);
animationState.addAnimation(0, "run", true, 0);

addAnimation() 的第一个参数是轨道. 第二个参数是动画名称. 第三个参数定义是否循环动画. 最后一个参数指定延迟秒数, 在此延迟后此动画应替换轨道上的前一个动画.

在上面的示例中, 首先播放 "walk" 动画. 2秒后, 播放一次 "jump" 动画, 然后过渡到 "run" 动画, 该动画将循环播放.

从一个动画过渡到另一个动画时, AnimationState 将在特定持续时间内mix动画. 这些mix时间在 AnimationStateData 实例中定义, AnimationState 从中检索mix时间.

AnimationStateData 实例也可以通过 AnimationState 访问. 你可以设置默认mix时间, 或特定动画对之间的mix时间:

animationState.data.defaultMix = 0.2;
animationState.data.setMix("walk", "jump", 0.1);

设置或添加动画时, 返回一个 TrackEntry 对象, 允许进一步修改该动画的播放. 例如, 你可以设置轨道条目以反向播放动画:

const entry = drawable.animationState.setAnimation(0, "walk", true);
entry.reverse = true;

更多选项请参阅 TrackEntry 类文档.

请注意: 不要在函数外部保留 TrackEntry 实例. 轨道条目在内部重复使用, 因此一旦它代表的动画完成, 它们将变得无效.

在动画轨道上设置空动画或队列空动画, 就可将skeleton重置为setup pose:

drawable.animationState.setEmptyAnimation(0, 0.5);
drawable.animationState.addEmptyAnimation(0, 0.5, 0.5);

setEmptyAnimation()的第一个参数指定轨道号. 第二个参数指定mix持续时间(以秒为单位), 这段时间可以mix掉之前的动画并过渡到空动画.

addEmptyAnimation()的第一个参数指定轨道号. 第二个参数指定mix持续时间(以秒为单位). 第三个参数则是延迟时间(以秒为单位), 轨道上的上一个动画在延迟后将mix到空动画.

通过 AnimationState.clearTrack() 可以立即清空轨道上的所有动画. 要一次性清空全部轨道, 可使用 AnimationState.clearTracks(). 这将使skeleton保持中断时的pose.

要将skeleton的pose重置为setup pose, 请使用 Skeleton.setupPose():

drawable.skeleton.setupPose();

这将把skeleton和槽位都重置为其setup pose设置. 使用 Skeleton.setupPoseSlots() 则仅将槽位重置为其setup pose配置.

AnimationState事件

AnimationState 将在动画播放的生命周期中触发事件. 你可以监听这些事件来按需响应. Spine Runtimes API 定义了以下几种 事件类型:

  • Start: 动画开始时触发.
  • Interrupted: 清空了某条动画轨道或设置了某个新动画后触发.
  • Completed: 当动画完成一次循环后触发.
  • Ended: 当不再应用某个动画后触发.
  • Disposed: 当销毁了某个动画的轨道条目后触发.
  • Event: 当用户定义的 事件 发生后触发.

要接收事件, 你可以在 AnimationState 上注册一个 AnimationStateListener 回调函数来接收所有动画的事件, 也可以注册到 TrackEntry 上以便监听队列中某个动画的事件:

const entry = drawable.animationState.setAnimation(0, "walk", true);
entry.listener = {
event: (entry, event) => console.log(`User defined event: ${event.data.name}`),
complete: (entry) => console.log(`Animation loop completed.`)
}

drawable.animationState.setListener({
end: (entry) => console.log(`Animation ${entry.data.name} has ended and will not be applied again.`
});

参见 example/animation-state-events.html 示例.

皮肤

许多应用程序和游戏都允许用户用头发、眼睛、裤子或耳环、包包等配饰等部件来创建自定义形象. 而用Spine可以通过 皮肤混搭 来实现这一功能.

这样写可以从其他皮肤中创建自定义皮肤:

// Create a custom, empty skin
const skin = new spine.Skin("custom");

// Add other skins to the custom skin
skin.addSkin(skeletonData.findSkin("skin-base"));
skin.addSkin(skeletonData.findSkin("nose/short"));
skin.addSkin(skeletonData.findSkin("eyelids/girly"));
skin.addSkin(skeletonData.findSkin("eyes/violet"));
skin.addSkin(skeletonData.findSkin("hair/brown"));
skin.addSkin(skeletonData.findSkin("clothes/hoodie-orange"));
skin.addSkin(skeletonData.findSkin("legs/pants-jeans"));
skin.addSkin(skeletonData.findSkin("accessories/bag"));
skin.addSkin(skeletonData.findSkin("accessories/hat-red-yellow"));
skeleton.setSkin(skin);
skeleton.setupPoseSlots();

使用 Skin() 构造函数创建自定义皮肤.

接下来, 使用 SkeletonData 通过 SkeletonData.findSkin() 按名称查找皮肤.

通过 Skin.addSkin() 将所有要组合的皮肤添加到新的自定义皮肤中.

最后, 通过 Skeleton.setSkin()Skeleton 上设置新皮肤, 并调用 Skeleton.setupPoseSlots() 以确保没有留下先前皮肤和/或动画的附件.

参见 example/mix-and-match.html 示例.

设置骨骼变换

在Spine编辑器中创作骨架时, skeleton定义在所谓的骨架坐标系中. 使用 Bone.worldToLocal() 方法将相对于画布的触摸或鼠标坐标转换为骨骼的坐标系.

当你想基于用户输入驱动骨骼位置时, 这可能很有用.

参见 example/ik-following.html 示例.

性能

spine-canvaskit使用 CavansKit.MakeVertices()Canvas.drawVertices() 来绘制各个skeleton附件的网格. 虽然Skia会在后台对这些网格合批, 但在spine-canvaskit中对附件网格合批, 更有可能提高性能..

详情参见 example/micro-benchmark.html 示例.