Pixi.js + Spine で、WEB ページ上に動く 2D キャラを描画する

こんにちは。ココネウェブ開発室の D です。
最近は、中国発の某大手ゲーム会社のとあるプロダクトウェブサイトアニメーション表現にとても感動していて、その表現を弊社の Web ページにも取り入れたいと思う今日この頃です。

そこで今回は、Spine で出力した 2D データを PixiJS で組み込んで、Web ページ上に描画する方法をここにまとめます。

最終的な成果物

Spine と PixiJS

Spine とは

Spine は、Esoteric Software 社製の 2D アニメーション製作ソフトで、主にゲーム開発において利用されています。 spine は日本語で「脊椎」を意味し、その名の通り「ボーン」をイラストに組み込み動かすことでアニメーションを作ることができます。

同じような 2D 表現が出来るソフトとして Live2D も有名ですが、2 つのソフトはアプローチが異なります。

■ Live2D …… 面(メッシュ)で変形させることに特化したアニメーション

■ Spine …… 骨組み(ボーン)で動かすことに特化したアニメーション

PixiJS とは

PixiJS は、JavaScript 製ライブラリで、ブラウザ上に CG 描画をするための WebGL を用いて、2D 表現に特化し平易に利用できるようにしたラッパーです。公式で言及しているようにライブラリ自体が軽量でもあるため、導入のしやすさもメリットとなっています。

Web ページへの描画方法

1. Spine で設定したアニメーションデータをエクスポートする

何はともあれ、まずは描画したいアニメーションデータを Spine からエクスポートします。
ちなみに本記事では、Spine の公式アセットを使用して紹介します。

データを「JSON」で選択し、「JSON エクスポート」内は上記画像通りにします。あとは任意出力先を指定してエクスポートを押下し、.json、.png、.atlas の 3 つのファイルが生成されればエクスポート完了です。因みに出力されたファイルの中身は以下のようになっています。

  • .json  モデルやアニメーションに関する全ての数値情報(スケルトン)
  • .png  スプライト画像
  • .atlas  スプライト画像のパーツ名やテクスチャ座標(マッピング情報)

2. JavaScript(TypeScript)でアニメーションを描画

先ほどエクスポートしたデータを用いて、ページ上に描画します。 描画にあたって以下のパッケージを利用するので、環境に応じて依存関係の追加や CDN による呼び出しをしておきます。

  • PixiJS
    本記事の「PixiJS とは」を参照。
  • pixi-spine
    PixiJS で Spine からエクスポートしたデータを扱うための サードパーティ製 JavaScript ランタイム。

今回はバンドル化を想定したコードで例として載せます。

① canvas の生成

描画先の canvas 要素を生成します。PIXI.Applicationという、 canvas 要素を PixiJS の機能を通して操作出来るラッパーインスタンスが用意されているので、これを使用します。

import * as PIXI from 'pixi.js';

/**
 * PixiJS機能を盛り込んだcanvasの生成
 * PIXI.Applicationで縦横比、
 * app.view.styleで親要素の最大値を指定することでcanvas自体のリサイズが出来る。
 */
const app = new PIXI.Application<htmlcanvaselement>({
    width: 1920,
    height: 1080,
    backgroundColor: 0x000000
});
app.view.style.width = '100%';
app.view.style.height = 'auto';
document.body.appendChild(app.view);

② Spine でエクスポートしたデータを描画する

ランタイムパッケージを用いて、先ほどエクスポートした json ファイルを読み込み、canvas に追加することで 2D イラストが描画されます。このとき、他 2 つの png と atlas ファイルは、json ファイルと同じ場所に置いておくことで自動的に読み込まれます。もし、この時点で描画の変化が見られない場合(ソースコードエラーを除く)、canvas 左上の見えない場所でレンダリングされている可能性があるため、見える位置に持ってくるために座標や中心軸を指定する必要があります。(今回の場合、 Spine 公式アセットのデフォルトの中心軸が底辺の真ん中になっているため指定しています。)

import { Spine } from 'pixi-spine';

// アニメーションデータのロード(引数にjsonのパスを入れる)
const charaPromise = PIXI.Assets.load('spineboy.json');

// ロード完了後にcanvas追加
charaPromise.then((res) => {
    // Spineインスタンスで生成
    const spineboy = new Spine(res.spineData);
    // 中心軸の位置指定(初期値が下の真ん中、Spine側で変えられるかも?)
    spineboy.pivot.set(0, -spineboy.height / 2);
    // 描画位置の座標を中心に指定
    spineboy.position.set(app.screen.width / 2, app.screen.height / 2);
    // canvasに追加
    app.stage.addChild(spineboy);
});

③ アニメーションを切り替える

最後に、先ほど表示した 2D イラストのアニメーションを指定します。指定の際には Spine 内で設定したアニメーション名と同様の名称を渡すことで動くようになります。下記では、公式アセットが複数のアニメーションを用意しているため、2D イラストのクリック時にランダムにアニメーションを変更するようにしています。

// ロード完了後にcanvas追加(②の続き)
charaPromise.then((res) => {
        :
        :
        :

    // 初回描画後のアニメーション設定
    spineboy.state.setAnimation(
        0,
        currentAnimation,
        ['idle', 'run', 'walk'].includes(currentAnimation)
    );
    // インタラクションの有効化
    spineboy.interactive = true;
    // マウスクリック時にアニメーションを切り替える
    spineboy.on('pointerdown', () => {
        if (!spineboy) return;
        const animations: AnimationSpineboy[] = [
            'death',
            'hit',
            'idle',
            'jump',
            'run',
            'shoot',
            'walk'
        ];
        const nextAnimationIndex = Math.floor(Math.random() * animations.length);
        currentAnimation = animations[nextAnimationIndex];
        spineboy.state.setAnimation(
            0,
            currentAnimation,
            ['idle', 'run', 'walk'].includes(currentAnimation)
        );
    });
});

これで最初のデモのような、2D アニメーションの描画が出来るようになったかと思います。

まとめ

PixiJS やランタイムの恩恵により、シンプルにアニメーション 2D 描画をするだけであれば、サクサク実装することができました。あとは画面全体を考慮したデザインやアニメーションと上手く組み合わせていくことができれば、非常に動きのあるサイトが作れるかと思います。今後は Spine を用いた 2D プロダクトを想定している(2D モーションデザイナー採用情報より)ようなので、クライアントアプリのみならず、Web でも上手く活用していければと考えています。

ココネでは一緒に働く仲間を募集中です。

ご興味のある方は、ぜひこちらのエンジニア採用サイトをご覧ください。

→ココネ株式会社エンジニアの求人一覧

 

Category

Tag

%d人のブロガーが「いいね」をつけました。