spine-pixi 运行时文档

Licensing

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

简介

本页面为官方 PixiJS运行时的文档汇总页, 你可以在此找到官方 PixiJS 运行时列表及其对应的兼容版本:

运行时 PixiJS 版本 是否维护
spine-pixi-v8

8 (最低 8.4.0)

Yes

spine-pixi-v7

7 (最低 7.2.0)

Yes

spine-pixi

7 (最低 7.2.0)

No (切到 spine-pixi-v7)

除非另有说明, 否则本文中的示例和 GitHub 文件链接均表示 v8 版本. 然而本文中所有内容亦适用于 v7 版本, 只需将路径和链接中的 -v8 替换为 -v7 即可.

Spine PixiJS 运行时基于 spine-ts core 实现的, 是 Spine Runtimes 核心 API 的渲染器无关 TypeScript 实现.

spine-pixi-v8 既可以使用 WebGL 也可以使用 WebGPU进行渲染. spine-pixi-v7 则只能使用 WebGL 渲染(WebGPU 支持是在 PixiJS 8 中引入的). 运行时不支持使用 canvas APIs 渲染.

Spine PixiJS 运行时支持全部 Spine 功能.

安装

要在 Pixi 项目中使用 Spine PixiJS 运行时, 必须首先导入其源码.

原生JavaScript

在原生 JavaScript 中安装运行时, 请使用 script 标记引入 unpkg 中的 spine-pixi-v8 运行时(也可以自建服务):

<script src="https://unpkg.com/@esotericsoftware/spine-pixi-v8@4.2.*/dist/iife/spine-pixi-v8.js"></script>

注意: 确保 spine-pixi 的 major.minor 版本与用来导出资产的Spine编辑器的 major.minor 版本一致. 更多详情请见 同步版本号 一节.

这将自动安装 Spine 扩展, 然后便可在 Pixi 项目中使用 spine-pixi-v8 运行时. 查看 index.html 可了解完整示例.

spine-pixi-v8 软件包不仅提供了用于调试的源码映射, 还提供了 spine-pixi-v8 的精简版(minified), 在 unkg URL 中将 .js 后缀替换为 .min.js 即可使用.

如需自行构建 spine-pixi-v8.js, 请遵循 spine-ts 的 README.md 文档内容.

NPM和Yarn

若使用 NPM 或 Yarn 管理软件包依赖关系, 则按常规方式安装 spine-pixi-v8 即可:

npm install @esotericsoftware/spine-pixi-v8@~4.2.0

注意: 确保 spine-pixi 的 major.minor 版本与用来导出资产的Spine编辑器的 major.minor 版本一致. 更多详情请见 同步版本号 一节.

完成后请直接导入 Spine 类.

import PIXI from "pixi.js"
import { Spine } from '@esotericsoftware/spine-pixi-v8';

这也将自动安装 Spine 扩展, 然后便可在项目中使用 Spine PixiJS 运行时. 最小示例请查看 esbuild/TypeScript project.

模块包中包含源码映射和 d.ts 类型信息, 可用于调试改进和实际开发.

示例

spine-pixi 运行时中内置了几个演示用的功能示例.

如需在本地运行这些示例则:

  1. 在操作系统上安装 Git 和 Node.js.
  2. 克隆 spine-runtimes 代码库: git clone https://github.com/esotericsoftware/spine-runtimes
  3. 导航至 spine-runtimes/spine-ts 目录, 然后运行 npm install & npm run dev.

以上步骤将构建 spine-pixi-v8 运行时, 并打开浏览器显示所有基于 spine-ts 运行时的示例目录.

点击便可查看你感兴趣的 spine-pixi-v8 示例, 其对应代码位于 spine-runtimes/spine-ts/spine-pixi-v8/example 文件夹.

更新spine-pixi-v8运行时

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

如需更新原生 JavaScript 的 spine-pixi-v8 运行时, 请更改 spine-pixi-v8 的 unkg URL 中 src 属性或 script 标记中的版本号.

而使用 NPM 或 Yarn 更新 spine-pixi-v8 运行时则请更改 package.json 文件中的版本号.

注意: 若更改了spine-haxe的 major.minor 版本, 则必须使用同版本的Spine编辑器重新导出Spine skeleton!更多详情请见 同步版本号 一节.

