spine-haxe 运行时文档

Licensing

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

简介

spine-haxe运行时包括一个渲染器无关的核心模块, 该模块是一个Spine Runtimes核心API的Haxe实现, 以及一个用 starling框架 实现的渲染器. spine-haxe针对HTML5进行了测试, 且目前可使用 WebGL 进行渲染.

spine-haxe运行时支持除premultiplied alpha atlases和双色tinting以外的所有Spine功能.

安装

spine-haxe的核心模块不含任何依赖项. 但Starling实现的渲染器有两个依赖项:openfl和starling. 因此使用spine-haxe前需要安装必要的依赖:

haxelib install openfl
haxelib install starling

依赖项安装好之后, 可以 下载安装最新版本的spine-haxe:

haxelib install spine-haxe-x.y.z.zip

请注意 lib.haxe.org 中没有spine-haxe库. 所以必须下载zip压缩包来安装这个库.

示例

spine-haxe运行时内置了数个用于功能展示的示例.

你可按以下步骤运行示例项目:

  1. 安装Git和Haxe.
  2. 配置haxelib并安装必要的依赖项:
haxelib setup
haxelib install openfl
haxelib run openfl setup
haxelib install starling
  1. 克隆spine-runtimes代码库, 导航至 spine-runtimes/spine-haxe 目录下, 然后执行lime test命令:
git clone https://github.com/esotericsoftware/spine-runtimes
cd spine-runtimes/spine-haxe
haxelib dev spine-haxe .
lime test html5

这将构建spine-haxe运行时. 最后打开浏览器便可在网页中浏览示例. spine-runtimes/spine-haxe/example/src 文件夹中包含所有示例的代码.

更新spine-haxe运行时

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

更新spine-haxe运行时的第一步是从 https://esotericsoftware.com/files/spine-haxe/4.2/spine-haxe-x.y.z.zip 下载所需版本的运行时 (请将链接中的x.y.z更改为你需要的版本). 然后运行 haxelib install spine-haxe:x.y.z.zip.

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

使用spine-haxe

spine-haxe 运行时支持除premultiplied alpha atlases和双色tinting以外的全部Spine功能.

spine-haxe使用starling作为框架. spine-haxe已经针对HTML5进行了测试, 且目前可使用WebGL进行渲染. 但运行时尚不支持使用 Canvas APIs 进行渲染.

资产管理

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

请按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类型发送.

更新Spine资产

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

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

核心类

spine-haxe API构建在一个不依赖于starling和openfl的通用Haxe运行时上. 它提供了平台无关的核心类和算法, 用于加载、查询、修改和动画化Spine skeleton.

本节将简要讨论日常使用spine-haxe时会遇到的关键核心类. 请同时查阅 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-haxe运行时的starling渲染器也构建在这些核心类之上.

Spine Haxe运行时starling渲染器

Spine Haxe运行时的starling渲染器暴露了两个类: StarlingTextureLoaderSkeletonSprite.

StarlingTextureLoader 是对Spine TextureLoader 的实现, 它用于在Starling中创建和销毁图像.

SkeletonSprite 则是对starling DisplayObject 类的扩展, 它用于播放Spine动画.

加载Spine资产

Spine资产, 如skeleton数据.json/.skel文件或.atlas文件, 是通过openfl的 Assets 类加载的.

在创建 SkeletonSprite 实例前, 必须先加载skeleton和atlas文件. 资产文件有两种加载方式:

  • Assets.getText(string): 加载文本类型的资产, 如 .json.atlas 文件.
  • Assets.getBytes(string): 加载二进制资产, 如 .skel 文件.

这里假设你已导出了名为 skeleton.skel 的二进制skeleton文件, atlas名称为 skeleton.atlas 且对应一个 skeleton.png 文件, 那么加载资源的写法便是:

haxe
var atlasFile = Assets.getText("skeleton.atlas");
var skeletonFile = Assets.getBytes("skeleton.skel");

资产加载完成后, 就需要获取 TextureAtlasSkeletonData 的实:

haxe
var atlas = new TextureAtlas(atlasFile, new StarlingTextureLoader("skeleton.atlas"));
var skeletondata = SkeletonData.from(skeletonFile, atlas);

这里 TextureAtlas 的构造函数需要一个 StarlingTextureLoader 实例. 因此必须向 StarlingTextureLoader 提供atlas文件的名称. 运行时会对各个texture atlas页图像进行透明加载, 无需在代码中显式加载它们.

Skeleton原始数据和atlas本身无法动画化或进行渲染, 因此需要基于它们构建一个 SkeletonSprite. 使用相一套资产实例化的 SkeletonSprite 们会共享这套skeleton数据和atlas.

创建SkeletonSprite实例

加载完skeleton原始数据和对应atlas后, 得先实例化出一个 AnimationStateData 才能实例化 SkeletonSprite
haxe
var animationStateData = new AnimationStateData(skeletondata);

此时便能实例化 SkeletonSprite 了:

haxe
// Instantiate the SkeletonSprite
var skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);

// Add the SkeletonSprite as a child of the DisplayObject in the stage
addChild(spineboy);

SkeletonDataAnimationStateData 传入 SkeletonSprite 的构造函数来创建 SkeletonSprite 实例.

