spine-flutterランタイム ドキュメント

ライセンスについて

Spineランタイムをアプリケーションに組み込む前に、必ずSpine Runtimes Licenseを確認してください。

はじめに

spine-flutterランタイムは、spine-cppの上にFlutter FFIプラグインとして実装されています。Flutterがサポートするすべてのプラットフォーム(デスクトップ、Android、iOS、ウェブ)をサポートし、ティントブラックを除くすべてのSpineの機能をサポートしています。

インストール方法

spine-flutterはFlutter 3.10.5以降でサポートされています。Flutterプロジェクトでspine-flutterを使うには、プロジェクトの pubspec.yaml ファイルに以下の依存関係を追加してください:

yaml
dependencies:
...
spine_flutter: ^4.1.1

spine-flutterの major.minor バージョンが、エクスポートを行ったSpineエディターの major.minor と一致していることを確認してください。詳しくは「バージョンの同期」を参照してください。

次に、main()関数の最初に以下の2行を追加して、spine-flutterランタイムを初期化します:

dart
void main() async {
   WidgetsFlutterBinding.ensureInitialized();
   await initSpineFlutter(enableMemoryDebugging: false);
   ...
}

注意: main() メソッドは必ず async である必要があります。

WebデプロイメントにはCanvasKitが必要で、この記事を書いている時点では、Webデプロイメントに約2MBの依存関係が追加されます。Canvaskitを使ってWeb用にアプリをコンパイルするには、次のようにします:

flutter build web --web-renderer canvaskit

Webレンダラーについての詳細はFlutterのドキュメントを参照してください。

サンプル

spine-flutterランタイムには、利用できる機能セットを実演しているサンプルが多数含まれています。

pub.devから spine_flutter パッケージを直接ダウンロードした場合は、以下の手順で example/ フォルダにあるサンプルプロジェクトを実行できます:

bash
cd path/to/downloaded/spine_flutter
cd example
flutter run

または、以下の手順でサンプルプロジェクトを実行することもできます:

  1. Flutter SDKをインストールし、 flutter doctorを実行します。これは他にインストールすべき依存関係を指示してくれます。
  2. spine-runtimesリポジトリをクローンします: git clone https://github.com/esotericsoftware/spine-runtimes
  3. spine-flutter/ フォルダ内の setup.sh スクリプトを実行します。 WindowsではGit for Windowsに含まれるGit Bashを使ってこのBashスクリプト setup.sh を実行できます。

その後、IntelliJ IDEA/Android StudioVisual Studio Codeなど、Flutterをサポートするお好みのIDEやエディターで spine-flutter を開き、サンプルを確認したり実行したりできます。

そのほか、コマンドラインからサンプルを実行することもできます。

サンプルプロジェクトには、以下の例が含まれています:

  • example/lib/simple_animation.dart: これは SpineWidgetSpineWidgetController を使用して、エクスポートしたSpineスケルトンをロードして、ウィジェットに表示し、特定のアニメーションを再生するまでの基本的な使い方を実演しています。
  • example/lib/pause_play_animation.dart: アニメーションの一時停止と再開の方法を実演しています。
  • example/lib/animation_state_events: スロットの色を設定する方法、複数のアニメーションをキューに入れる方法、AnimationStateのイベントをリッスンする方法を実演しています。
  • example/lib/debug_rendering.dart: SpineWidgetControlleronAfterPaint コールバックを使用して、レンダリングされたスケルトンの上にカスタム描画を実行する方法を実演しています。
  • example/lib/dress_up.dart: Spineのスキン機能と、キャラクター作成UIで使用するスケルトンをサムネイルにレンダリングする方法を実演しています。
  • example/lib/ik_following.dart: マウスまたはタッチ入力でスケルトンのボーンをドラッグする方法を実演しています。
  • example/lib/flame_example.dart: Flameゲームエンジンでspine-flutterを使用するための簡単なFlameコンポーネントの書き方を実演しています。

spine-flutterランタイムのアップデート

プロジェクトのspine-flutterランタイムをアップデートする前に、Spineエディターとランタイムのバージョン管理に関するガイドをよく確認してください。

