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

ライセンスについて

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

はじめに

spine-canvaskitはTypeScriptベースのランタイムで、ブラウザやNode.js環境でCanvasKitを使ってSpineのスケルトンをロード、操作、レンダリングします。spine-canvaskitはフロントエンド(UI要素のレンダリング)とバックエンド(ヘッドレスでスケルトンをレンダリング)の両方で使用できます。

spine-canvaskitはCanvasKit +0.39.1 を必要とし、2色ティントを除くすべてのSpine機能をサポートしています。

spine-canvaskitはSpineランタイム コアAPIのTypeScript実装である spine-core 上に構築されています。コアAPIの詳細についてはSpineランタイムガイドを参照してください。

インストール方法

注意: spine-canvaskitの major.minor バージョンがエクスポートを行ったSpineエディターの major.minor バージョンと一致していることを確認してください。詳しくは「Spineエディターとランタイムのバージョン管理に関するガイド」を参照してください。

NPM または Yarn の場合

spine-canvaskitはNPMまたはYarn経由でプロジェクトに追加できます:

npm install @esotericsoftware/spine-canvaskit@^4.2.0
yarn add @esotericsoftware/spine-canvaskit@^4.2.0

spine-canvaskitはECMAScriptモジュールで、Node.jsやすべてのモダンブラウザでネイティブに使用したり、webpack、rollup、esbuild などのツールでバンドルしたりできます。また、デバッグしやすいようにソースマップも含まれています。

補足: spine-canvaskitモジュールからクラス、列挙型(enum)、または関数にアクセスするには、import { loadTextureAtlas } from "@esotericsoftware/spine-canvaskit" のように単純にそれらをインポートします。

バニラJavaScriptの場合

バニラJavaScript(カスタマイズされていないJavaScript)プロジェクトでは、scriptタグを使ってunpkg CDNからspine-canvasを追加できます:

<script src="https://unpkg.com/canvaskit-wasm@latest/bin/canvaskit.js"></script>
<script src="https://unpkg.com/@esotericsoftware/spine-canvaskit@4.2.*/dist/iife/spine-canvaskit.js"></script>

ソースマップを有効にすると、ランタイムのオリジナルのTypeScriptソースコードをデバッグできます。

また、spine-canvaskitのminified(縮小)バージョンも提供しており、unkgのURLで接尾辞 .jsmin.js に置き換えることで使用できます。

<script src="https://unpkg.com/canvaskit-wasm@latest/bin/canvaskit.js"></script>
<script src="https://unpkg.com/@esotericsoftware/spine-canvaskit@4.2.*/dist/iife/spine-canvaskit.js"></script>

注意: バニラJavaScriptプロジェクトにspine-canvaskitをインクルードする場合、すべてのクラス、列挙型、および関数にグローバルspineオブジェクト (spine.loadTextureAtlas()spine.SkeletonData など) を介してアクセスする必要があります。以下のコードサンプルではspineオブジェクトは省略されているので注意してください。

サンプル

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

サンプルを実行するには:

  1. Node.jsをインストールします
  2. spine-runtimesリポジトリをクローンします
  3. ターミナルで以下を実行します:
    cd path/to/spine-runtimes/spine-ts
    npm run dev

するとブラウザウィンドウが開き、すべてのspine-tsランタイムの例が表示されます。spine-canvaskit関連のサンプルは CanvasKit の見出しの下にあります。

以下のサンプルが含まれています:

  • spine-canvaskit/example/headless.js: Node.jsコマンドラインアプリで、Spineboyのスケルトンとアトラスをロードし、portal アニメーションをアニメーションPNGファイルにレンダリングします。npm run dev を起動したら、ターミナルで node spine-canvaskit/example/headless.js を実行すると、アニメーションを含む output.png というファイルが生成されます。
  • spine-canvaskit/example/index.html: ブラウザでCanvasKitを介したSpineスケルトンのロードとレンダリングを実演するウェブアプリです。
  • spine-canvaskit/example/animation-state-events.html: アニメーションステートイベントのリスナーの設定を実演するウェブアプリです。
  • spine-canvaskit/example/mix-and-match.html: 複数スキンを組み合わせる方法を実演するウェブアプリです。

spine-canvaskitランタイムの更新

