##これまでのあらすじ
PixiJSの基本となる仕組みを把握し、
図形、スプライト、文字の描画方法を学び、
それをアニメーションさせる方法まで追ってきました。(超ざっくり)
今回はクリックやドラッグなど、ユーザーの操作に反応させる方法を見ていきます。
##PIXI.utils.EventEmitter
####PIXI.DisplayObjectの基本クラス
さて、これまで見てきた描画オブジェクトはどれもPIXI.DisplayObjectの派生クラスでした。
しかしPIXI.DisplayObjectにもまだ上の基本クラスが存在します。
それがPIXI.utils.EventEmitterです。
では早速、PIXI.utils.EventEmitterがどのような機能を持っているのかAPI Documentationを見てみましょう。
(リファレンス:PIXI.utils.EventEmitter)
なんでか知りませんが、PIXI.utils.EventEmitterについてはリファレンスで何も触れられていないんですよね。
そしてただ"See"(見ろ)と一言そえてgithubのリンクがあるのみ。
・・・ソースを読め、自力でソースから読み取れ。
そうおっしゃいますか、そうですか。
ならば仕方ない、がんばってソースから内容を把握しましょう。アタイ負けないわ。
リンクからgithubを開くと「index.d.ts」に型定義が、「index.js」に実装が記述されています。
ここから必要な情報を拾えそうなので、本記事は以後、このソースを頼りに記述しています。
・・・少し話が脱線したので本題に戻します。
描画オブジェクトの基本クラスであるPIXI.DisplayObjectは、PIXI.utils.EventEmitterの派生クラスでもありました。
そして掲題のクリックやドラッグなどのユーザーインタラクションを担っているのがこのPIXI.utils.EventEmitterです。
つまり描画オブジェクトはどれでもインタラクション機能を継承しており、スプライトはもちろん、コンテナや図形、文字なども全てイベントに反応させることができるわけです。
要点1:PIXI.DisplayObjectはPIXI.utils.EventEmitterの派生クラスであり、PIXI.utils.EventEmitterが各インタラクションイベントの反応を担っている。
要点2:つまりPIXI.DisplayObjectの派生クラスである各描画オブジェクトであれば、どれもインタラクションイベントに関する機能を継承している。
####サンプルコード
Examplesにちょうど良いサンプルが用意されていたので、今回はそれをそのまま題材として使わせてもらうことにします。
コード全容は リンク先 をご確認ください。
####冒頭の雑多なあれこれ
記事の主旨からは外れるのでさらっと流します。
const app = new PIXI.Application({ backgroundColor: 0x1099bb });
document.body.appendChild(app.view);
// create a texture from an image path
const texture = PIXI.Texture.from('examples/assets/bunny.png');
// Scale mode for pixelation
texture.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
for (let i = 0; i < 10; i++) {
createBunny(
Math.floor(Math.random() * app.screen.width),
Math.floor(Math.random() * app.screen.height),
);
}
まずはお約束通りにPIXI.Applicationをインスタンス化し、view
をHTMLのbody要素にappendChild
しています。(第一回の内容)
次にURLから直接テクスチャを生成しています。第三回でPIXI.Splite#from
メソッドについて言及しましたが、PIXI.Textureにも同じようなメソッドが用意されていたわけです。
次のtexture.baseTexture.scaleMode
についてですが、まず、テクスチャ(PIXI.Textureインスタンス)はそのプロパティとして、更に**PIXI.BaseTexture**というテクスチャに関する様々な設定情報を一まとめにしたオブジェクトを持っています。
scaleMode
プロパティはその設定の内の一つです。
これはテクスチャの拡大縮小を行った際にスムージングを行うか否かの設定であり、「LINEAR(線形補完)」「NEAREST(近似)」の二つの値を選べます。
おわかりいただけるだろうか。
LINEARの方は良く言えばなめらか、悪く言えばボヤけています。
なので、ドット絵をカチッと見せるのであればNEARESTに、写真などを自然に見せる場合はLINEARですね。
さて本筋に戻ってサンプルコードの続きですが、後はfor
ループ内でx、y座標をランダムに用意してcreateBunny
ファンクションに投げ、バニーさんをたくさん生成しています。
####interactiveプロパティ
function createBunny(x, y) {
// create our little bunny friend..
const bunny = new PIXI.Sprite(texture);
// enable the bunny to be interactive... this will allow it to respond to mouse and touch events
bunny.interactive = true;
// (後略)
}
まず冒頭で用意したテクスチャーでスプライトを生成しています(第三回の内容)
その次が今回のキモ、interactive
プロパティです。
これはPIXI.DisplayObjectに用意されたプロパティであり、描画オブジェクトならどれも継承しています。
(リファレンス:PIXI.DisplayObject#interactive)
この後、スプライトにイベントハンドラを追加していくことになるのですが、このinteractive
プロパティがfalseのままだと仮にイベントハンドラを登録済でも何も反応しません。(初期値がfalse)
これにより、状況に応じてイベントへの反応をオン/オフできます。
要点3:インタラクションイベントに反応させるには、まずinteractive
プロパティをtrueにする必要がある。
####buttonModeプロパティ
// this button mode will mean the hand cursor appears when you roll over the bunny with your mouse
bunny.buttonMode = true;
buttonMode
プロパティはPIXI.DisplayObjectのプロパティであり、trueに設定するとマウスオーバーした際にカーソルを指(pointer)の状態にしてくれます。
これもインタラクションの一種なので、上記のinteractive
プロパティがfalseの場合は反応しません。
####途中、雑多な部分
// center the bunny's anchor point
bunny.anchor.set(0.5);
// make it a bit bigger, so it's easier to grab
bunny.scale.set(3);
anchor
とscele
をセットしています。(第二回と第三回の内容)
はい、次いきます。
####onメソッド
// setup events for mouse + touch using
// the pointer events
bunny
.on('pointerdown', onDragStart)
.on('pointerup', onDragEnd)
.on('pointerupoutside', onDragEnd)
.on('pointermove', onDragMove);
本日のキモ、その2です。
on
メソッドはPIXI.utils.EventEmitterで定義されたメソッドであり、イベントリスナの登録を行います。DOMで言うところのaddEventListener
のようなものです。
・on(event, fn, context)
引数を3つ取ります。
event:イベント種類
fn:イベントハンドラ
context:イベントハンドラをbindする対象
eventはDOMのイベントと良く似ていますが独自の機能も持っています。
具体的にどのような項目が用意されているかはPIXI.DisplayObjectのリファレンスにまとめられています。
今回は
「pointerdown(マウスクリック、タッチパネルでのタッチ)」
「pointerup(マウスでクリックを離した、タッチパネルでタッチを離した)」
「pointerupoutside(描画オブジェクトの外側でクリック、タッチを離した)」
「pointermove(描画オブジェクト上でマウス、タッチが動いた)」
の4つのイベントを続けて登録しています。
on
メソッドは、戻り値に自身(this)をリターンする特徴があるので、それを利用してメソッドチェーンで記述しています。
fnはイベント発生時に呼び出されるイベントハンドラです。これについては特に説明することはありません。
contextですが、イベントハンドラを呼び出す際にbindする対象となるobjectです。たいていは省略して問題なく、省略した場合はイベントの発生した描画オブジェクトが「this」となります。
要点4:PIXI.DisplayObject派生クラスはon
メソッドを持っており、このon
メソッドでイベントリスナの登録を行う。
####イベント種類についてもう少し掘り下げ
// For mouse-only events
// .on('mousedown', onDragStart)
// .on('mouseup', onDragEnd)
// .on('mouseupoutside', onDragEnd)
// .on('mousemove', onDragMove);
// For touch-only events
// .on('touchstart', onDragStart)
// .on('touchend', onDragEnd)
// .on('touchendoutside', onDragEnd)
// .on('touchmove', onDragMove);
全てコメントアウトされていますが、割と重要な部分です。
コメントの内容通りではあるのですが、マウス操作にのみ反応する「mouse****」系イベント、タッチパネルのタッチ操作にのみ反応する「touche****」系イベントがそれぞれ用意されています。
実際にはそのどちらにも対応した「pointer****」系イベントが扱い易いですが、マウス操作とタッチパネル操作の両方に対応させようとすると、その挙動の差分を考慮しつつイベントハンドラを記述する必要が発生し、それはそれで大変だったりもします。
####余談:offメソッド、onceメソッド、removeAllListenersメソッド等々
EventEmitterのソースを見ていたら、他にもイベントリスナを取り除くoff
メソッド、一度だけしか反応しないリスナを登録するonce
メソッド、全てのイベントリスナを一括除去するremoveAllListeners
メソッドなんてのもあります。
ここでは深く触れませんが、必要に応じてどのような機能があるのか探せるよう、ソースを読みに行く姿勢は常に意識したいですね(自戒)
####雑多な処理、後半
// move the sprite to its designated position
bunny.x = x;
bunny.y = y;
// add it to the stage
app.stage.addChild(bunny);
createBunnyファンクションで受け取った引数をスプライトのx、yにセットし、addChilde
しています。
特に言及すべきこともないので、ささっと次に行きます。
####各イベントハンドラ
function onDragStart(event) {
// store a reference to the data
// the reason for this is because of multitouch
// we want to track the movement of this particular touch
this.data = event.data;
this.alpha = 0.5;
this.dragging = true;
}
function onDragEnd() {
this.alpha = 1;
this.dragging = false;
// set the interaction data to null
this.data = null;
}
function onDragMove() {
if (this.dragging) {
const newPosition = this.data.getLocalPosition(this.parent);
this.x = newPosition.x;
this.y = newPosition.y;
}
}
ここからは各イベントハンドラについて見ていきます。
onDragStart
は引数としてeventを受け取っていますが、これはDOMのイベントオブジェクトと似て否なるPIXI.InteractionEventです。
今回のdata
プロパティのようにDOMにはない要素があったり、逆にtimeStamp
プロパティのようなDOMにはあるのにPIXI.InteractionEventにはないプロパティもあるので、注意が必要です。
さて、コードの内容に戻ります。
onDragStart
ではeventのdata
を、thisのdata
プロパティとして持たせています。
on
メソッドの項でも触れましたが、contextを省略している場合、イベントハンドラはイベントの発生した描画オブジェクトに結び付けられます。そのため、ここでの「this」はクリックしたバニーさんインスタンスです。
要点5:(on
メソッドでのイベントリスナ登録時にcontextを省略した場合、)イベントハンドラはイベントの発生した描画オブジェクトにbindされる。そのため、イベントハンドラ内の「this」はその描画オブジェクトを指す。
後は、this(つまり対象のバニーさん)のalpha
プロパティ(不透明度)を0.5にして半透明にし、ドラッグ中のフラグをtrueにしています。alpha
はPIXI.DisplayObjectで定義されているプロパティです。
残りのイベントハンドラの内容は本記事の主旨から逸れるので駆け足で流します。
onDragEnd
はクリックまたはタッチが離れた時の処理であり、半透明にしていたalpha
を1に戻し、ドラッグ中のフラグを解除、イベントデータをnullにして空の状態にしています。
onDragMove
はドラッグ中のフラグがtrueかどうかを判定し、trueの場合にのみ動くようにしています。これによりマウスカーソルがスプライト上を素通りしようとしただけで反応してしまうことを防いでいます。
位置座標newPosition
をthis.data
を介して取得しているのは、タッチパネル操作の場合を想定しての対応です。マウス操作の場合はカーソル位置は一つしかありませんが、タッチパネルの場合はマルチタッチの可能性が存在するため、タッチしたイベント情報をdata
として保持させることでタッチと該当スプライトを紐付けしているわけです。
##まとめ
要点1:PIXI.DisplayObjectはPIXI.utils.EventEmitterの派生クラスであり、PIXI.utils.EventEmitterが各インタラクションイベントの反応を担っている。
要点2:つまりPIXI.DisplayObjectの派生クラスである各描画オブジェクトであれば、どれもインタラクションイベントに関する機能を継承している。
要点3:インタラクションイベントに反応させるには、まずinteractive
プロパティをtrueにする必要がある。
要点4:PIXI.DisplayObject派生クラスはon
メソッドを持っており、このon
メソッドでイベントリスナの登録を行う。
要点5:(on
メソッドでのイベントリスナ登録時にcontextを省略した場合、)イベントハンドラはイベントの発生した描画オブジェクトにbindされる。そのため、イベントハンドラ内の「this」はその描画オブジェクトを指す。
今回はここまで!
この第五回までで
PixiJSの基本の仕組み、
図形、スプライト、文字などの描画、
tickerでのアニメーション、
イベントによるインタラクション処理、
と本当に浅くではありますが、一通り全体を横断してきました。
次回は、これまで触れてこなかったけど基本として押さえておきたい、落穂拾い的な部分を拾っていきます。