spine-flutterランタイムをアップデートするには、pubspec.yamlspine_flutter パッケージのバージョン文字列を変更してください。

注意: spine_flutter パッケージの major.minor バージョンを変更した場合、使用しているSpineスケルトンを同じ major.minorバ ージョンのSpineエディターを使って再エクスポートする必要があることに注意してください!

spine-flutterを使用する

spine-flutter ランタイムは、Spine で作成されたアニメーションのロード、再生、操作をサポートする汎用 spine-cpp のイディオム Dart FFI ラッパー です。spine-flutter ランタイムは spine-cpp APIのほとんどすべてをDartのイディオムとして公開し、FlutterおよびFlame固有のクラスを提供して、Spineスケルトンを簡単に表示したり操作できるようにします。

spine-flutterランタイムは、ティントブラックを除き、Spineのすべての機能をサポートしています。

アセットのマネージメント

spine-flutter用にエクスポートする

以下の実行方法については、Spineユーザーガイド内で紹介されています :

  1. スケルトン&アニメーションデータのエクスポート
  2. スケルトンの画像を含むテクスチャアトラスのエクスポート

スケルトンのデータとテクスチャアトラスをエクスポートすると、以下のファイルが得られます:

  1. skeleton-name.json または skeleton-name.skel: これはスケルトンとアニメーションのデータを含んでいます。
  2. skeleton-name.atlas: これはテクスチャアトラスの情報を含んでいます。
  3. 1つまたは複数の .png ファイル: これはテクスチャアトラスの各ページで、スケルトンが使用するイメージを含んでいます。

補足: JSONエクスポートよりもバイナリ形式でのスケルトンエクスポートの方がサイズが小さく、読み込みが速いので、基本的にはそちらを選択したほうが良いでしょう。

これらのファイルを、AtlasSkeletonDataSkeletonDrawableSpineWidgetなどのspine-flutterクラスを使って読み込むことができます。

注意: spine-flutterランタイムは現在、Flutterの技術的制限により、乗算済みアルファ(Premultiplied alpha)を使用してエクスポートされたアトラスをサポートしていません。Flutterのレンダリングエンジンは、よくある非乗算済みアルファのアーティファクトを確実に回避します。

Spineアセットの更新

開発中にスケルトンデータやテクスチャアトラスファイルを更新したい時は、単純にSpineエディターから再エクスポートを行なって、Flutterプロジェクト内の既存のファイル(.json.skel.atlas.png)を置き換えるだけで簡単にこれらのソースファイルを更新できます。

その際、spine-flutterの major.minor バージョンとエクスポートを行ったSpineエディターの major.minor が一致していることを確認してください。詳しくは「バージョンの同期」を参照してください。

コアクラス

spine-flutterのAPIは汎用spine-cppランタイムの上に構築されており、プラットフォームに依存しないコアクラスとSpineスケルトンのロード、クエリ、変更、アニメーションを行うアルゴリズムを提供します。コアクラスはDart FFIでラップされ、Dartのイディオムクラスとして公開されます。

ここではspine-flutterを使用する際によく見ることになる最も重要なコアクラスについてのみ簡単に説明しています。Spineランタイムのアーキテクチャ、コアクラス、APIの使用法の詳細については、Spineランタイムガイドを参照してください。

Atlas クラスは、.atlas ファイルとそれに対応する .png 画像ファイルからロードしたデータを保管します。

SkeletonData クラスは、.json または .skel ファイルからロードされたデータを保管します。このスケルトンデータには、ボーン階層、スロット、アタッチメント、コンストレイント、スキン、アニメーションに関する情報が含まれます。SkeletonData インスタンスは、通常、それが表すスケルトンで使用されるイメージをソースとする Atlas(アトラス) も一緒に提供することによってロードされます。これは、Skeleton インスタンスを作成するための設計図として機能します。複数のスケルトンを同じアトラスとスケルトンデータからインスタンス化し、ロードされたデータを共有することで、ロード時間と実行時のメモリ消費を最小限に抑えることができます。

