应用动画

Spine提供了两个应用动画API。

AnimationState API

AnimationState支持随时应用动画、排队稍后播放、混合动画(交叉淡入淡出)及在各动画上应用多个动画(分层)。

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

AnimationState建立在时间轴API上,可处理多数播放需求,向后播放除外。如需反向播放,可直接使用时间轴API,或使用框选缩放来复制和反向播放动画。

AnimationState update方法用于获取自上次调用后已过时间和更新内部状态。apply方法用于获取一个骨架并应用相应动画。

AnimationState state = ...
...
// 每一帧:
state.update(delta);
state.apply(skeleton);

混合时间

创建一个AnimationState实例需要提供一个AnimationStateData。当AnimationState更改当前动画时,会使用AnimationStateData中定义的混合时间自动混合动画(交叉淡入淡出),从而实现流畅的动画过渡。

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);

无需为每个动画手动指定混合时间,设置一个默认混合时间即可。还可使用TrackEntry mixDuration属性逐例设置混合时间:

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

如"Data"后缀所示,AnimationStateData是无状态的。同一个AnimationStateData实例可与多个AnimationStates共用。

通道(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值会增加,跛行会加重。

播放

设置track动画通过调用setAnimation完成。将使用指定的动画取代该track中的当前动画和任何已排队的动画。如果在前一动画和当前动画之间定义了混合动画时间,则当前动画将在混合动画时间上混出,让动画过渡更流畅。

setAnimation返回一个TrackEntry,可以多种方式自定义播放。

默认动画将继续应用直至另一动画播放或track清空。要在一个具体时间后停止动画,可设置TrackEntry trackEnd时间。

排队

要排队动画在将来播放,可调用addAnimation,安排该动画在此track当前动画或最后排队的动画后播放。如果此track空了,则等于调用setAnimation

addAnimation返回一个TrackEntry,可用于自定义播放。

空动画

当在空track上设置一个动画,则立即开始播放。类似地,如果清空了一个track,动画将停止应用。要混入或混出一个动画,可指定一个空动画,即没有时间轴的动画。空动画作为一个占位符,可设置混合时间。为了方便,提供有setEmptyAnimationaddEmptyAnimation方法用于设置或排队空动画。

要从装配姿势混入一个动画,可设置一个空动画,然后设置混合时间:

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

要混出一个动画到装配姿势,可设置或排队一个指定了混合时间的空动画:

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

当动画到达TrackEntry trackEnd时间时,该动画设置的关键帧属性将设置装配姿势,并清空track。可根据需要使用setEmptyAnimationaddEmptyAnimation将骨架混回到装配姿势,而非让其即刻发生。

TrackEntry

设置或排队动画的方法返回一个TrackEntry,可用于进一步自定义播放。参考TrackEntry API参考获取许多可用设置。

引用

可保留一个TrackEntry引用,例如可随时调整alphatimeScale属性。但是必须注意不要在dispose侦听器事件发生后保留该引用。

侦听器

应用程序可注册一个回调以通知TrackEntry生命周期事件。AnimationState上的addListener可为所有TrackEntry事件注册侦听器。也可以在一个具体TrackEntry上设置侦听器以只接收该entry的事件。

AnimationStateListener上列出了可能的事件并保证按指定的顺序发生。事件在AnimationState updateapply方法完成内部处理时排队,之后在方法刚返回前通知侦听器。这样侦听器可以安全地操纵AnimationState,比如设置动画或清空tracks。但是仍会触发所有已排队的事件,除非使用clearListenerNotifications

侦听器中的AnimationState更改(如设置一个新动画)不会应用到骨架直至下次调用AnimationState apply时。这可在侦听器中完成,但是必须注意先调用update:

// 侦听器内:
state.setAnimation(0, "jump", false);
state.update(0); // 更新内部状态。
state.apply(skeleton);

apply方法不会更改任何内部状态,使一个AnimationState可应用到多个骨架。调用update让AnimationState知道所有应用完成了,后续的apply调用是下一帧的。如果未调用updateapply可能触发同一个侦听器,导致无限循环和堆栈溢出。

时间轴API

时间轴API是应用动画最低层的API,包含AnimationTimeline类。这些类都是无状态的,必须在外部存储和操纵应用动画的时间及其他参数。此API对动画播放提供大部分控制,但是自己需要更多工作来管理播放状态。所以多数用户偏好使用AnimationState API

一个动画非常简单,有一个名称和一系列时间轴。每个时间轴知道在何时如何修改一个具体骨架属性。将动画应用到骨架是通过为动画中的每个时间轴调用apply完成。

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;

动画有一个很方便的apply方法,只需为每个时间轴调用 apply即可。

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

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