使用 spine-pixi-v8

Spine PixiJS 运行时支持全部 Spine 功能. spine-pixi-v8 可使用 WebGLWebGPU 进行渲染. spine-pixi-v7 只能使用 WebGL 渲染(WebGPU 支持是在 PixiJS 8 中引入的). 运行时不支持使用 canvas APIs 渲染.

资产管理

导出适用于Spine PixiJS的资产

请按Spine用户指南中的操作说明来:

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

导出的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格式, 因为二进制格式尺寸更小且加载速度更快.

在服务器上提供这些文件时, 应设置正确的 MIME 类型:

  • skel 文件为 application/octet-stream
  • json 文件为 application/json
  • atlas 文件为 application/octet-stream
  • png 文件为 image/png

更新Spine资产

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

确保spine-haxe的 major.minor 版本与用来导出资产的Spine编辑器的 major.minor 版本一致. 更多详情请见 同步版本号 一节.

核心类

Spine PixiJS API 构建于通用的 TypeScript spine-core 运行时上, 该运行时提供了平台无关的核心类和算法, 可用于加载、查询、修改 Spine Skeleton和驱动 Skeleton 动画.

本节将简要讨论日常使用 Spine PixiJS 时会遇到的关键核心类. 请同时查阅 Spine 运行时指南 详细了解Spine运行时的架构、核心类及API使用方法.

TextureAtlas 类存储了从 .atlas 文件和其对应的 .png 图像中加载的数据.

SkeletonData 类存储了从 .json.skel Skeleton文件中加载的数据. Skeleton数据包含了骨骼层次结构、槽位、附件、约束、皮肤和动画的信息.通常做法是通过 Atlas 来加载 SkeletonData 实例, 实例再从 Atlas 中获取Skelton所需的图片. 因此 Atlas 可视为创建 Skeleton 实例的蓝图. 同一套atlas和Skelton数据可以实例化出多个skeleton实例, 这些实例将共享被加载的数据, 由此最大限度地缩短运行时加载时间并减小内存开销.

Skeleton 类存储的是用 SkeletonData 实例创建的Skeleton实例. Skeleton实例会存储其当前pose, 即当前的骨骼位置和槽位、附件及活动皮肤的配置. 虽然手动修改骨骼变换(transform)可以更改当前pose, 但更常规的做法是应用 AnimationState 中的动画来更改当前pose.

AnimationState 类负责跟踪需应用于skeleton的动画, 根据上一帧和当前渲染帧之间的时间提前和mix这些动画, 并将动画应用于skeleton实例, 从而设置其当前pose. AnimationState 会查询 AnimationStateData 以获取动画间的mix时间, 如果一对动画之间没有设置mix时间, 则会使用默认mix时间.

Spine Pixi 运行时便构建于这些核心类之上.

Spine Pixi运行时

spine-pixi-v8 运行时会自动在 Pixi 中安装两个 Asset 类型的扩展:skeletonLoaderatlasLoader. 它们给 PIXI.Assets加入了(预)加载 .json.skel.atlas 文件的功能.

Spine 类是 Pixi ViewContainer 类的扩展 (PixiJS 7 中则是 Container), 并提供了一个工厂函数, 它可以用已加载的 Skeleton 数据和 atals 文件创建 Spine 的容器实例.

在 PixiJS v8, 它还额外安装了 SpinePipe, 这是 WebGPUPipes、WebGLPipes 和 CanvasPipes 的扩展. 最后它还会安装 DarkTintBatcher, 这是用于支持 tint black 的合批处理器. 而在 PixiJS v7 中, 它会安装 RendererPlugin 类型的扩展, 用于渲染在附件上使用了 tint black 的 Spine 容器.

加载Spine资产

Spine 资产(如 Skeleton 数据 .json/.skel 文件或 .atlas 文件)可通过 PIXI.Assets 类实例(如 Assets.load )中的常用函数加载.

在创建 Spine 容器实例前, 必须加载 Skeleton 和对应的 atals 文件. 一种方法是用 Assets.addAssets.load 函数来加载它们.

  • Assets.add({ alias: string, src: string }):可以指定如何解析 url 中的资产 alias. 该函数可用于所有 Spine 资产文件(.json.skel.atlas).
  • Assets.load(string[]):加载用 Assets.add 添加的资产 alias.