Skeleton クラスは、SkeletonData インスタンスから作成されたスケルトンのインスタンスを格納します。スケルトンは現在のポーズを保管します。つまり、ボーンの位置、スロット、アタッチメント、アクティブなスキンの現在の構成を保管します。現在のポーズは、手動でボーンのトランスフォームを変更するか、より一般的には、AnimationState を介してアニメーションを適用することで計算されます。

AnimationState クラスは、 スケルトンに適用する(単数または複数の)アニメーションを追跡し、最後のレンダリングフレームと現在のレンダリングフレームの間の経過時間に基づいてそれらのアニメーションを進め、ミックスを行い、スケルトンインスタンスにアニメーションを適用して現在のポーズを設定します。AnimationStateAnimationStateData インスタンスに問い合わせ(処理要求)をして、アニメーション間のミキシング時間を取得します。特定のアニメーション間に使用するミキシング時間が無ければデフォルトミックスタイムを取得します。

spine-flutterランタイムはこれらのコアクラスの上に構築されています。

SpineWidget

/img/spine-runtimes-guide/spine-flutter/simple-animation.png

SpineWidget は、Spineスケルトンのロードと表示を担当する StatefulWidget です。最低限、ウィジェットはスケルトンとアトラスファイルをロードする場所を知る必要があり、アニメーションの設定やスケルトンのスキンの変更など、ウィジェットの状態を変更する SpineWidgetController インスタンスを受け取る必要があります。

最も単純なケースでは、SpineWidget は次のように他のウィジェットの build() メソッド内でインスタンス化できます:

dart
@override
Widget build(BuildContext context) {
   final controller = SpineWidgetController(onInitialized: (controller) {
    // トラック0にwalkアニメーションをセットし、それをループさせます
    controller.animationState.setAnimationByName(0, "walk", true);
   });

   return Scaffold(
    appBar: AppBar(title: const Text('Simple Animation')),
    body: SpineWidget.fromAsset("assets/spineboy.atlas", "assets/spineboy-pro.skel", controller)
   );
}

インスタンス化すると、SpineWidget は指定されたファイルを非同期にロードし、それらのファイルから基礎となるコアクラスのインスタンス(AtlasSkeletonDataSkeletonAnimationStateDataAnimationState のインスタンス)を構築します。

ロードが完了すると SpineWidgetController が呼び出され、(単数または複数の)アニメーションの設定、ボーン階層の操作、スケルトンのスキンの変更など、ウィジェットの状態を変更できるようになります。詳しくは後述の SpineWidgetController セクションを参照してください。

SpineWidget クラスは、異なるソースからスケルトンとアトラスファイルをロードするための複数の静的ファクトリーメソッドを提供します:

  • SpineWidget.fromAsset() は、ルートバンドルまたは提供されたバンドルからファイルをロードします。
  • SpineWidget.fromFile() は、ファイルシステムからファイルをロードします。
  • SpineWidget.fromHttp() は、URLからファイルをロードします。
  • SpineWidget.fromDrawable() は、SkeletonDrawable からウィジェットを構築します。これは、スケルトンデータをプリロード、キャッシュ、および/または SpineWidget インスタンス間で共有したい場合に便利です。詳しくは後述の「スケルトンデータのプリロードと共有」のセクションを参照してください。

すべてのファクトリーメソッドにはオプションの引数があり、Spineスケルトンをウィジェット内にどのようにフィットさせ、整列させるか、またウィジェットのサイズをどのように設定するかを定義できます。

  • fit: ウィジェット内のスケルトンをフィットするために使用するBoxFit
  • alignment: ウィジェット内のスケルトンの位置合わせに使用するAlignment
  • BoundsProvider: fitとalignmentを計算するときにスケルトンに使用するバウンディングボックスのピクセルサイズを計算するために使用します。デフォルトでは、スケルトンのセットアップポーズのバウンディングボックスが使用されます。詳細は、SetupPoseBoundsRawBoundsSkinAndAnimationBounds のクラスドキュメントを参照してください。
  • sizedByBounds: BoundsProvider によって計算された境界によってウィジェットのサイズを決めるか、親ウィジェットによってサイズを決めるかを定義します。