プロジェクトの spine-canvaskit ランタイムを更新する前に、Spineエディターとランタイムのバージョン管理に関するガイドを参照してください。

spine-canvaskitランタイムの更新は、package.json ファイル内の spine-canvaskit パッケージのバージョン文字列を変更して、再度 npm install を実行するだけでできます。バニラJavaScriptの場合は、scriptタグ内のunpkg URLを更新してください。

注意: spine-canvaskit パッケージの major.minor バージョンを変える場合は、必ずSpineスケルトンも同じmajor.minorバージョンのSpineエディターで再エクスポートする必要はあります!

spine-canvaskitを使用する

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

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

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

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

注意: spine-canvaskitはアトラスイメージに自動的に乗算済みアルファ(premultiplied alpha)を適用します。そのため、アトラスをエクスポートするときは乗算済みアルファを使用しないでください!

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

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

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

これらはアプリに同梱することになるファイルです。

Spineアセットの更新

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

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

CanvasKitの初期化

spine-canvaskitはSpineスケルトンの読み込みとレンダリングをCanvasKitに依存しています。spine-canvaskitを使用する前に、CanvasKitを初期化する必要があります。

NodeJSまたはES6対応のブラウザプロジェクトする場合:

import CanvasKitInit from "canvaskit-wasm";

const ck = await CanvasKitInit();

ブラウザのscriptタグでバニラJavaScriptを使用する場合:

<script src="https://unpkg.com/canvaskit-wasm@latest/bin/canvaskit.js"></script>
<script type="module">
const ck = await CanvasKitInit();
</script>

以降のコード例では、ck が初期化された CanvasKit オブジェクトへの参照を保持していると仮定します。

コアクラス

spine-canvaskit APIはTypeScriptベースの汎用spine-coreランタイムの上に構築されており、プラットフォームに依存しないコアクラスとSpineスケルトンのロード、クエリ、変更、アニメーションを行うアルゴリズムを提供します。コアクラスもspine-canvaskitの一部です。

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

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

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

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

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

spine-canvaskit は spine-core の上に機能を追加することで、Spineスケルトンの読み込み、変更、レンダリングを簡単にしています。

アセットのロード

Spineアトラスおよびスケルトンデータファイルは、それぞれ loadTextureAtlas() 関数およびloadSkeletonData() 関数でロードできます。

spine-canvaskitは、NodeJSやブラウザなど、CanvasKitが利用可能なあらゆるJavaScript環境で動作します。そのため、ローダー関数はプラットフォームに依存せず、絶対パスまたは相対パスを受け取り、ファイルの生(raw)のバイナリコンテンツを含む Buffer(NodeJS)またはArrayBuffer(ブラウザ)を返す関数を提供する必要があります。

この関数の TypeScript シグネチャは以下のとおりです:

readFile(path: string): Promise<any>

NodeJSの場合、この関数は以下のように実装できます:

import * as fs from "fs"

async function readFile(path) {
return fs.readFileSync(path)
}

ブラウザ環境では、この関数は次のように実装できます:

async function readFile(path) {
const response = await fetch(path);
if (!response.ok) throw new Error("Could not load file " + path);
return await response.arrayBuffer();
}

loadTextureAtlas() 関数を使用して、.atlas ファイルとその .png ページ画像ファイルを読み込みます:

const atlas = await loadTextureAtlas(ck, "myatlas.atlas", readFile);

アトラスの .png ファイルは、.atlas ファイルが存在するディレクトリに対して相対的に解決されます。

同様に、loadSkeletonData() 関数を使用して、スケルトンデータの .json または .skel ファイルをロードします:

const skeletonData = await loadSkeletonData("myskeleton.skel", atlas, readFile);

loadSkeletonData() は、ファイル名のそれぞれの拡張子に基づいて、.json ファイルと .skel ファイルの両方をロードできます。atlas は、スケルトンデータから派生したスケルトンをレンダリングするために必要な画像を取得するために使用されます。

SkeletonDrawable

SkeletonDrawable は、スケルトンの現在のポーズとスキンを保存する Skeleton と、アニメーションの追跡と適用を行う AnimationState をカプセル化します。

スケルトンのアトラスとスケルトンデータファイルをロードしたら、そこから1つ以上のSkeletonDrawable インスタンスを作成できます。SkeletonData は暗黙的にTextureAtlas を参照していることに注意してください。

