Spine 动画
使用面板选择各骨骼播放的动画、调整速度或暂停。
本教程使用 playcanvas-spine 插件,播放通过 Spine 编辑器制作的两个骨骼动画。坦克从二进制 .skel 导出文件加载,Celestial Circus 从 JSON 导出文件加载。完成后的场景见教程项目。用户手册的 Spine 页面详细介绍了插件。
添加插件
项目使用 playcanvas-spine 仓库 build 文件夹中的两个脚本:
playcanvas-spine.4.3.js:适用于 Spine 4.3 导出骨骼的插件,提供 spine 组件,并将 spine-core 运行时暴露为全局变量spine。spine.js:名为spine的脚本,根据其属性指定的资源,为实体添加 spine 组件。
在项目设置的脚本加载顺序中,插件排在 spine.js 之前。
导入骨骼
每个骨骼与纹理图集一起从 Spine 导出,包括 .atlas 文件和 .png 图集页面,然后导入项目的 spine43 文件夹:
| 文件 | 资源类型 |
|---|---|
tank-pro.skel | 二进制 |
celestial-circus-pro.json | JSON |
tank-pma.atlas, celestial-circus-pma.atlas | 文本 |
tank-pma.png, celestial-circus-pma.png | 纹理 |
二进制 .skel 导出文件比 JSON 小数倍,加载也更快。编辑器将其作为二进制资源导入。
Spine 4.3 在伽马空间渲染,因此纹理资源关闭了 sRGB。纹理资源保留文件名,因为图集会引用这些名称。
向实体添加骨骼
Tank 和 CelestialCircus 实体都具有脚本组件,挂载 spine 与 spineAnimation 脚本。spine 脚本通过以下属性引用骨骼资源:
- Atlas:图集文本资源。
- Skeleton:JSON 骨骼资源,供 CelestialCircus 使用。
- Skeleton Binary:二进制骨骼资源,Tank 使用它替代 JSON 骨骼。
- Textures:图集页面的纹理资源。
- Priority:重叠骨骼的绘制顺序,优先级数值较小的先渲染。
插件将 Spine 坐标缩放为原来的 0.01,因此在 Spine 编辑器中高 700 像素的骨骼,在场景中高 7 个单位。通过实体缩放使骨骼适配视图:女孩位于上半部分,坦克及其长条地面位于下半部分。
播放动画
spine 脚本在初始化时创建骨骼。实体上的 spineAnimation 排在它后面,因此自身 initialize 执行时骨骼已经存在,可以开始播放属性中指定的动画。省略骨骼或动画缺失检查后的脚本如下:
import { Script } from 'playcanvas';
export class SpineAnimation extends Script {
static scriptName = 'spineAnimation';
/**
* The name of the animation to play.
*
* @attribute
*/
animation = '';
/**
* Whether the animation loops.
*
* @attribute
*/
loop = true;
/**
* The playback speed of the skeleton.
*
* @attribute
* @range [0, 2]
*/
timeScale = 1;
initialize() {
const spine = this.entity.spine;
spine.state.setAnimation(0, this.animation, this.loop);
spine.spine.timeScale = this.timeScale;
}
}
entity.spine.state 是 spine-core 运行时的 AnimationState,负责在轨道上播放并混合动画;entity.spine.spine 是控制播放速度的插件对象。
控制动画
SpineControls 实体上的 spineControls 脚本创建 HTML 面板。其 postInitialize 在所有实体的脚本初始化后运行,此时骨骼已经存在,可以为场景中的每个 spine 组件添加一张面板卡片:
for (const component of this.app.root.findComponents('spine')) {
panel.append(this.createCard(component));
}
每张卡片通过 spine-core 对象控制对应骨骼:
const { state, skeleton } = component;
// mix between animations when switching, instead of switching instantly
state.data.defaultMix = this.mix;
// list the animations of the skeleton
for (const animation of skeleton.data.animations) {
select.append(new Option(animation.name, animation.name));
}
// play the selected animation
state.setAnimation(0, select.value, true);
// change the speed, or pause with a speed of 0
component.spine.timeScale = paused ? 0 : speed;
各卡片上的标记根据骨骼资源类型显示导出格式。更多 spine-core API 见 Spine 运行时指南。