スケルトンデータのプリロードと共有

複数の SpineWidget インスタンス間でアトラスとスケルトンデータを共有したい場合は、アセットを手動で事前にロードすることができます:

final atlas = await Atlas.fromAsset("assets/test.atlas");
final skeletonData = await SkeletonData.fromAsset("assets/test.json", atlas);

同じデータから1つ以上の SpineWidget インスタンスをインスタンス化できるので、ロード時間とメモリの節約になります:

SpineWidget.fromDrawable(SkeletonDrawable(skeletonData, atlas));

ただしそれらを参照する SpineWidget (または SkeletonDrawable) が存在しなくなったら、アトラスとスケルトンデータを破棄する必要があるので注意してください。

skeletonData.dispose();
atlas.dispose();

SpineWidgetController

SpineWidgetControllerは、SpineWidget のスケルトンのアニメーションとレンダリングを制御します。このコントローラーはコンストラクタの引数としてオプションのコールバックセットを提供し、SpineWidget のライフタイム中に特定のタイミングで呼び出されます。

このコントローラーは、AtlasSkeletonDataSkeletonAnimationState などの Spine Runtimes API オブジェクトを返すゲッターを通して、スケルトンの状態を公開します。詳しくはは、Spineランタイムガイドおよびクラスのドキュメントを参照してください。

SpineWidget の初期化時に、コントローラーの onInitialized() コールバックメソッドが一度だけ呼び出されます。このメソッドは、再生する(単数または複数の)アニメーションの初期設定や、スケルトンのスキン設定などに使用できます。

初期化が完了すると、SpineWidget は画面のリフレッシュレートで連続的にレンダリングされます。毎フレーム AnimationState が現在キューに入っているアニメーションに基づいて更新され、Skeleton に適用されます。

次に、オプションの onBeforeUpdateWorldTransforms() コールバックが呼び出され、Skeleton.updateWorldTransform() を使用して現在のポーズが計算される前にスケルトンを変更できます。

現在のポーズが計算された後、オプションの onAfterUpdateWorldTransforms() コールバックが呼び出され、スケルトンがレンダリングされる前に、現在のポーズをさらに修正することができます。手動でボーンの位置を変更したい場合にはこれを利用すると良いでしょう。

SpineWidget によってスケルトンがレンダリングされる前に、オプションの onBeforePaint() コールバックが呼び出されます。これを利用して Canvas 上でスケルトンの後ろにあるべき背景や他のオブジェクトをレンダリングすることができます。

SpineWidget が現在のスケルトンのポーズを Canvas にレンダリングした後、オプションの onAfterPaint() コールバックが呼び出されます。これを利用してスケルトンの上に追加のオブジェクトをレンダリングすることができます。

デフォルトでは、ウィジェットはスケルトンを毎フレーム更新してレンダリングします。スケルトンの更新とレンダリングを一時停止したい場合は、SpineWidgetController.pause() メソッドを使用します。スケルトンの更新とレンダリングを再開するには、SpineWidgetController.resume() メソッドを使用します。SpineWidgetController.isPlaying()ゲッターは、現在の再生状態を報告します。詳しくは、サンプル example/lib/animation_state_events.dart を参照してください。

SkeletonDrawable

SkeletonDrawable は、Skeletonのロード、保存、更新、およびレンダリングと、それに関連する AnimationState を、単一の使いやすいクラスにまとめたものです。このクラスは、カスタムウィジェットの実装の基礎として使用できます。SpineWidget は、SkeletonDrawable のインスタンスを介して、表示するスケルトンの状態をカプセル化します。

ファイルアセットから SkeletonDrawable を構築するには、fromAsset()fromFile()fromHttp() メソッドのいずれかを使用します。複数の SkeletonDrawable インスタンス間で Atlas と SkeletonData 共有するには、コンストラクタで drawable をインスタンス化し、それぞれに同じアトラスとスケルトン データを渡します。

SkeletonDrawable は、スケルトンのクエリ、変更、アニメーションを行うために、SkeletonAnimationStateを公開します。また、そのスケルトンとAnimationStateを構築するのに使った AtlasSkeletonData も公開します。