SkeletonDrawable コンストラクタを呼び出して、新しいインスタンスを作成します:

const drawable = new SkeletonDrawable(skeletonData);

SkeletonDrawable 内の SkeletonAnimationState には、それぞれのフィールドからアクセスできます:

// スケルトンの位置とスケール
const skeleton = drawable.skeleton
skeleton.x = 300;
skeleton.y = 380;
skeleton.scaleX = skeleton.scaleY = 0.5;

// AnimationStateにアニメーションをセットする
const animationState = drawable.animationState;
animationState.setAnimation(0, "walk", true);

Skeleton および AnimationState APIの包括的な説明については、Spineランタイムガイドを参照してください。以下に、API の最低限の使用例を示します。

アニメーションの適用

補足: より詳しい情報についてはSpineランタイムガイドの アニメーションの適用セクションを参照してください。

AnimationState を使用すると、複数のトラックに1つ以上のアニメーションをキューに入れることができます。トラックはインデックス0から始まります。上位トラックのアニメーションは、下位トラックのアニメーションでキーになっているプロパティを上書きします。このトラックの概念により、複数のアニメーションを一度に再生したり、ミックスしたりすることができます。

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

drawable.animationState.setAnimation(0, "walk", true);

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

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

const animationState = drawable.animationState;
animationState.setAnimation(0, "walk", true);
animationState.addAnimation(0, "jump", false, 2);
animationState.addAnimation(0, "run", true, 0);

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

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

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

AnimationStateData インスタンスは、AnimationStateからも利用できます。デフォルトのミックスタイムや、特定のアニメーションのペアのミックスタイムを設定できます:

animationState.data.defaultMix = 0.2;
animationState.data.setMix("walk", "jump", 0.1);

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

const entry = drawable.animationState.setAnimation(0, "walk", true);
entry.reverse = 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() を使います:

drawable.skeleton.setToSetupPose();

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

AnimationStateイベント

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

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

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

const entry = drawable.animationState.setAnimation(0, "walk", true);
entry.listener = {
event: (entry, event) => console.log(`User defined event: ${event.data.name}`),
complete: (entry) => console.log(`Animation loop completed.`)
}

drawable.animationState.setListener({
end: (entry) => console.log(`Animation ${entry.data.name} has ended and will not be applied again.`
});

詳しくは example/animation-state-events.html をご覧ください。

スキン

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

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

// カスタム用の空のスキンを作成
const skin = new spine.Skin("custom");

// 作成したカスタムスキンに他のスキンを追加
skin.addSkin(skeletonData.findSkin("skin-base"));
skin.addSkin(skeletonData.findSkin("nose/short"));
skin.addSkin(skeletonData.findSkin("eyelids/girly"));
skin.addSkin(skeletonData.findSkin("eyes/violet"));
skin.addSkin(skeletonData.findSkin("hair/brown"));
skin.addSkin(skeletonData.findSkin("clothes/hoodie-orange"));
skin.addSkin(skeletonData.findSkin("legs/pants-jeans"));
skin.addSkin(skeletonData.findSkin("accessories/bag"));
skin.addSkin(skeletonData.findSkin("accessories/hat-red-yellow"));
skeleton.setSkin(skin);
skeleton.setSlotsToSetupPose();

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

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

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

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

詳しくは example/mix-and-match.html をご覧ください。

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

Spineエディターでスケルトンを構築する際、スケルトンは「スケルトン座標系」と呼ばれるもので定義されます。そのため、キャンバスに対するタッチ座標やマウス座標をボーンの座標系に変換するには、Bone.worldToLocal() メソッドを使用します。

これは、ユーザー入力に基づいてボーンの位置を駆動する場合に便利です。

詳しくは example/ik-following.html をご覧ください。

パフォーマンス

spine-canvaskitは CavansKit.MakeVertices()Canvas.drawVertices() を使用して、個々のスケルトンアタッチメントのメッシュを描画します。Skiaはこれらのメッシュをバックグラウンドでバッチ処理しているように見えますが、spine-canvaskit自体でアタッチメントのメッシュのバッチ処理を行うことで、さらなるパフォーマンスの向上が期待できます。

詳しくは example/micro-benchmark.html をご覧ください。