# spine-canvaskit 运行时文档

> **Licensing**
>
> 将官方的Spine运行时整合到你的应用程序之前, 请仔细阅读 [Spine运行时许可页面](/spine-runtimes-license).

# 简介

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

spine-canvaskit要求使用CanvasKit 0.39.1及以上版本, 并支持除 [双色tinting](/spine-slots#着成黑色) 外的全部Spine功能.

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

## 安装

> **请注意:** spine-canvaskit的 `major.minor` 版本必须与导出skeleton的Spine编辑器的 `major.minor` 版本匹配. 更多详情请见 [Spine编辑器和运行时版本管理指南](/spine-runtime-architecture#Versioning).

### 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模块](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules), 可原生地在Node.js和所有现代浏览器中使用, 也能通过webpack、rollup或esbuild等工具打包. 它包含了源码映射(source maps)以便调试.

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

### 原生JavaScript

可以通过script标签从 [unpkg CDN](https://unpkg.com/) 将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](https://nodejs.org/en)
1. 克隆 [spine-runtimes仓库](https://github.com/esotericsoftware/spine-runtimes)
1. 在终端中:

```
cd path/to/spine-runtimes/spine-ts
npm run dev
```

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

包含以下示例:

* [`spine-canvaskit/example/headless.js`](/git/spine-runtimes/spine-ts/spine-canvaskit/example/headless.js): 一个Node.js命令行应用程序, 加载 [Spineboy](/spine-examples-spineboy) skeleton和atlas, 并将 `portal` 动画渲染为动画PNG文件. 调用 `npm run dev` 后, 可以在终端中运行 `node spine-canvaskit/example/headless.js`, 这将生成一个名为 `output.png` 的文件, 其中包含动画.
* [`spine-canvaskit/example/index.html`](/git/spine-runtimes/spine-ts/spine-canvaskit/example/index.html): 一个web应用程序, 演示了通过CanvasKit在浏览器中加载和渲染Spine skeleton.
* [`spine-canvaskit/example/animation-state-events.html`](/git/spine-runtimes/spine-ts/spine-canvaskit/example/animation-state-events.html): 一个web应用程序, 演示了为动画状态事件设置监听器.
* [`spine-canvaskit/example/mix-and-match.html`](/git/spine-runtimes/spine-ts/spine-canvaskit/example/mix-and-match.html): 一个web应用程序, 演示了组合多个皮肤.

## 更新spine-canvaskit运行时

在更新项目中的spine-canvaskit运行时前, 请先阅读 [Spine 编辑器和运行时版本管理指南](/spine-runtime-architecture#版本控制).

要更新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资产

![](/img/spine-runtimes-guide/spine-ue4/export.png)
请按照Spine用户指南中的说明:

1. [导出skeleton和动画数据](/spine-export)
2. [导出包含skeleton图像的texture atlases](/spine-texture-packer)

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

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

![](/img/spine-runtimes-guide/spine-ue4/exported-files.png)

1. `skeleton-name.json` 或 `skeleton-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版本管理](/spine-versioning#同步版本).

## 初始化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](/git/spine-runtimes/spine-ts/spine-core) 运行时之上, 该运行时提供了平台无关的核心类和算法, 用于加载、查询、修改和动画化Spine skeleton. 核心类也是spine-canvaskit的一部分.

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

[`Atlas`](/git/spine-runtimes/spine-ts/spine-core/src/TextureAtlas.ts) 类存储从 `.atlas` 文件及其对应的 `.png` 图像文件加载的数据.

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

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

[`AnimationState`](/git/spine-runtimes/spine-ts/spine-core/src/AnimationState.ts) 类负责跟踪应应用于skeleton的动画, 基于上一帧和当前渲染帧之间经过的时间推进和mix这些动画, 并将动画应用于skeleton实例, 从而设置其当前姿势. `AnimationState` 查询 [`AnimationStateData`](/git/spine-runtimes/spine-ts/spine-core/src/AnimationStateData.ts) 实例以获取动画之间的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` 内的 `Skeleton` 和 `AnimationState`:

```
// 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);
```

有关 `Skeleton` 和 `AnimationState` API 的全面讨论, 请参阅 [Spine运行时指南](/spine-runtimes-guide). 下面你将找到最基本的API用法示例来帮助你入门.

## 应用动画
>
> **请注意:** 更多深入信息请参阅Spine运行时指南中的 [应用动画](/spine-applying-animations).

`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` 类文档](/git/spine-runtimes/spine-ts/spine-core/src/AnimationState.ts#L798).

> **请注意:** 不要在函数外部保留 `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 定义了以下几种 [事件类型](/git/spine-runtimes/spine-ts/spine-core/src/AnimationState.ts#L1095):

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

要接收事件, 你可以在 `AnimationState` 上注册一个 [`AnimationStateListener`](/git/spine-runtimes/spine-ts/spine-core/src/AnimationState.ts#L1161) 回调函数来接收所有动画的事件, 也可以注册到 `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`](/git/spine-runtimes/spine-ts/spine-canvaskit/example/animation-state-events.html) 示例.

## 皮肤

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

这样写可以从其他皮肤中创建自定义皮肤:
```
// 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`](/git/spine-runtimes/spine-ts/spine-canvaskit/example/mix-and-match.html) 示例.

## 设置骨骼变换

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

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

参见 [`example/ik-following.html`](/git/spine-runtimes/spine-ts/spine-canvaskit/example/ik-following.html) 示例.

## 性能

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

详情参见 [`example/micro-benchmark.html`](/git/spine-runtimes/spine-ts/spine-canvaskit/example/micro-benchmark.html) 示例.