スケルトンをアニメーションさせるには、AnimationState.setAnimation()AnimationState.addAnimation() などの AnimationState のAPIを使用して、単数または複数のトラックにアニメーションをキューに入れます。

アニメーションの状態を更新し、それをスケルトンに適用し、現在のスケルトンのポーズを更新するには、SkeletonDrawable.update() メソッドを呼び出し、アニメーションを進めるための秒単位のデルタ時間を与えます。

スケルトンの現在のポーズをレンダリングするには、レンダリングメソッド SkeletonDrawable.render()SkeletonDrawable.renderToCanvas()SkeletonDrawable.renderToPictureRecorder()SkeletonDrawable.renderToPng()SkeletonDrawable.renderToRawImageData() のいずれかを使用します。

SkeletonDrawable は、ネイティブヒープに割り当てられたオブジェクトを格納します。そのため、SkeletonDrawable が不要になったら、SkeletonDrawable.dispose() を呼び出して、ネイティブオブジェクトを手動で破棄する必要があります。これを行わないと、ネイティブ メモリリークが発生してしまいます。

補足: SpineWidget を使用する場合は、ウィジェットが使用する SkeletonDrawable を手動で破棄する必要はありません。SkeletonDrawable は、ウィジェット自身が破棄されるときにウィジェットが破棄します。

アニメーションの適用

SpineWidget で表示されるスケルトンにアニメーションを適用するには、SpineWidgetController の コールバックで AnimationState を使用します。

注意: アニメーショントラックやアニメーションのキューイングなど、より詳しい情報については、Spineランタイムガイドのアニメーションの適用を参照してください。

トラック0に特定のアニメーションを設定するには、AnimationState.setAnimation() を呼び出します:

dart
final controller = SpineWidgetController(onInitialized: (controller) {
// トラック0にwalkアニメーションをセットし、それをループさせます
controller.animationState.setAnimationByName(0, "walk", true);
});

最初のパラメーターはトラック、2番目のパラメーターはアニメーションの名前、3番目のパラメーターはアニメーションをループさせるかどうかを指定します。

複数のアニメーションをキューに入れることもできます:

dart
controller.animationState.setAnimationByName(0, "walk", true);
controller.animationState.addAnimationByName(0, "jump", false, 2);
controller.animationState.addAnimationByName(0, "run", true, 0);

addAnimationByName() の最初のパラメーターはトラックです。2番目のパラメーターはアニメーションの名前です。3番目のパラメーターは、このアニメーションが同じトラック上の前のアニメーションと置き換わるまでの時間(ディレイ)を秒単位で指定します。最後のパラメーターはアニメーションをループさせるかどうかを指定します。

上の例では、まず "walk" アニメーションが再生されます。その2秒後に "jump" アニメーションが一度再生され、続いて "run" アニメーションに切り替わり、ループします。

あるアニメーションから別のアニメーションに遷移するとき、AnimationState はミックスタイムと呼ばれる特定の時間だけアニメーションをミックスします。これらのミックスタイムは AnimationStateData インスタンスで定義され、AnimationState はそこからミックスタイムを取得します。

AnimationStateData インスタンスはコントローラーを介して利用することもできます。デフォルトのミックスタイムや、特定のアニメーションのペアのミックスタイムを設定することができます:

dart
controller.animationStateData.setDefaultMix(0.2);
controller.animationStateData.setMixByName("walk", "jump", 0.1);

アニメーションを設定または追加すると、TrackEntry オブジェクトが返されます。これを利用してアニメーションの再生をさらに変更できます。例えば、アニメーションを逆再生するようにTrackEntryを設定することができます:

dart
final entry = controller.animationState.setAnimationByName(0, "walk", true);
entry.setReverse(true);

利用できるオプションについて詳しくは TrackEntry クラスのドキュメントをご覧ください。

注意: TrackEntry インスタンスを使用している関数の外部で保持しないでください。TrackEntryは内部で再利用されるため、それが表すアニメーションが完了すると無効になります。

