はじめてのPixiJSでブラウザ上でアニメーションを実現

GoodPatchさんから最近リリースされたクラウドサービス「Strap」でPixiJSを利用しているという記事を目にしたのでどのようなものか確認するためにPixiJSの動作確認を行ってみました。
複雑なことを行うまでには時間がかかると思いますので本文書では公式サイトのサンプルとマニュアル、ネット上の情報を利用して基本的な動作確認を行いPixiJSに慣れ親しむことを目的にしています。
目次
PixiJSとは
PixiJSはインタラクティブなアプリケーションに利用できる2Dのレンダリングライブラリで、ゲームにも利用されています。どのようなゲームに利用されているかまでは不明ですが簡単に言えばブラウザ上でアニメーションを作成することができます。
Hello World
PixiJSを使ってブラウザの画面上にHello World!を表示させてみたいと思います。動作確認を手元の環境でも行えるようにcdnを利用します。
<script src="https://pixijs.download/release/pixi.js"></script>
下記のコードでPIXIアプリケーションのインスタンスを作成することから始めます。
const app = new PIXI.Application();
PIXIアプリケーションはcanvas要素を作成するので下記のコードでbodyの中にcanvasの要素を挿入します。挿入する場所にbodyをしていますが、要素であればbodyである必要はありません。
document.body.appendChild(app.view);
index.htmファイルにcdnと上記の2行のコードを追加します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>PixiJS</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.3/pixi.min.js"></script>
</head>
<body>
<script>
const app = new PIXI.Application();
document.body.appendChild(app.view);
</script>
</body>
</html>
ブラウザで確認すると画面上に背景が黒の四角の領域が表示されます。

ブラウザのデベロッパーツールで確認するとbodyの中にcanvas要素が挿入されていることが確認でき、幅は800, 高さは600に設定されています。このcanvas要素の中に画像やテキストを描写していきます。
<canvas width="800" height="600" style="touch-action: none; cursor: inherit;"></canvas>

デベロッパーツールのコンソールを見るとバージョンを確認することができます。

PIXIアプリケーションで作成されたcanvasの中にテキストで文字を表示させます。テキストを表示したい場合はPIXI.textを利用します。場所をx,yで指定していますが、指定しないと左上に表示されます。テキストの色は背景色が黒なので白に設定をしています。
const message = new PIXI.Text("Hello World!");
message.x = 200;
message.y = 200;
message.style.fill = "white";
app.stage.addChild(message);
ブラウザで確認すると下記のようにHello Worldが表示されることがわかります。

文字を回転
表示したHello Worldを回転させてみましょう。tickerを含んだ一行を追加するだけで文字を回転されることができます。デフォルトでは左上が回転軸となります。下の図は静止画ですが実際には右回りに回転しています。tickerを利用することでアニメーションを実行できることがわかります。
app.ticker.add((delta) => (message.rotation += 0.01 * delta));

文字の中心で回転させるためには、anchor.set(0.5)を設定します。anchorを設定するとx,yの位置もこの位置を基準に設定されます。
const message = new PIXI.Text("Hello World!");
message.anchor.set(0.5);
message.x = 200;
message.y = 200;
message.style.fill = "white";
app.stage.addChild(message);
app.ticker.add((delta) => (message.rotation += 0.01 * delta));

文字の移動
文字の自動的な移動についてはx, yの値をtickerの中で変更することで移動できるのではと予想することができるかと思います。先程までの回転を設定していたrotationからxに変更すると右方向にゆっくりと文字が移動することがわかります。大きな値を入れると一瞬で画面から文字が消えます。
app.ticker.add((delta) => (message.x += 1));

イベントの設定
ここまではテキストがtickerの設定により自動で動いていたためブラウザを閲覧するユーザとのインタラクションは全くありませんでした。ユーザとのインタラクションを持たせるため次はイベントを利用してユーザがクリックすることでテキストの色を変える方法を確認します。
ユーザからの何かの操作を受け取るには、interactiveをtrueに設定する必要があります。この設定を行わないとclickイベントを設定しても何も反応はありません(イベントが発火しない)。
message.interactive = true;
onメソッドの第一引数にイベント名を設定し、第二引数にcallback関数を設定します。jQueryなどの書式に似ていますが、これでclickイベントの設定は完了です。ブラウザ上で右側に動いているHello Worldのテキストをクリックしブラウザのデベロッパーツールのコンソールにclickが表示されたらイベントの設定は正常に動作しています。
message.on("mousedown", () => console.log("click"));
clickイベントの設定方法がわかったのでテキストの文字の色をクリックすることで変更させます。
message.on("mousedown", () => (message.style.fill = "red"));
右に動いしている”Hello World”をクリックするとクリックするとHello World!の色が赤に変わります。たった数行のコードでアニメーションを行っているテキストに対してイベントまで設定しインタラクティブな動作を行えるようになりました。