假设你将 Skeleton 数据导出为二进制Skeleton文件 skeleton.skel, 并将 atlas 导为 skeleton.atlas 文件, 其对应的图像文件为 skeleton.png, 则可以像这样加载资产:

PIXI.Assets.add({ alias: "skeleton-data", src: "path/to/skeleton.skel" });
PIXI.Assets.add({ alias: "skeleton-atlas", src: "path/to/skeleton.atlas" });
await PIXI.Assets.load["skeleton-data", "skeleton-atlas"];

Assets.load 函数从 skeleton.skel 文件加载 SkeletonData, 并将其缓存于 skeleton-data 键中. 它还会从 skeleton.atlas 文件加载 TextureAtlas, 并从对应的 skeleton.png 文件中加载texture. Atlas缓存在 skeleton-atlas 键中, 而每个texture atlas页图像都是透明加载, 无需显式加载它们.

预加载完成后, 可通过 Asset.get(atlasKey) 访问 TextureAtlas. 你也同样可以通过 Asset.get(skeletonKey) 访问原始的 .skel 文件. 不过在此时, SkeletonData 实例还没准备好.

原始的Skeleton数据和atlas本身无法进行动画或渲染. 因为你需要用它们构建一个 Spine容器. 用同一个资产键(key)实例化的 Spine 容器会共享同一份Skeleton数据和atlas.

你也可以使用 Pixi bundles 来加载资产. 要使 Assets.init 中的 texturePreference.formattexturePreference.resolution 属性生效, 需按 FILENAME@RESOLUTION.FORMAT.atlas 格式指定atlas名称. 以下是一个 bundle 中的 manifest.json 文件示例, 演示了如何在指定了 formatresolution 的同时加载该文件:

json
{
   "bundles": [
      {
         "name": "spineboy",
         "assets": [
            {
               "alias": ["spineboyAtlas"],
               "src": [
                  "spineboy.png.atlas",
                  "spineboy@2x.png.atlas",
                  "spineboy@3x.png.atlas",
                  "spineboy.webp.atlas",
                  "spineboy@2x.webp.atlas",
                  "spineboy@3x.webp.atlas"
               ]
            },
            { "alias": ["spineboyData"], "src": ["spineboy-pro.json"] }
         ]
      }
   ]
}
javascript
// Initialize Assets with given manifest and preference
await PIXI.Assets.init({
basePath: './assets/spineboy-bundle',
manifest: './manifest.json',
texturePreference: {
   resolution: Math.min(PIXI.utils.isMobile.any ? window.devicePixelRatio : 3, 3),
   format: ['webp', 'png'],
},
});

// Load the bundle that includes the skeleton data and atlas
await PIXI.Assets.loadBundle("spineboy");

创建Spine容器对象

加载了原始Skeleton数据和对应的atlas后, 可通过 Spine 类中的 from() 静态函数创建 Spine 容器:

javascript
// Pixi app creation
const app = new PIXI.Application({ ... });
...
// Create a Spine container through the Spine.from factory
const spineboy = Spine.from({ skeleton: "spineboyData", atlas: "spineboyAtlas", ... });

// Add the Spine container to the stage
app.stage.addChild(spineboy);

Spine 类中的 from() 函数需要一个 SpineFromOptions 对象作为参数. 它需要:

  • skeleton: 加载到 Assets 中的 Skeleton .skel.json 文件的资产名称
  • atlas: 加载到 Assets 中的 atlas 文件的资产名称
  • (可选) scale: 传入Skeleton读取器(reader)的值. 若省略则默认为 1.
  • (可选) darkTint: 若置为 true, 则使用 dark tint 渲染器来渲染 Skeleton; 若置为 false, 则使用默认的 pixi 渲染器来渲染 Skeleton; 若为 undefined, 则在存在 tint black 槽位时使用 dark tint 渲染器
  • (可选) autoUpdate: 设置 Spine 实例的 autoUpdate 值. 若省略则置为 true, 动画将会自动播放. 反之则将调用 update 方法.

Spine容器