若需获取skeleton的边界, 可调用skeleton的 getBounds() 函数. 而调用 SkeletonSpritegetAnimationBounds(string, bool) 函数则会获取包含动画的 Rectangle, 其第一个参数是动画名称, 第二个参数是一个布尔值, 表示在计算边界时是否需要考虑剪辑附件(clipping attachments). 请注意, SkeletonSpritegetBounds(DisplayObject) 始终返回大小为 0, 0 的 Rectangle, 因此不要使用该方法.

SkeletonSprite

SkeletonSprite 则是对starling DisplayObject 类的扩展,它将 Skeleton 以及其关联的 AnimationState 的存储、更新和渲染功能统一在了一起. 如上节所述, SkeletonSprite 实例是基于skeleton数据和atlas创建. 在代码中通过 skeletonstate 字段便能访问 SkeletonAnimationState.

每一帧中, SkeletonSprite 容器都会:

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

应用动画

在Spine容器中呈现的skeleton上应用动画可通过 AnimationState 实现.

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

要在0号轨道上设置某段动画, 可调用 AnimationStatesetAnimation 方法:

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

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

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

haxe
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时长:

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

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

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

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

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

使用空动画可以将skeleton从setup pose平滑过渡为动画内容, 反之亦可:

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保持最后的姿势.

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

spineObject.skeleton.setToSetupPose();

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

AnimationState事件

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

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

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

haxe
// add callback to the AnimationState
spineObject.state.onStart.add(entry -> trace('Started animation ${entry.animation.name}'));
spineObject.state.onInterrupt.add(entry -> trace('Interrupted animation ${entry.animation.name}'));
spineObject.state.onEnd.add(entry -> trace('Ended animation ${entry.animation.name}'));
spineObject.state.onDispose.add(entry -> trace('Disposed animation ${entry.animation.name}'));
spineObject.state.onComplete.add(entry -> trace('Completed animation ${entry.animation.name}'));
spineObject.state.onEvent.add((event entry) -> trace('Custom event for ${entry.animation.name}: ${event.data.name}'));

// add callback to the TrackEntry
var trackEntry = spineObject.state.setAnimationByName(0, "walk", true);
trackEntry.onEvent.add((entry, event) => trace('Custom event for ${entry.animation.name}: ${event.data.name}'));

参见 EventsExample.hx 了解更多.

皮肤

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

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

haxe
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.skeletonskin = skin;
spineObject.skeleton.setToSetupPose();

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

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

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

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

完整示例代码请参见 MixAndMatchExample.hx.

设置骨骼变换

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

SkeletonSprite 提供了 haxeWorldCoordinatesToBone(point, bone) 方法, 该方法接受一个 SkeletonSprite 坐标点作为参数, 并将其转换为相对于指定骨骼的skeleton坐标点.

反之, 从skeleton坐标系转换到Haxe坐标系, 可以使用 SkeletonSprite.skeletonToHaxeWorldCoordinates(point) 实现.

完整示例代码参见 ControlBonesExample.hx.

访问Spine运行时API

spine-phaser 通过 SkeletonSprite 属性 skeletonstatestate.data 公开了全部核心API. 详细信息请参阅这些类的JS文档和通用的 Spine运行时指南.

设置最简项目

如果你只想在Haxe中使用Starling展示Spine动画, 或者你想从自带Spine动画的项目开始开发, 你可以按照以下步骤创建项目.

当你安装完所有必要 依赖项 之后, 可以这样新建项目:

openfl create starling:project MySpineProject

这将创建一个名为 MySpineProject 的文件夹, 其中包含了用OpenFL启动一个空项目所需的全部文件. 用你喜欢的Haxe IDE打开 MySpineProject. 将资产复制到 Assets 文件夹中. 最后编辑 project.xml 文件添加spine-haxe作为依赖项:

<haxelib name="spine-haxe" />

如需加载二进制格式的skeleton .skel, 将这行:

<assets path="Assets" rename="assets" />

替换为下面这两行 (否则openfl会将 skel 作为纯文本文件加载, 致其损坏):

<assets path="Assets" rename="assets" exclude="*.skel"/>
<assets path="Assets" rename="assets" include="*.skel" type="binary" />

打开 Source/Game.hx 文件, 用以下内容替换构造函数:

haxe
public function new () {
   super ();
   var atlas = new TextureAtlas(
      Assets.getText("assets/raptor.atlas"),
      new StarlingTextureLoader("assets/raptor-pro.atlas"));
   var skeletondata = SkeletonData.from(Assets.getText("assets/raptor-pro.json"), atlas, .5);
   var animationStateData = new AnimationStateData(skeletondata);
   var skeletonSprite = new SkeletonSprite(skeletondata, animationStateData);
   skeletonSprite.x = Starling.current.stage.stageWidth / 2;
   skeletonSprite.y = Starling.current.stage.stageHeight * .75;
   skeletonSprite.state.setAnimationByName(0, "walk", true);
   addChild(skeletonSprite);
   Starling.current.juggler.add(skeletonSprite);
}

最后别忘了导入必要的类. 此时便可以用这个命令从项目目录下启动它了:

lime test html5

设置VS Code

我们推荐安装了以下插件的 Visual Studio Code 作为IDE使用:

  1. Haxe扩展
  2. HXCPP调试器扩展
  3. Lime扩展

这些扩展赋予了VSCode编辑器自动补全、调试和构建等IDE功能.

如需调试构建, 请在VS Code底部的状态栏中设置Lime目标, 例如 HTML5 / Debug. 按 F5 即可执行 lime 运行配置.