应用动画

Spine提供了两个应用动画的API.

AnimationState API

AnimationState支持随时应用动画、队列播放、mix动画(crossfading, 淡入淡出)及在堆叠应用多个动画(layering, 动画分层).

AnimationState是有状态的, 用于存储动画时间及其他应用动画的参数. 虽然一个AnimationState可以调整多个Skeleton的姿势, 但是很少有情况需要让多个skeleton的姿势完全一样. 因此通常每个Skeleton实例只使用一个AnimationState实例.

AnimationState基于时间轴API, 足以处理大多数除逆向播放外的动画播放需求. 如需逆向播放动画应直接使用时间轴API, 或在Spine中框选缩放来复制并逆向播放动画.

AnimationState 的 update 方法将自上次调用后的已过时长(elapsed time)作为参数接收, 并据此更新内部状态. apply 方法则接收一个skeleton参数并对其应用相应动画.

AnimationState state = ...
...
// Every frame:
state.update(delta);
state.apply(skeleton);

Mix时长

创建AnimationState实例需要为其提供一个AnimationStateData. 当AnimationState更改当前动画时, 会自动使用AnimationStateData中定义的mix时长来mix动画(即交叉淡入淡出), 从而实现流畅的动画过渡.

AnimationStateData stateData = new AnimationStateData(skeletonData);
stateData.setDefaultMix(0.1);
stateData.setMix("walk", "jump", 0.2);
stateData.setMix("jump", "walk", 0.4);
stateData.setMix("jump", "run", 0.25);
stateData.setMix("walk", "shoot", 0);
AnimationState state = new AnimationState(stateData);

无需为每个动画手动指定mix时长, 只需设置一个默认的mix时长即可. 还可使用TrackEntry mixDuration属性来单独设置mix时长:

TrackEntry entry = state.setAnimation(0, "walk", true);
entry.mixDuration = 0.6;

如"Data"后缀所示, AnimationStateData是无状态的. 同一个AnimationStateData实例可在多个AnimationStates间共享.

轨道(Track)

轨道用于分层应用动画功能, 每个轨道都存储了一个动画和其播放参数. 轨道编号从零开始(在运行时内部轨道索引就是一个数组下标索引). 在将AnimationState应用于一个skeleton后, 会从最低的轨道号开始依序应用轨道中的动画.

轨道用途繁多, key入属性较少的动画可置于高层轨道, 只覆盖其key入属性的低层轨道属性. 例如,Track 0可以有行走、奔跑、游泳或其他动画, track 1则是key入了一个只为手臂和开枪设置属性的射击动画. 此外, 在高层轨道上设置TrackEntry alpha可使其与下层轨道mix. 例如, track 0有一个行走动画而track 1则是一个跛行动画. 当玩家受伤后增大track 1的alpha值可使跛行效果加重.

播放

调用setAnimation可在轨道上设置动画. 这将使用指定的动画取代该轨道中的当前动画和任何已队列的动画. 如果在前一动画和当前动画间定义了动画mix时长, 则将在mix时长中淡出当前动画, 以使动画过渡更流畅.

setAnimation 会返回一个TrackEntry, 用于自定义播放行为.

运行时会持续应用默认动画直至需要播放另一动画或轨道被清空. 要在某个时刻后停止动画, 应设置TrackEntry trackEnd时间.

队列

要队列动画以备播放, 可调用addAnimation, 这表示运行时会在该轨道的当前动画或最后队列动画之后播放该动画. 若该轨道本就是空轨道, 则等同于调用setAnimation.

addAnimation 会返回一个TrackEntry, 用于自定义播放行为.

空动画

若在空轨道上设置了动画, 则会立即开始播放动画. 若需从setup pose(初始姿势)立刻mix到特定动画, 则应使用空动画.

清空轨道后, 将停止应用轨道动画, 使得skeletons保持当前姿势. 若在这时需mix到setup pose, 也可使用空动画.

