一.Spine概述
Spine 是一款针对游戏开发的 2D 骨骼动画编辑工具。 Spine 旨在提供更高效和简洁 的工作流程,以创建游戏所需的动画。
Spine原理:将一个模型,根据动画的需求分成一些骨骼,一个骨骼对应一张贴图,控制骨骼位置、缩放、旋转等形成动画;
Spine工作流程如下所示:
参考:认识Spine
二.导入spine-unity 运行时
下载spine-unity unitypackage
下载完成后将其拖入到Project视图中, Import,完成导入,新增Spine和Spine Examples目录
Important:spine运行时版本需要和导入的spine资源一致,若导入不一致的spine资源时,会弹出对话框并报错
参考:spine-unity 下载
三.导入spine资源
3.1 spine导出的文件转unity资产
通过spine编辑器导出的unity资源如图所示
skeleton-name.json
或skeleton-name.skel.bytes
, 其中包含了skeleton和动画数据.skeleton-name.atlas.txt
, 保存的是texture atlas的信息.- 单个或数个
.png
文件, texture atlas中的一页就对应着这样一个文件, 而texture atlas则包含了skeleton所需的全部图片.
将导出的文件(或包含它们的文件夹)拖到Unity的项目面板中的某个目录下
spine-unity运行时在检测到文件添加后会自动生成Unity所需的资产文件.
自动生成具体会产生以下资产:
- 一个代表texture atlas文件 (
.atlas.txt
)的 _Atlas 资产文件. 它包含对material(材质)和.atlas.txt
文件的引用. - 一个代表各 texture atlas页 (
.png
) 的 _Material 资产文件. 它包含对着色器和.png
texture的引用. - 一个存储了skeleton数据 (
.json
,.skel.bytes
) 的 _SkeletonData 资产文件. 它包含了对.json
或.skel.bytes
文件的引用以及对自动生成的 _Atlas 资产的引用. 它还可以自定义的skeleton导入和设置skeleton动画.
3.2 更新spine资源
在实际开发过程中, 你可能经常需要更新Spine skeleton数据和texture atlas文件. 直接覆盖这些文件其实就能更新这些文件 (.json
, .skel.bytes
, .atlas.txt
, .png
). 只需从Spine编辑器中重新导出资产, 并将新导出的文件复制到项目中 Assets
文件夹的子文件夹中覆盖现有文件即可.
Unity会检测这些文件的变化, 并自动重新导入修改后的资产. 自动重新导入会保持对Spine资产引用的完整性同时使用最新数据.
3.3 动画预览
选中*_SkeletonData,在Inspector中可以预览动画,获取动画名称
所有时间轴事件以紫色标记显示. 在播放预览时将鼠标悬停在标记上, 就能显示事件名称.
注意:play模式下无法预览
点击 Create Animation Reference Assets
按钮即可为skeleton的所有动画生成引用资产. AnimationReferenceAsset
和一个引用了 Spine.Animation
的Unity资产行为完全一致, 也可以将它用在Unity检查器的组件属性中.
参考:资产
四.Editor中创建动画对象
- 将*_SkeletonData文件拖入Hierarchy视图中
- 选择需要的组件
- 设置Animation Name
五.spine运行时核心概念(重点)
5.1 混合(重中之重)
Spine支持动画之间的混合, 混合主要有以下两方面的应用场合
- A.多个动画同时播放,形成新的,较复杂的动画
- B.动画之间的平滑过渡,而不是从一个姿势突然变成另一个姿势
其实这种需求是动画上的通用需求,Unity的Animator动画通过Transition(过渡),Layer(分层),blend tree(混合树)支持了混合在3D动画上的应用
5.2 通道(Track)
Track可分层应用动画,每个track存储了一个动画和播放参数。Track编号从零累加(track索引在内部是一个数组索引)。在将AnimationState应用到一个骨架后,track动画会从最低的track号开始依序应用。
Track有许多用途,例如,没有任何关键帧的动画可在高层track中运行,只覆盖有关键帧的低层track。例如,Track 0可以有行走、奔跑、游泳或其他动画,track 1可以有一个只为手臂和开枪设置了关键帧的射击动画。此外,为高层track设置TrackEntry alpha可使其与下面的轨道混合。例如,track 0可以有一个行走动画,track 1可以有一个跛行动画。当玩家受伤时,track 1的alpha值会增加,跛行会加重。
5.3 播放
在一个通道上播放动画通过调用setAnimation完成。将使用指定的动画取代该track中的当前动画和任何已排队的动画。如果在前一动画和当前动画之间定义了混合动画时间,则当前动画将在混合动画时间上混出,让动画过渡更流畅。
5.4 排队
要排队动画在将来播放,可调用addAnimation,安排该动画在此track当前动画或最后排队的动画后播放。如果此track空了,则等于调用setAnimation。
5.5 空动画
空动画是spine中比较常用和重要的概念,主要有以下两种应用情况
A.立刻停止轨道上的所有动画,调用setEmptyAnimation
B.当多个动画混合播放(比如动画a0在track0播放,动画a1在track1播放),当a1停止后需要过渡到只有a0播放的状态,调用addEmptyAnimation
Case1:a1播放完后什么都不做(track1未进入empty状态),则a0和a1会混合失败,动画整体或局部静止。
Case2:调用setEmptyAnimation让a1静止,则可能a1播放中途停止,动画表现比较突兀,不是想要的效果。
参考:应用动画
六.主要API
6.1 SetAnimation
setAnimation(int trackIndex, string animationName, bool loop)
setAnimation(int trackIndex, Animation animation, bool loop)
在轨道trackIndex立刻播放animationName这个动画,关于轨道的概念参考底部运行时术语
loop
若为true则循环播放动画. 若为false则不会循环播放,
SkeletonAnimation skeletonAnimation = GetComponent<SkeletonAnimation>();
skeletonAnimation.AnimationState.SetAnimation(SpineBlinkPlayer.BlinkTrack, "blink", false);
6.2 AddAnimation
addAnimation(int TrackIndex, Animation animation, bool loop, float delay)
添加一个待播放动画, 在某轨道的当前或最后一个排队动画之后播放. 若该轨道为空, 则相当于调用setAnimation
SkeletonAnimation skeletonAnimation = GetComponent<SkeletonAnimation>();
skeletonAnimation.AnimationState.SetAnimation(0, "anim1", false);
skeletonAnimation.AnimationState.AddAnimation(0, "anim2", true, 0);
6.3 setEmptyAnimation
setEmptyAnimation ( int trackIndex, float mixDuration)
在轨道上设置一个空动画并移除队列中的全部动画, 同时设置轨道条目的mixDuration. 空动画没有时间轴, 它被当作淡入淡出的占位符.
6.4 addEmptyAnimation
addEmptyAnimation ( int trackIndex, float mixDuration, float delay)
添加一个空动画, 在某轨道的当前或最后一个排队动画之后播放, 同时设置该轨道条目的 mixDuration. 若该轨道为空, 则相当于调用 setEmptyAnimation
API参考文档
七.回调事件
7.1 不包括混合(mixing)/淡入淡出(crossfading)的事件
触发流程图
- Start 当动画开始播放时触发,
- 当调用
SetAnimation
时立刻触发. - 它也可以在一个队列中的动画开始播放时触发.
- 当调用
- End 当动画从轨道中被移除(或中断)时触发,
- 当当前动画快要播放完成时你调用
SetAnimation
中断了它, 该事件将被触发. - 当使用
ClearTrack
或者ClearTracks
清除了轨道时, 该事件也会被触发. - 在混合(mix)/淡入淡出(crossfade)期间,在mix完成后End事件将被触发.
- 当注册到
AnimationState
时, 永远不要在End
事件处理中调用SetAnimation
, 它会引发无限递归. 请看下面的警告. 可以注册一个TrackEntry.End
来替代. - 注意, 默认情况下, 非循环动画的TrackEntries不再在动画的持续时间内停止. 相反, 会继续无限期地保持最后一帧, 直到你移除它或用其他动画取代它. 如果你想让你的
TrackEntry
达到其动画持续时长(duration)时清空轨道, 请将TrackEntry.TrackEnd设置为动画持续时长.
- 当当前动画快要播放完成时你调用
- Dispose 当AnimationState释放一个(在其生命周期结束时的)TrackEntry时, 会对TrackEntry触发.
- 像spine-libgdx和spine-csharp这样的运行时会把TrackEntry对象缓存,以减小非必要的GC压力。这在Unity中尤为重要,因为Unity的GC实现有较为老旧低效.
- 当TrackEntries被释放后,一定要记得移除对它们的所有引用,因为它们可能稍后就会被写入多余数据或触发意外事件.
- Dispose事件会在End事件后立即触发.
- Interrupt 当设置了新的动画且当前有一个动画还在播放时触发.
- 当一个动画开始mixing/crossfading到另一个动画时触发.
- Complete 当动画完成时触发,
- 当一个非循环的动画播放完毕时触发,无论是否存在下一个在排队的动画.
- 在循环动画每次循环结束后,也会触发.
- Event 任何用户自定义事件被监听到时触发.
- 这些事件点在Spine编辑器的动画中设置的。它们显示围为紫色的关键帧。在树状视图中也可以看到一个紫色的icon.
- 为了区分不同的事件,你需要检查
Spine.Event e
的Name
参数。(或者Data
引用). - 当你想要按照动画节点去播放声音时它会非常有用,比如播放脚步声。它也可以根据Spine动画去同步或者通知非Spine系统,比如Unity的粒子系统或者产生单独的特效,甚至是诸如对齐发射子弹的时刻这样的游戏逻辑(如果你真的想这么做的话).
- 每个TrackEntry都有一个
EventThreshold
属性. 它定义了在淡入淡出的哪些时间点上不触发用户事件.
在一个动画播放完成后,另一个队列中的动画即将开始播放的时候,事件触发的顺序为: Complete
, End
, Start
7.2 混合动画的事件
当你有设置了mix time(或者在你Skeleton Data Asset中设置了Default Mix
),下一段动画开始播放且透明度逐渐增加,同时前一段动画依然在skeleton上生效, 这需要一小段时间.
我们把这段时间称为"淡入淡出"或"混合(mix)".
一个TrackEntry的EventThreshold控制在混合期间如何处理用户事件.
- 默认值为
0
,当下一段动画开始播放时,立即停止触发用户事件. - 若置为
0.5
,就会在淡入淡出/混合过程到一半再停止触发用户事件. - 若置为
1
,将继续触发事件,直到淡入淡出/混合过程的最后一帧.
给动画设置合适的EventThreshold
值是很重要的,因为你可能有交叠(overlapping)的相同动画,而它们不应该触发同一个事件,或者你希望即使动画已被中断但仍然触发其事件.
// Sample written for for Spine 3.7
using UnityEngine;
using Spine;
using Spine.Unity;
// Add this to the same GameObject as your SkeletonAnimation
public class MySpineEventHandler : MonoBehaviour {
// The [SpineEvent] attribute makes the inspector for this MonoBehaviour
// draw the field as a dropdown list of existing event names in your SkeletonData.
[SpineEvent] public string footstepEventName = "footstep";
void Start () {
var skeletonAnimation = GetComponent<SkeletonAnimation>();
if (skeletonAnimation == null) return;
// This is how you subscribe via a declared method.
// The method needs the correct signature.
skeletonAnimation.AnimationState.Event += HandleEvent;
skeletonAnimation.AnimationState.Start += delegate (TrackEntry trackEntry) {
// You can also use an anonymous delegate.
Debug.Log(string.Format("track {0} started a new animation.", trackEntry.TrackIndex));
};
skeletonAnimation.AnimationState.End += delegate {
// ... or choose to ignore its parameters.
Debug.Log("An animation ended!");
};
}
void HandleEvent (TrackEntry trackEntry, Spine.Event e) {
// Play some sound if the event named "footstep" fired.
if (e.Data.Name == footstepEventName) {
Debug.Log("Play a footstep sound!");
}
}
}
参考:Spine事件 & AnimationState回调函数
八.Spine运行时术语
Atlas 纹理集(atlas), 也被称为纹理图集(texture atlas), 是一个存储了一个纹理的命名区域. Spine可以打包纹理来新建一个图集, 也可以使用像Texture Packer Pro(大多数运行时使用的是 "libgdx "图集格式)这样的外部工具来创建图集.
Animation 动画(Animation)是一个存储了多条时间轴的列表. 每条时间轴都存储着许多关键帧, 每个关键帧都包含一个时间点以及一个/多个值. 当应用动画时, 时间轴使用这些关键帧来操作骨架(skeleton)、触发事件等. 动画不会存储任何状态.
AnimationState 动画状态(AnimationState)是一个方便的类, 它包含可用于骨架(skeleton)的一个或多个动画状态. 它有一个"轨道"的概念, 其索引号从零开始. 在每一帧中依次应用每条轨道中的动画, 且可以在动画轨道之上应用动画. 每个轨道都可以队列动画以便稍后播放. 当当前动画发生变化时, 动画状态还可以处理动画间的Mix(比如淡入淡出).
Attachment 附件(Attachment)放置在槽位中, 由此便附加于骨骼上. 它可以是一块texture区域也可以是边界框.
AttachmentLoader SkeletonJson使用附件加载器(AttachmentLoader)创建附件. 这个钩子函数用于注入自定义的附件实现, 例如实现惰性加载. 附件加载器最常见的用途是用于定制区域附件的图片源.
Bone 一个骨骼包含局部变换(SRT), 其子骨骼也会继承该变换. 一个骨骼也有世界变换, 它是所有父骨骼变换与局部变换的组合. 世界变换使用和根骨骼相同定义的坐标系.
Bounding box attachment 一个用于执行撞击检测、物理模拟等功能的多边形附件.
Draw order 绘制顺序(Draw order)是骨架(skeleton)上槽位的一个列表. 该列表的顺序表示从后到前绘制每个槽位中附件的顺序.
Mixing 混合(Mixing), 也被称为淡入淡出, 是通过在当前pose和动画pose间进行线性混合(blend)来使用动画的一种方式.
Region attachment 一个附件(attachment)包含一个纹理(texture)区域和一个偏移SRT, 这个偏移量用于对齐相对于附件的骨骼区域.
Slot 槽位(Slot)是骨骼上的一个占位符. 一个槽位既可包含一个附件也可不包含附件. 它也有自己的颜色且记录其附件切换后的保持时长.
Skeleton 骨架(Skeleton)保存着一个骨架的状态. 这包括当前pose、骨骼、槽位、绘制顺序等.
SkeletonBounds 骨架边界(SkeletonBounds)是一个方便的类, 它使用当前附加的边界框附件为骨架进行碰撞检测.
SkeletonData 骨架数据(SkeletonData)包含骨架信息(绑定pose的骨骼、槽位、绘制顺序、附件、皮肤等)和动画, 但不保存任何状态. 多个骨架可以共享同一个骨架数据
SkeletonJson 用于从JSON中加载SkeletonData.
SkeletonRenderer 骨架渲染器(SkeletonRenderer)在骨架的绘制顺序中重复遍历各插槽位, 并负责渲染各种附件.
Skin 皮肤(Skin)是一个映射(map), 其键(key)是槽位和名称, 值为附件. 皮肤是一个间接层, 它可以按槽位和名称来找到某个附件. 例如, 一个皮肤可能的键为[slot:head,name:head], 值为[attachment:redHead]. 另一个皮肤的同样的键值则为[attachment:greenHead]. 在动画中使用名称可使具有不同附件的骨架通过改变附件的方法复用动画.
SRT 缩放(Scale)、旋转(Rotation)和平移(Translation). 也被称为"变换(transform)".
Transform 缩放(Scale)、旋转(Rotation)和平移(Translation).
参考:
Spine概述
spine-unity 运行时文档
Spine运行时指南
Spine运行时文档