イベントの種類、またcallback関数の中身を変更することでユーザとのさまざまなインタラクションを行うことができそうです。
tikerで設定できるのは1つの動作だけではないので右側に移動しならが回転しているHello Worldをクリックするとテキストの色を赤に変更することも可能です。
const message = new PIXI.Text("Hello World!");
message.anchor.set(0.5);
message.x = 200;
message.y = 200;
message.style.fill = "white";
app.stage.addChild(message);
message.interactive = true;
message.on("mousedown", () => (message.style.fill = "red"));
app.ticker.add((delta) => {
message.rotation += 0.01 * delta;
message.x += 1;
});
ドラッグ&ドロップ
次はドラッグ&ドロップでテキストの場所をユーザが移動させる方法を確認します。
mousedownイベントの設定
ドラッグ&ドロップを行うためには、マウスのイベントを利用します。最初にテキストの上でマウスを押すとイベントが発火するmousedownを設定します。

callbackに入っているeはeventの略で、e.data.global.xでマウスの現在の位置を取得することができます。
const app = new PIXI.Application();
document.body.appendChild(app.view);
const message = new PIXI.Text('Hello World!');
message.interactive = true;
message.x = 200;
message.y = 200;
message.style.fill = 'white';
message.on('mousedown', (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
});
app.stage.addChild(message);
Hello Worldのテキスト文字にマウスダウンイベントを設定しているのでテキストの左端もしくは右端をクリックしてください。テキストの中心がマウスを押した場所に移動します。テキスト外の場所をクリックしてもテキストにイベントが設定されているだけなので何も起きません。
mousemoveイベント
マウスの移動に合わせてイベントが発火するmousemoveイベントを設定します。まず、テキストにmousemoveイベントを設定するとどのような動作になるか確認します。
message.on("mousemove", function (e) {
message.x = e.data.global.x;
message.y = e.data.global.y;
});
マウスの動きでイベントが発火するのでマウスのポインターが四角の中に入った瞬間にマウスポインターの場所までHello Worldのテキストが移動してきます。その後は四角の枠にマウスポインターがある間はずっとマウスについてきます。
下記ではマウスが右上から四角に入ったのでテキストも右上にいます。

ドラッグ&ドロップを実現したいので、テキストをクリック後(mousedown)からmousemoveイベントを発火させたいため、draggableというパラメータを設定します。

mousedownイベントを発火した際にdraggableをtrueにして、draggableがtrueの場合のみmousemoveでマウスの移動についていくように設定を行います。
message.on("mousedown", (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
message.draggable = true;
});
message.on("mousemove", function (e) {
if (message.draggable) {
message.x = e.data.global.x;
message.y = e.data.global.y;
}
});
ここまでの設定でドラッグが動作することが確認できます。一度ドラッグできるようになったテキストを離すことができません。ドロップを実現するためにmouseupイベントを利用します。
mouseupイベントの設定
mouseupイベントはmousedownボタンで押しているマウスのボタンが上がった時にイベントが発火します。このイベントが発火した時にdraggableをfalseにするとドラッグ処理が停止します。
message.on("mouseup", (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
message.draggable = false;
});
もう一点四角の外側にマウスが出た際にもmouseupするとドロップできるようにmouseupoutsideイベントを設定します。
message.on("mouseoutside", (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
message.draggable = false;
});
アルファ値を設定
ドラッグ&ドロップが行われていることがわかるようにアルファ値を設定し、透明度を変えます。
message.on("mousedown", (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
message.draggable = true;
message.alpha = 0.5;
});
message.on("mousemove", (e) => {
if (message.draggable) {
message.x = e.data.global.x;
message.y = e.data.global.y;
}
});
message.on("mouseup", (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
message.draggable = false;
message.alpha = 1;
});
message.on("mouseoutside", (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
message.draggable = false;
message.alpha = 1;
});
ドラッグしている間はテキストが薄くなっていることがわかります。

複数のHello Worldを画面に表示
先ほど作成したドラッグ&ドロップのコードからテキストのmessageを作成する関数createTextを作成します。
const createText = (x, y) => {
const message = new PIXI.Text("Hello World!");
message.anchor.set(0.5);
message.x = x;
message.y = y;
message.style.fill = "white";
app.stage.addChild(message);
message.interactive = true;
message.on("mousedown", (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
message.draggable = true;
message.alpha = 0.5;
});
message.on("mousemove", (e) => {
if (message.draggable) {
message.x = e.data.global.x;
message.y = e.data.global.y;
}
});
message.on("mouseup", (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
message.draggable = false;
message.alpha = 1;
});
message.on("mouseoutside", (e) => {
message.x = e.data.global.x;
message.y = e.data.global.y;
message.draggable = false;
message.alpha = 1;
});
};
forループを利用して画面上に50個のテキストを作成します。x, yのテキストの位置をランダムに決めています。app.screenは四角枠の幅と高さを取得することができます。
for (let i = 0; i < 50; i++) {
createText(
Math.floor(Math.random() * app.screen.width),
Math.floor(Math.random() * app.screen.height)
);
}
ブラウザで確認すると50のテキストが作成し、それぞれを個別にドラッグ&ドロップすることができます。

シンプルな動作確認しかしていませんPixiJsはいかがだったでしょうか。ブラウザ上に表示したテキストに対してアニメーション、イベントを設定してユーザとのインタラクティブな動作を行えることがわかりました。