空动画没有时间轴. 它只是一个用于设置mix时长的占位符. 为方便起见, 运行时提供了 setEmptyAnimationaddEmptyAnimation 方法来设置或者队列空动画.

要从setup pose mix到某个动画, 只需设置一个空动画再配上mix时长:

state.setEmptyAnimation(track, 0);
TrackEntry entry = state.addAnimation(track, "run", true, 0);
entry.mixDuration = 1.5;

要某个动画mix到setup pose, 则应设置或队列某个指定了mix时长的空动画:

state.setAnimation(track, "run", true, 0);
state.addEmptyAnimation(track, 1.5, 0);

当动画播放到TrackEntry 的 trackEnd时间时, key入该动画的属性也会被重置到setup pose中的值且同时清空轨道. 可根据需要用 setEmptyAnimationaddEmptyAnimation 将skeletons 平滑地mix到setup pose, 而非让其立刻跳变.

TrackEntry

设置或队列动画的方法都会返回一个TrackEntry, 它用于进一步自定义播放行为. 具体请查看 TrackEntry API参考 获取更多细节.

引用

可以保存一个对TrackEntry的引用, 用于——例如在播放时随时调整alphatimeScale属性. 但是必须要注意在 dispose 事件发生后不要再保留这一引用.

侦听器

应用程序可注册一个回调来接收TrackEntry生命周期事件的通知. AnimationState上的addListener会为所有TrackEntry事件注册侦听器(或称监听器). 也可以在某一个TrackEntry上设置侦听器, 这样会只接收该entry的事件.

AnimationStateListener 中有全部可能的事件并会确保按指定的顺序触发事件. 在AnimationState 的 updateapply方法完成内部处理时会队列事件, 然后在方法返回前便会通知侦听器. 这样侦听器可以安全地管理AnimationState, 比如设置动画或清空轨道. 不过所有已队列的事件仍会被触发, 除非使用clearListenerNotifications.

侦听器中更改AnimationState(如设置一个新动画)不会立刻应用到skeleton, 而是需要等到下次调用AnimationState apply时. 在侦听器中这样做时必须注意要先调用update:

// Inside a listener:
state.setAnimation(0, "jump", false);
state.update(0); // Advance internal state.
state.apply(skeleton);

apply方法不会更改任何内部状态, 这样便能让一个AnimationState可应用于多个skeleton. 调用update会通知AnimationState完成了所有应用操作, 后续的apply调用是用于下一帧的. 如果没调用update 就执行 apply, 则可能触发同一个侦听器从而导致无限循环和堆栈溢出。

时间轴API

时间轴API是应用动画的最低层API, 包含AnimationTimeline类. 这些类都是无状态的, 必须在外部存储和管理应用动画的时间及其他参数. 该API提供了大部分对动画播放的控制能力, 但是需要更多手搓一些部件来管理播放状态. 所以多数用户更偏好使用AnimationState API.

一个动画类非常简单, 它包含一个名称和一个时间轴列表. 每条时间轴都储存着应在何时如何修改某一个Skeleton属性. 在动画中的每条时间轴上调用apply函数就能将动画应用于Skeleton.

time += delta;
alpha = 1; // For mixing between the current or setup pose (0) or the animation pose (1).
blend = MixBlend.first; // How the current or setup pose is mixed with the animation pose.
direction = MixDirection.in; // Whether mixing out to the setup pose or in to the animation pose.

for (Timeline timeline : animation.timelines)
   timeline.apply(skeleton, lastTime, time, events, alpha, blend, direction);

// The events list contains any events fired between lastTime and time.
// Process them here, then clear the list.
events.clear();

lastTime = time;

动画类有一个包含loop参数的便利apply方法, 它会在每条时间轴上调用 apply.

loop = true;
animation.apply(skeleton, lastTime, time, loop, events, alpha, blend, direction);

下一节: 运行时skeleton 上一节: 加载skeleton数据