スケルトンをスムーズにセットアップポーズに戻したい場合は、アニメーショントラックに空のアニメーションをセットまたはキューに追加します:

dart
controller.animationState.setEmptyAnimation(0, 0.5);
controller.animationState.addEmptyAnimation(0, 0.5, 0.5);

setEmptyAnimation() の最初のパラメーターはトラックを指定します。2番目のパラメーターは、前のアニメーションをミックスアウトし、"空の"アニメーションをミックスするために使用するミックスタイムを秒単位で指定します。

addEmptyAnimation() の最初のパラメーターはトラックを指定します。2番目のパラメーターはミックス時間を指定します。3番目のパラメーターはディレイ(秒単位)で、このディレイの後に空のアニメーションがミキシングされてトラック上の前のアニメーションと置き換わります。

AnimationState.clearTrack() を使えばトラック上のすべてのアニメーションを即座にクリアすることができます。すべてのトラックを一度にクリアするには AnimationState.clearTracks() を使います。しかしこれはスケルトンを最後に適用されたポーズのままにする点に注意してください。

スケルトンのポーズをセットアップポーズに戻すには、Skeleton.setToSetupPose() を使います:

dart
controller.skeleton.setToSetupPose();

これはボーンとスロットの両方をセットアップポーズの設定にリセットします。スロットだけをセットアップポーズの設定にリセットしたい場合は Skeleton.setSlotsToSetupPose() を使用してください。

AnimationStateイベント

AnimationState は、再生中のアニメーションのライフサイクル中に様々なイベントを発行します。必要に応じてこのイベントをリッスンすることで、それらに反応させることができます。SpineランタイムのAPIでは、以下のイベントタイプを定義しています:

  • start: アニメーションが開始された時に発されます。
  • interrupt: アニメーションのトラックがクリアされた、または新しいアニメーションが設定されたなどにより中断された時に発されます。
  • end: アニメーションが二度と適用されない時に発されます。
  • dispose: アニメーションのTrackEntryが破棄された時に発されます。
  • complete: アニメーションが1ループを完了するごとに発されます。
  • event: ユーザーが定義したイベントが発生した時に発されます。

イベントを受け取るには、AnimationStateListener コールバックを、すべてのアニメーションでイベントを受信するAnimationState か、キューされた特定のアニメーションのTrackEntryに登録します:

dart
final entry = controller.animationState.setAnimationByName(0, "walk", true);
entry.setListener((type, trackEntry, event) {
if (type == EventType.event) {
    print("User defined event: ${event?.getData().getName()}");
}
});

controller.animationState.setListener((type, trackEntry, event) {
print("Animation state event $type");
});

詳しくは example/lib/animation_state_events.dart をご覧ください。

スキン

/img/spine-runtimes-guide/spine-flutter/skins.png

多くのアプリケーションやゲームでは、髪や目、ズボン、イヤリングやバッグなどのアクセサリーなど、さまざまなアイテムを組み合わせてカスタムアバターを作ることができます。Spineでは、複数スキンを組み合わせることでこれを実現することができます。

以下のようにして、他のスキンからカスタムスキンを作成することができます:

dart
final data = controller.skeletonData;
final skeleton = controller.skeleton;
final customSkin = Skin("custom-skin");
customSkin.addSkin(data.findSkin("skin-base")!);
customSkin.addSkin(data.findSkin("nose/short")!);
customSkin.addSkin(data.findSkin("eyelids/girly")!);
customSkin.addSkin(data.findSkin("eyes/violet")!);
customSkin.addSkin(data.findSkin("hair/brown")!);
customSkin.addSkin(data.findSkin("clothes/hoodie-orange")!);
customSkin.addSkin(data.findSkin("legs/pants-jeans")!);
customSkin.addSkin(data.findSkin("accessories/bag")!);
customSkin.addSkin(data.findSkin("accessories/hat-red-yellow")!);
skeleton.setSkin(customSkin);
skeleton.setSlotsToSetupPose();

まず、コンストラクタ Skin() でカスタムスキンを作成します。

次に、スケルトンから SkeletonData を取得します。これは SkeletonData.findSkin()でスキンを名前から探すのに使用します。