Spine 容器是 Pixi ViewContainer(PixiJS 7 则是 Container)的扩展, 用于存储、更新和渲染 Skeleton 及其相关的 AnimationState. 如前一节所述, Spine 容器实例通过 Skeleton 数据和atlas创建. SkeletonAnimationState 可分别通过 skeletonstate 字段访问.

在每一帧中 (当 autoUpdate 置为 true 时), Spine 容器都会:

  • 更新 AnimationState
  • AnimationState 应用到 Skeleton
  • 更新 Skeleton 的世界变换, 产生一个新的pose
  • 以当前pose来渲染 Skeleton

边界

调用 getBounds 函数时, 其返回的容器边界尺寸是基于当前皮肤和动画计算得出的. 如果希望根据 setup pose 确定边界, 请在创建容器后立即调用 getBounds(). 你可能需要先配置好皮肤再计算边界.

应用动画

使用 AnimationState 可对 Spine 容器中显示的Skeleton应用动画.

注意: 关于动画轨道和队列动画的详情, 请参阅Spine运行时指南中的 应用动画 一节..

调用动画状态 setAnimation 可以在轨道 0 上设置某段动画:

javascript
spineObject.state.setAnimation(0, "walk", true);

第一个参数指定轨道号, 第二个参数表示动画名称, 第三个参数指定是否循环播放动画.

可以使用 addAnimation 来队列多段动画:

javascript
spineObject.state.setAnimation(0, "walk", true);
spineObject.state.addAnimation(0, "jump", 2, false);
spineObject.state.addAnimation(0, "run", 0, true);

addAnimation() 的第一个参数是轨道号. 第二个参数是动画的名称. 第三个参数指的是延迟时间(以秒为单位), 延迟后该动画就会替换轨道上的前一个动画. 最后一个参数定义是否应循环播放动画.

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

从一个动画过渡到另一个动画时, AnimationState 会在某段时间内mix(混合)动画, 该时间称为mix时长. 这些mix时长保存在 AnimationStateData 实例中, 而 AnimationState 会从中获取mix时长.

也可通过 AnimationState.data 属性获取 AnimationStateData 实例. 你既可以为它设置全局的默认的mix时长, 也可以单独设置某对动画间的mix时长:

javascript
spineObject.state.data.setDefaultMix = 0.2;
spineObject.state.data.setMix("walk", "jump", 0.1);

在设置或添加动画后, 会返回一个 TrackEntry 对象, 通过该对象可以进一步定制动画的播放细节. 例如, 你可以用轨道条目来设置mix时长或者反向播放动画:

javascript
const entry = spineObject.state.setAnimation(0, "walk", true);
entry.mixDuration = 0.4;
entry.reverse = true;

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

注意: 切勿不要函数作用域外保留 TrackEntry 实例. 轨道条目会在内部重复使用, 因此一旦发生轨道条目的 销毁(dispose)事件, 轨道条目就会失效.

使用空动画可以将 skeleton 从 setup pose 平滑过渡到动画中的动作, 反之亦可:

javascript
spineObject.state.setEmptyAnimation(0, 0);
spineObject.state.addAnimation(0, "walk", 0).mixDuration = 0.5;
spineObject.state.addEmptyAnimation(0, 0.5, 6);

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

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

调用 AnimationState.clearTrack() 可以立即清除轨道上的所有动画. 要清空全部轨道, 可使用 AnimationState.clearTracks(). 这将使 skeleton 保持最后的姿势, 而这通常不是需要的效果. 所以此时可以使用空动画自然地过渡到setup pose.

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

javascript
spineObject.skeleton.setToSetupPose();

这会把骨骼和槽位重置为setup pose中的配置. 使用 Skeleton.setBonesToSetupPose()Skeleton.setSlotsToSetupPose() 则只会将骨骼或槽位重置为setup pose设置.

AnimationState事件

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

  • start: 动画开始时触发.
  • interrupt: 当清除了某条动画轨道或设置了某个新动画时触发.
  • end: 当不再应用某个动画时触发.
  • dispose: 当销毁了某个动画的轨道条目时触发.
  • complete: 当动画完成循环时触发.
  • event:当用户定义的事件触发时触发.

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

