spine-phaser 运行时文档
Licensing
将官方的Spine Runtime整合到你的应用程序中前请阅读Spine 运行时许可.
开始使用
spine-phaser 运行时基于spine-ts core实现, 它是对渲染器无关的 Spine Runtimes 核心 API 的 TypeScript 实现. spine-phaser 运行时支持了Phaser 3所支持的全部平台, 支持用Canvas APIs进行渲染和用WebGL进行渲染.
使用 Phaser WebGL 渲染器时, spine-phaser 运行时支持全部Spine功能. 而使用 Phaser Canvas 渲染器时, spine-phaser 运行时不支持网格、tint black 或 blend模式.
安装
要在 Phaser 项目中使用 spine-phaser 运行时, 必须首先引入(include)其源代码.
原生JavaScript
在原生 JavaScript 中, 请使用 script
标记来引入来自 unpkg 的 spine-phaser 运行时:
注意: 务必确保 spine-phaser 的
major.minor
版本与Spine编辑器的major.minor
版本一致. 请参阅同步版本了解更多.
下一步, 将 Spine 场景插件添加到 Phaser 游戏配置中:
...
plugins: {
scene: [
{ key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
]
}
}
new Phaser.Game(config);
至此项目中的所有场景均可使用 spine-phaser 运行时了.
请查看 basic-vanilla-js-example.html
, 了解完整的示例.
spine-phaser 软件包还提供了用于调试的源映射文件, 以及 spine-phaser 的精简版, 只需在 unpkg URL 中将.js
文件后缀替换为.min.js
即可引入.
若要在本地使用, 请按照 spine-ts README.md 中的说明构建 spine-phaser.js
.
使用NPM或Yarn
使用 NPM 或 Yarn 进行依赖管理时, 如常添加 spine-phaser 即可:
注意: 请确保 spine-phaser 的
major.minor
版本与您要导出的 Spine 编辑器的major.minor
版本一致. 请参阅同步版本了解更多.
下一步是把 Spine 场景插件添加到 Phaser 游戏配置中:
import {SpinePlugin} from "@esotericsoftware/spine-phaser"
const config = {
...
plugins: {
scene: [
{ key: "spine.SpinePlugin", plugin: SpinePlugin, mapping: "spine" }
]
}
}
new Phaser.Game(config);
现在项目中的所有场景现在都可以使用 spine-phaser 运行时了.
请访问 spine-phaser esbuild/TypeScript 项目 以获取最简示例.
本模块包还包含源映射文件以及 d.ts
类型声明(typings)文件, 以帮助调试和开发.
示例
spine-phaser 运行时包含多个示例项目, 用于演示其功能.
要在本地运行这些示例请按以下步骤操作:
- 在操作系统上安装 Git 和 Node.js.
- 克隆 spine-runtimes 仓库:
git clone https://github.com/esotericsoftware/spine-runtimes
- 在终端中导航到
spine-runtimes/spine-ts
目录, 然后执行npm install & npm run dev
.
该命令会构建 spine-phaser 运行时, 然后打开浏览器, 显示所有基于 spine-ts 运行时的示例项目列表.
点击你感兴趣的 spine-phaser 示例, 可在 spine-runtimes/spine-ts/spine-phaser/example
文件夹中查看对应项目的代码.
更新 spine-phaser 运行时
在更新项目的 spine-phaser 运行时前, 请查阅我们的Spine 编辑器和运行时版本管理指南.
要更新原生JavaScript中的 spine-phaser 运行时, 请更改src
属性或 script
标记中的版本字符串, 它们用于从 unpkg 获取 spine-phaser.
要在使用 NPM 或 Yarn 管理依赖关系时更新 spine-phaser 运行时, 直接更改package.json
文件中的版本号字符串即可.
注意: 若更改了spine-phaser软件包的
major.minor
版本, 就必须用同major.minor
版本的Spine编辑器重新导出Spine skeleton! 更多信息请参阅 同步版本 一节.
使用 spine-phaser
使用Phaser WebGL渲染器时, spine-phaser运行时支持全部Spine功能. 而使用 Phaser Canvas 渲染器时, spine-phaser 运行时不支持网格、tint black 或 blend模式.
管理美术资产
为spine-phaser导出资产
请按Spine用户指南中的步骤操作:
导出skeleton数据和skeleton texture atlas将生成以下文件:
skeleton-name.json
或skeleton-name.skel
, 其中包含skeleton和动画数据(JSON 或二进制格式).skeleton-name.atlas
, 其包含了texture atlas信息.- 单个或数个
.png
文件, texture atlas中的一页就对应着这样一个文件, 而texture atlas则保存着skeleton所需的图片包.
注意: 与导出的JSON格式相比, 导出的二进制skeleton体积更小, 加载速度也更快.
在服务器提供这些文件的访问时, 请确保服务器配置了正确的 MIME 类型.
更新Spine资产
在开发过程中, 你可能需要经常更新Spine skeleton数据和texture atlas文件. 您只需从Spine编辑器重新导出并替换Phaser项目中的资产文件即可更新这些源文件(.json、.skel、.atlas、.png).
请确保 spine-phaser 的 major.minor
版本与用来导出资产的Spine编辑器的 major.minor
版本一致. 请参阅 同步版本 一节了解更多详情.
核心类
spine-phaser API的构建基于通用的TypeScript spine-core 运行时, 该运行时提供了与平台无关的核心类和算法, 用于加载、查询、修改 Spine skeleton并将其动画化.
在此将简要讨论日常使用 spine-phaser 时将涉及的重要核心类. 请同时查阅 Spine 运行时指南 详细了解 Spine 运行时架构、核心类和 API 使用方法.
TextureAtlas
类存储了从.atlas
文件及其对应的.png
图像文件加载的数据.
SkeletonData
类存储了从 .json
或 .skel
格式的skeleton文件加载的数据. SkeletonData
包含了有关骨骼层次结构、槽位、附件、约束、皮肤和动画的信息. 加载 SkeletonData
实例时通常也需要传入一个 Atlas
实例, skeleton会从该实例中获取所需图片. 该类是创建 Skeleton
实例的蓝图. 可以通过同一套atlas和skeleton数据来实例化多个skeleton实例, 它们共享同一份数据, 因此可最大限度地降低运行时的加载时间和内存消耗.
Skeleton
类会存储skeleton的一个实例, 该skeleton由 SkeletonData
实例创建. Skeleton类会存储其当前pose, 即骨骼位置以及槽位、附件和活动皮肤的当前配置. 当前pose可以通过手动修改骨骼变换来计算得出, 而更常见的做法是通过 AnimationState
应用动画来改变当前pose.
AnimationState
类负责跟踪应应用于skeleton的动画, 根据上一帧和当前渲染帧之间的时间提前和mix这些动画, 并将动画应用于skeleton实例, 从而设置其当前姿势. AnimationState
会查询AnimationStateData
实例以获取动画间的mix时间, 如果一对动画之间没有设置mix时间, 则会使用默认mix时间.
spine-phaser 运行时正是建立在这些核心类之上.
Spine场景插件
spine-phaser 场景插件添加了将导出的.json
、.skel
和.atlas
文件(预)加载进场景的 LoaderPlugin (Scene.load
) 的功能. 它提供了访问原始skeleton数据和texture atlas的获取器(getter).
该场景插件用函数扩展了场景的 GameObjectFactory (Scene.add
) 和 GameObjectCreator (Scene.make
)类, 这样就能直接从加载的skeleton数据和atlas文件创建SpineGameObject
实例.
加载Spine资产
Spine资产, 如skeleton数据.json
/.skel
文件或.atlas
文件, 是通过SpinePlugin
附加到Phaser Scene
的LoaderPlugin (Scene.load
)类的附加函数加载的.
spineBinary(key: string, url: string, xhrSettings: Phaser.Loader.XHRSettingsObject)
: 从url
加载二进制skeleton.skel
文件, 然后通过key
访问.XHRSettingsObject
为可选参数.spineJson(key: string, url: string, xhrSettings: Phaser.Loader.XHRSettingsObject)
: 从url
加载 JSON skeleton.json
文件, 然后通过key
访问.XHRSettingsObject
为可选参数.spineAtlas(key: string, url: string, premultipliedAlpha: boolean, xhrSettings: Phaser.Loader.XHRSettingsObject)
:从url
加载texture atlas的.atlas
文件及其相关的.png
页文件, 然后通过key
访问.premultipliedAlpha
为可选参数, 用于指示atlas是否使用了premultiplied-alpha. 默认使用存储在.atlas
文件中的值.XHRSettingsObject
为可选参数.
假设您已将skeleton数据导出成名为 skeleton.skel
的二进制skeleton文件, 并将图集导出成名为 skeleton.atlas
的文件, 同时导出了一个对应的 skeleton.png
文件, 那么可以在场景的 preload()
函数中以如下方式加载资产:
this.load.spineBinary("skeleton-data", "path/to/skeleton.skel");
this.load.spineAtlas("skeleton-atlas", "path/to/skeleton.atlas");
}
preload()
函数从skeleton.skel
文件加载SkeletonData, 并将其缓存在skeleton-data
键下. 它还会从skeleton.atlas
文件加载TextureAtlas, 并从相应的skeleton.png
文件加载texture. Atlas缓存在键 skeleton-atlas
中. 每个texture atlas页的图像均采用透明加载机制, 无需显式加载.
预加载完成后, 可通过 Scene.spine.getAtlas(atlasKey)
来访问 TextureAtlas
. 同样, 也可以通过 Scene.spine.getSkeletonData(dataKey, atlasKey)
访问原始的 SkeletonData
. 请注意第二个参数: SkeletonData
实例只能在同时持有该skeleton数据的atlas时创建.
Skeleton数据和atlas本身不能进行动画或渲染, 而是使用一个从Skeleton数据和atlas构建的SpineGameObject
. SpineGameObject
实例可以共享相同的skeleton数据和atlas.
创建SpineGameObject实例
当加载了skeleton数据和相应的atlas, 就可以创建一个SpineGameObject
, 并可通过被SpinePlugin
添加到场景的GameObjectFactory (Scene.add
) 类和GameObjectCreator (Scene.make
)类中的spine()
函数, 将SpineGameObject
添加到当前场景中. 可以在场景的 create()
函数中使用它们:
// Create a SpineGameObject through the GameObjectFactory and add it to the scene
const spineObject = this.add.spine(400, 500, "skeleton-data", "skeleton-atlas");
// Create a SpineGameObject through the GameObjectCreator. It is not automatically
// added to the scene.
const spineObject2 = this.make.spine({
x: 200, y: 500,
dataKey: "skeleton-data", atlasKey: "skeleton-atlas"
});
// Manually add the game object to the scene
this.add.existing(spineObject2);
}
GameObjectFactory
上的spine()
函数将对象位置、skeleton数据的键值(key)和atlas的键值作为参数. 该函数将自动把对象加入场景.
GameObjectCreator
上的spine()
函数将SpineGameObjectConfig作为参数, 该参数还指定了位置、数据的键值和atals的键值. 但该函数不会自动将对象加入场景.
默认情况下, SpineGameObject
的尺寸取决于其setup pose的边界框尺寸. 向任意spine()
函数传入一个可选的 SpineGameObjectBoundsProvider
参数即可更改该行为.
边框提供器(bounds provider)会计算 Spine 游戏对象(game object)的边框尺寸. 默认使用的是 SetupPoseBoundsProvider
, 它会根据skeleton在setup pose中的边界来计算边界框尺寸.
另一个边框提供器是SkinsAndAnimationBoundsProvider
, 它会根据给定皮肤和动画的最大边界来计算边界框尺寸.
你也可以通过实现 SpineGameObjectBoundsProvider
接口来向spine()
函数传入自定义的边框提供器.
SpineGameObject
SpineGameObject
是一种Phaser GameObject, 它将存储、更新和渲染 Skeleton
和相关 AnimationState
的功能捆绑在一起. 如上节所述, SpineGameObject
实例基于skeleton数据和atlas创建. 可分别通过 skeleton
和 animationState
字段访问 Skeleton
和 AnimationState
.
SpineGameObject
在每帧都会:
- 更新
AnimationState
- 将
AnimationState
应用于Skeleton
- 更新
Skeleton
的世界变换, 得出新姿势 - 以当前姿势渲染
Skeleton
应用动画
通过AnimationState
可将动画应用于由SpineGameObject
呈现的skeleton.
注意: 请参阅 Spine 运行时指南中的 应用动画一节了解更多信息, 特别是关于动画轨道和动画队列的信息.
要在0号轨道上设置某个动画, 可以调用 AnimationState.setAnimation()
:
第一个参数指定了轨道号, 第二个参数则是动画名称, 第三个参数表示是否应循环播放动画.
你可以将多个动画队列起来:
spineObject.animationState.addAnimation(0, "jump", 2, false);
spineObject.animationState.addAnimation(0, "run", 0, true);
addAnimation()
的第一个参数是轨道号. 第二个参数是动画的名称. 第三个参数指的是延迟时间(以秒为单位), 延迟后该动画就会替换轨道上的前一个动画. 最后一个参数定义是否应循环播放动画.
在上例中, 首先播放的是"walk"
动画. 2 秒后, 播放一次"jump"
动画, 然后过渡到"run"
动画, 最后将循环播放这个"run"
动画.
从一个动画过渡到另一个动画时, AnimationState
会在某段时间内mix(混合)动画, 该时间称为mix时长. 这些mix时长定义在 AnimationStateData
实例中, 而 AnimationState
会从中获取mix时长.
也可通过 SpineGameObject
获取 AnimationStateData
实例. 你既可以设置全局的默认mix时长, 也可以单独设置某对动画间的mix时长:
spineObject.animationStateData.setMix("walk", "jump", 0.1);
设置或添加动画时会返回一个 TrackEntry
对象, 通过该对象可以进一步定制动画的播放细节. 例如, 你可以设置轨道条目来反向播放动画:
entry.reverse = true;
更多选项请参阅 TrackEntry
类文档.
注意: 小心不要函数作用域外保留
TrackEntry
实例. 轨道条目会在内部重复使用, 因此一旦发生轨道条目的 销毁(dispose)事件, 轨道条目就会失效.
在动画轨道上设置空动画或队列空动画, 就可将skeleton重置为setup pose:
spineObject.animationState.addEmptyAnimation(0, 0.5, 0.5);
setEmptyAnimation()
的第一个参数指定轨道号. 第二个参数指定mix持续时间(以秒为单位), 这段时间可以mix掉之前的动画并过渡到空动画.
addEmptyAnimation()
的第一个参数指定轨道号. 第二个参数指定mix持续时间(以秒为单位). 第三个参数则是延迟时间(以秒为单位), 轨道上的上一个动画在延迟后将mix到空动画.
通过 AnimationState.clearTrack()
可以立即清空轨道上的所有动画. 要一次性清空全部轨道, 可使用 AnimationState.clearTracks()
. 这将使skeleton保持中断时的pose.
要将skeleton的pose重置为setup pose, 请使用 Skeleton.setToSetupPose()
:
这将把skeleton和槽位都重置为setup pose. 使用 Skeleton.setSlotsToSetupPose()
则仅将槽位重置为setup pose设置.
AnimationState事件
AnimationState
将在动画播放的生命周期中触发事件. 你可以监听这些事件来按需响应. Spine Runtimes API 定义了以下几种 事件类型:
start
: 动画开始时触发.interrupt
: 当清空了某条动画轨道或设置了某个新动画时触发.end
: 当不再应用某个动画时触发.dispose
: 当销毁了某个动画的轨道条目时触发.complete
: 当动画完成循环时触发.event
:当用户定义的事件触发时触发.
要接收事件, 你可以在 AnimationState
上注册一个 AnimationStateListener
回调函数来接收所有动画的事件, 也可以注册到 TrackEntry
上以便监听队列中某个动画的事件:
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可以通过 皮肤混搭 来实现这一功能.
可以像这样从其他皮肤中创建自定义皮肤:
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坐标系"下. 该坐标系与Phaser中的坐标系大概率不一致. 因此 SpineGameObject
的鼠标和触摸操作的坐标值需要转换到skeleton坐标系中——比如用户可能需要通过触控来移动骨骼的时候.
SpineGameObject
为此提供了一个方法 phaserWorldCoordinatesToBone(point: { x: number, y: number}, bone: Bone)
, 该方法接受一个 SpineGameObject
坐标点作为参数, 并将其转换为相对于指定骨骼的skeleton坐标点.
反过来, 也就是从Skeleton坐标系转换到Phaser坐标系, 则可以用这句: SpineGameObject.skeletonToPhaserWorldCoordinates(point: { x: number, y: number})
来实现.
完整示例代码请参见 control-bones-example.html
.
访问Spine运行时API
spine-phaser 通过 SpineGameObject
属性 skeleton
、animationStateData
和 animationState
公开了全部 spine-ts core API. 详细信息请参阅这些类的JS文档和通用的Spine 运行时指南.
与Phaser Spine Plugin的差异
Phaser 提供了自己的 Spine Plugin, 它基于我们的 spine-ts WebGL
运行时. 我们在编写正式版spine-phaser运行时的时候已经尽力与Phaser的API保持了一致. 下文将探讨Phaser插件与spine-phaser官方运行时API间的几点差异.
从Spine 4.1开始, 我们将像维护其他Spine运行时一样维护spine-phaser运行时. 这意味着每当发布Spine编辑器的新版本时, 你都将及时获得运行时更新, 同时还将获得针对下一个Spine编辑器版本的测试版运行时, 并及时得到全部的最新改进和错误修复. Phaser维护者可能无法维持这样的更新步调, 因为维护Spine插件并不是他们的首要任务.
在API方面, 应该注意以下几点差异.
加载skeleton和atlas数据
使用Phaser Spine插件加载skeleton及其atlas只需一步:
而spine-phaser运行时中的atlas数据和skeleton数据需分开加载.
this.load.spineAtlas("spineboy-atlas", "spineboy.atlas");
这样就可以在不同的skeleton中共享同一个图集. spine-phaser 运行时还支持加载 JSON 和二进制skeleton数据, 而 Phaser Spine 插件只能加载 JSON skeleton数据. 最后, 加载器方法会自动推断skeleton atlas是否使用了pre-multiplied alpha.
spine-phaser skeleton数据加载器不支持从单个 JSON 文件加载多个spine skeleton, 然而 Phaser Spine 插件支持这一功能.
创建 SpineGameObject
实例
使用 Phaser Spine 插件创建 SpineGameObject
时只会引用一个已加载资产, 并提供了许多额外的可选参数.
spine-phaser 运行时需要指定skeleton数据和atlas的键值.
spineboy.animationState.setAnimation(0, "idle", true)
如上文所述, 你可以提供一个 SkeletonBoundsProvider
实例作为第 5 个参数. 但你不能通过此方法配置任何其他属性. 相反你可以直接访问 SpineGameObject
的方法和属性, 按照自己的喜好进行配置.
Spine容器
Phaser Spine插件有一个特殊的容器类, 名为 SpineContainer. 可用于批量渲染其内含的多个 SpineGameObject
实例并据此提高性能.
而该功能对于spine-phaser运行时就显得毫无必要, 因为渲染器会在可能的情况下自动处理后续的 SpineGameObject
实例, 而无关其容器父级.
示例项目移植
为进一步简化从Phaser Spine插件到官方spine-phaser运行时的迁移步骤, 我们已将大部分 Phaser Spine插件示例 移植到了 spine-phaser运行时 中.