アニメーションの適用

Spineには、アニメーションを適用するための2つのAPIが用意されています。

AnimationState API 

AnimationStateは後で再生するアニメーションをキューすることや、アニメーション間でのミキシング(クロスフェード)、複数アニメを重ねて適用する(レイヤリング)など時間の経過を伴うアニメーションの適用をサポートします。

AnimationStateはステートフル、つまり処理状態を把握し、アニメーションを適用するためにアニメーションの回数やその他のパラメーターを保管します。1つのAnimationStateで複数のスケルトンのポーズをとることができますが、複数のスケルトンに全く同じポーズを取らせることは稀です。通常1つのAnimationStateインスタンスは各Skeletonインスタンスに使用されます。

AnimationStateはTimeline API上で構築され、逆再生を除くほとんどのアニメーション再生のニーズに対応できます。逆再生が必要な場合、Timeline APIを直接使用するか、Spine内でボックス選択スケーリングを使用してアニメーションを複製し、逆にしてください。

AnimationStateのupdateメソッドは、最後に呼び出された時からの経過時間を取得してから内部のステートを更新します。applyメソッドはスケルトンを取得し、適切なアニメーションを適用します。

AnimationState state = ...
.
// Every frame:
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, 0);
entry.mixDuration = 0.6;

「Data」というサフィックス(接尾辞)に示される通り、AnimationStateDataはステートレス、つまり処理状態を把握しません。同じAnimationStateDataインスタンスを複数のAnimationStatesに使用できます。

トラック

トラックはアニメーションをレイヤで適用することを可能にします。各トラックはアニメ―ションと再生パラメータを保管します。トラックはゼロから昇順に番号が付けられます(トラックインデックスは、内部的には配列のインデックスです)。AnimationStateがスケルトンに適用されると、トラック番号が低いものから順にアニメーションが適用されます。

トラックには様々な用途があります。例えば、全てにキーを作成しないアニメーションを上位のトラックで再生し、キーが作成された下位のトラックのみをオーバーライドできます。トラック0は歩く、走る、泳ぐなどのアニメーションに設定し、トラック1は腕と銃の発射のみにキーを作成した射撃のアニメーションにするなどです。また上位のトラックにTrackEntry alphaを設定することで、その下のトラックとミックスすることが可能になります。例えば、トラック0を歩くアニメーションに設定し、トラック1を足を引きずるアニメーションに設定するなどです。プレーヤーがさらに怪我を負うにつれトラック1のアルファを増加するように設定すれば、足をもっと引きずるようにできます。

再生

アニメーションのトラック設定はsetAnimationを呼び出すことで行われます。これはトラック上の現在のアニメーションとキューされたアニメーションを指定したアニメーションに置き換えられます。ミックスデュレーションが前のアニメーションと現在のアニメーションの間で定義される場合、現在のアニメーションがミックスデュレーション中にミックスされるため、アニメーション間のトランジションがスムーズに行われます。

setAnimationは色々な方法で再生をカスタマイズするTrackEntryを返します。

デフォルトでは、別のアニメーションが再生されるか、トラックがクリアされるまで、アニメーションが適用され続けます。特定の時間が経過した後にアニメーションを停止するには、TrackEntry trackEnd時間を設定します。

キューイング

次に再生するアニメーションをキューするには、addAnimationを呼び出します。これにより、現在または最後にそのトラックにキューされたアニメーションの後にそのアニメーションを再生するように設定できます。トラックが空の場合、setAnimationを呼び出したのと同様になります。

addAnimationは再生のカスタマイズに使用できるTrackEntryを返します。

空のアニメーション

アニメーションが空のトラックに設定されると、直ちに再生が開始されます。同様に、トラックがクリアされるとアニメーションの適用が停止されます。アニメーションをミックスインまたはアウトさせるには、空のアニメーション、すなわちタイムラインのないアニメーションを指定する必要があります。空のアニメーションはミックスデュレーションを設定するプレースホルダーとして使用されます。空のアニメーションを設定したりキューに入れたりするのに便利なように、setEmptyAnimationメソッドとaddEmptyAnimationメソッドが用意されています。

セットアップポーズからアニメーションをミックスインするには、以下のように空のアニメーションを設定し、ミックスインするアニメーションを追加してmixDurationを設定します:

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時間に達すると、アニメーションでキーが作成された各プロパティにセットアップポーズを設定し、トラックがクリアされます。即座にセットアップポーズに戻す代わりに、setEmptyAnimationまたはaddEmptyAnimationを使用してスケルトンをセットアップポーズにミックスして戻す方が望ましい場合もあります。

TrackEntry

アニメーションを設定したり、キューに入れたりするメソッドはTrackEntryを返します。これは再生のカスタマイズに使用できます。TrackEntryで利用できる各種プロパティについては、TrackEntryのAPIリファレンスをご覧ください。

参照

TrackEntryへの参照を保持することも可能です。例えば、時間の経過とともにalphaまたはtimeScaleプロパティを調整することができます。しかしdisposeリスナーイベントの発生タイミングを過ぎても参照を保持してしまわないように注意する必要があります。

リスナー

アプリケーションはTrackEntryのライフサイクルイベントの通知を受けるためにコールバックを登録することができます。AnimationStateのaddListenerは全てのTrackEntryイベントにリスナーを登録します。また、特定のTrackEntryにリスナーを設定し、そのエントリーからのみイベントを受け取ることも可能です。

利用可能なイベントはAnimationStateListenerに記載されており、指定された順序で発生することが保証されています。イベントはAnimationStateのupdateまたはapplyメソッドにより内部処理中にキューされ、リスナーはその後、メソッドが返る直前に通知されます。これによりリスナーは、アニメーションを設定するまたはトラックをクリアするなどのAnimationStateを安全に操作できます。しかしすでにキューされた全てのイベントはclearListenerNotificationsが使用されない限り実行されます。

新しいアニメーションの設定など、リスナー内で行われたAnimationStateへの変更は、次にAnimationStateのapplyが呼び出されるまでスケルトンに適用されません。これはリスナー内では実行できますが、最初にupdateを呼び出す際には注意が必要です:

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

applyメソッドは内部の状態を変更せずに単一のAnimationStateを複数のスケルトンに適用することができます。updateを呼び出すことで、AnimationStateは、全ての適用が完了して後続のapply呼び出しが次のフレームで行われることを知ることができます。updateが呼び出されない場合、applyは同じリスナー通知を引き起こしてしまう可能性があり、無限ループやスタックオーバーフローの原因となります。

Timeline API

Timeline APIは、AnimationクラスとTimelineクラスにより構成されるアニメーション適用の最下層のAPIです。これらのクラスはステートレス、つまり処理状態を把握しないため、アニメーションを適用するための時間やその他のパラメータを外部に保存して操作する必要があります。このAPIはアニメーションの再生を最もコントロールしやすいですが、再生状態の管理に多くの作業を必要とします。そのため、ほとんどのユーザーはAnimationState APIを使用することを選ぶでしょう。

Animationは名前とTimelineのリストを持つ非常にシンプルなものです。各タイムラインが、スケルトンのプロパティを時間経過に伴いどのように変更するかを把握しています。アニメーションをスケルトンに適用するには、アニメーション内の各タイムラインに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;

Animationはloopパラメータを持ち、各タイムラインでapplyを呼び出すだけの便利なapplyメソッドを持っています。

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

次: ランタイムスケルトン 前: スケルトンデータのロード