Skin.addSkin() で新しいカスタムスキンにまとめたいスキンをすべて追加します。

最後に、Skeleton に出来上がった新しいスキンをセットし、Skeleton.setSlotsToSetupPose() を呼び出して、以前のスキンやアニメーションのアタッチメントが残らないようにします。

注意: Skin はC++オブジェクトをラップします。不要になったら Skin.dispose() を呼び出して手動で破棄する必要があります。

詳しくは example/lib/dress_up.dart をご覧ください。これは SkeletonDrawable を使用してスキンのサムネイルプレビューをレンダリングする方法も実演しています。

ボーンのトランスフォームの設定

/img/spine-runtimes-guide/spine-flutter/simple-animation.png

Spineエディター内でスケルトンを構築する際、スケルトンは、スケルトンのワールド座標系または「スケルトン座標系」と呼ばれるもので定義されます。この座標系は、スケルトンがレンダリングされる SpineWidget の座標系と一致しない場合があります。そのため、例えば、ユーザーがタッチでボーンを動かせるようにする場合などは SpineWidget に相対するマウス座標やタッチ座標をスケルトン座標系に変換する必要があります。

SpineWidgetControllertoSkeletonCoordinates() というメソッドを提供しており、関連する SpineWidget からの相対的な Offset を取得し、それをスケルトン座標系に変換します。

詳しくは example/lib/ik_following.dart をご覧ください。

Flame Integration

/img/spine-runtimes-guide/spine-flutter/flame.png

spine-flutterには、Flame EngineでSpineスケルトンをロードしてレンダリングする方法を実演しているサンプルが含まれています。詳しくはソースファイル example/lib/flame_example.dart を参照してください。

この例では、Flameの PositionComponent を継承したシンプルな SpineComponent を使用しています。SpineComponentは、静的な SpineComponent.fromAsset() メソッド、またはコンストラクタからインスタンス化できます。

この静的メソッドは、スケルトンとアトラスのデータを他のコンポーネントと共有する必要がない場合に、迅速な1回限りのロードメカニズムとして使用できます。このサンプルには SimpleFlameExample という FlameGame の実装が含まれており、Flameゲームの一部として画面上にSpineスケルトンを表示するシンプルな方法を実演しています。

コンストラクタで SpineComponent を作成すると、SkeletonDrawable を受け取ることで、データのロードと共有をより細かく管理できます。例えば、スケルトンデータとアトラスを事前にロードし、それを複数の SpineComponent インスタンスで共有できます。これにより、データが共有され、レンダリングがバッチ処理されるため、メモリ使用量とレンダリングパフォーマンスの両方が改善されます。詳しくは PreloadAndShareSpineDataExample という FlameGame の実装例を参照してください。

設計上、Flame はコンポーネントがいつ寿命に達したかを知ることができません。しかし、SpineComponent は寿命が来たときに解放する必要があるネイティブリソースを扱います。そのため、SpineComponent が使用されなくなった場合は、SpineComponent.dispose() を呼び出す必要があります。SpineComponentSkeletonDrawable から構築された場合は、PreloadAndShareSpineDataExample の例のように、構築元の SkeletonDataAtlas も手動で破棄する必要があります。

SpineランタイムのAPI呼び出し

spine-flutterは、SpineランタイムAPIのほとんどすべてをDartにマッピングしています。例えば SkeletonAnimationState のように、SpineWidgetControllerSkeletonDrawable が返すオブジェクトは、spine-cppのAPIをDartに1:1変換したものです。そのため、一般的なSpineランタイムガイドにある資料のほとんどすべてをDartコードに適用できます。

しかしながら、spine-cppからDart FFIへのブリッジの性質上、以下のような制限があります:

  • 返される配列やマップはすべて内部配列のコピーです。それらを変更しても影響はありません。しかし、返される Float32ListInt32List のインスタンスはネイティブメモリのラッパーであり、ネイティブデータを変更するために使用することができます。
  • ボーンやスロット、その他のSpineオブジェクトを直接、作成、追加、削除することはできません。
  • タイムラインのC++クラス階層はDartでは公開されていません。