javascript
spineObject.state.addListener({
   start: (entry) => log(`Started animation ${entry.animation.name}`),
   interrupt: (entry) => log(`Interrupted animation ${entry.animation.name}`),
   end: (entry) => log(`Ended animation ${entry.animation.name}`),
   dispose: (entry) => log(`Disposed animation ${entry.animation.name}`),
   complete: (entry) => log(`Completed animation ${entry.animation.name}`),
   event: (entry, event) => log(`Custom event for ${entry.animation.name}: ${event.data.name}`)
})

trackEntry.listener = {
   event: (entry, event) => log(`Custom event for ${entry.animation.name}: ${event.data.name}`)
}

请阅读 events-example.html 了解更多详情.

皮肤

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

可以像这样从其他皮肤中组合一套自定义皮肤:

javascript
const skeletonData = spineObject.skeleton.data;
const skin = new spine.Skin("custom");
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"));
spineObject.skeleton.setSkin(skin);
spineObject.skeleton.setToSetupPose();

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

从skeleton中可获取 SkeletonData. 使用 SkeletonData.findSkin() 则可以按名称查找皮肤.

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

最后, 在 Skeleton 上设置新皮肤, 并调用 Skeleton.setSlotsToSetupPose() 来确保先前的皮肤和/或动画附件没有残留.

完整示例代码参见 mix-and-match-example.html.

设置骨骼变换

在Spine Editor中创建skeleton时, skeleton定义skeleton世界坐标系下——即所谓的"skeleton坐标系". 该坐标系可能与Haxe的坐标系不一致. 因此 SpineGameObject 的鼠标和触摸操作的坐标值需要转换到skeleton坐标系——比如用户需要通过触控来移动骨骼的时候.

Spine 容器提供了 pixiWorldCoordinatesToBone(point: { x: number, y: number}, bone: Bone) 方法, 该方法接受一个 Spine 容器坐标作为参数, 并将其转换为相对于指定骨骼的skeleton坐标点.

反之, 从 skeleton 坐标系转换到 Pixi 坐标系, 可以使用 Spine.skeletonToPixiWorldCoordinates(point: { x: number, y: number}) 实现.

完整示例代码请参见 control-bones-example.html.

在槽位中添加 Pixi 对象

Spine 类有三种方便的方法, 可为 pixi Container 添加或移除槽位.

addSlotObject (slotRef: number | string | Slot, pixiObject: Container): void

此方法会将 pixiObject 添加到被 slotRef 引用的槽位中. 你可以传入名称、槽位索引或槽位对象本身.

每个槽位只能设置一个 Pixi 对象. 一旦添加了 Pixi 对象, 其变换(transform)将由 Spine 对象自动管理.

若需对添加的对象有进一步控制, 可以给槽位分配一个 Pixi Container, 然后便能在其中添加任意数量的 Pixi 对象, 也能设置它们的位置偏移、旋转角度、尺寸比例等参数.

如果要删除容器, 请调用该方法:

removeSlotObject (slotRef: number | string | Slot, pixiObject?: Container): void

其中 pixiObject 是可选参数. 若传入该参数, 仅移除内含了被 slotRef 引用的槽位中的 Pixi 对象.

要注意 Pixi 对象只是被移除, 而没有被销毁. 你需要主动管理这种手动添加的 Pixi 对象的生命周期.

若需列出添加到槽位的 Pixi 对象, 请使用该方法:

getSlotObject (slotRef: number | string | Slot): Container | undefined

如果存在对象, 则该方法会返回添加到被 slotRef 引用的槽位上的 Container.

完整的示例代码请参见 slot-objects.html.

网格的批处理尺寸

在 v7 中, Spine 对象使用 Pixi 网格来渲染附件. 同时 Pixi 会默认将顶点数多于 100 的网格标记为不可合批(batchable). 这将导致合批中断, 使绘制调用增加并降低性能表现. 为绕过这一限制, 建议将全局的 PIXI.Mesh.BATCHABLE_SIZE 设置成适配 skeleton 的值.

而 spine-pixi-v8 没有这种限制.

访问Spine运行时API

spine-pixi-v8 API 基于 spine-ts core, 因此你可以使用它提供的所有 API. 更多详情请参阅 Spine 运行时指南.