メインコンテンツまでスキップ

入力

エレメントは、アプリケーションのElementInputを通じて、マウス、タッチ、XRコントローラーに反応します。ElementInputはブラウザの入力イベントをリッスンし、ポインターの下にあるエレメントを特定して、そのエレメントのコンポーネントでイベントを発生させます。このページでは、インタラクティブなすべてのエレメントに共通するイベントを扱います。ボタンはこれらのイベントにホバーと押下の状態を加え、スクロールビューはこれらのイベントを使ってドラッグを行います。

UI入力の有効化​

エレメントが入力を受け取るには、2つの条件を満たす必要があります。アプリケーションがElementInputを持っていること(セットアップを参照)と、エレメントで入力が有効になっていることです。入力が有効でないエレメントがヒットすることはないので、ユーザーが操作するエレメントで入力を有効にし、装飾やボタンのラベルなど、それ以外のエレメントではオフのままにしておきます。そうすれば、ラベルのクリックはその下にあるボタンに届きます。

card.element.useInput = true;

addComponent('element', ...)にuseInput: trueを渡すこともできます。ElementInputのないアプリケーションでエレメントの入力を有効にすると、エンジンのデバッグビルドは警告をログに出力します。

作成される入力デバイスの組み合わせは環境ごとに異なり、UIとゲームの入力が交わる場面では、この違いが重要になります。

環境入力デバイスエレメント入力
エンジンAppOptionsに渡したものマウスとタッチのデバイスより前に、自分で作成します
エディターINPUT設定で有効にしたマウス、タッチ、キーボード、ゲームパッドのデバイス常にあり、他のデバイスより前に作成されます
Reactapp.mouseとapp.touch。キーボードデバイスはありません常にあり、他のデバイスより前に作成されます
Web Componentsapp.mouseとapp.keyboard。タッチデバイスはありませんが、エレメント入力が自らタッチを処理します常にあり、他のデバイスより前に作成されます

入力イベント​

Elementコンポーネントでは、次のイベントが発生します。

イベント発生するタイミング
mouseenterポインターがエレメントの上に入ったとき
mouseleaveポインターがエレメントの上から出たとき
mousemoveポインターがエレメントの上で動いたとき。エレメント上でボタンが押された後は、ボタンが離されるまで、エレメントがすべての移動を受け取ります
mousedownエレメントの上でマウスボタンが押されたとき
mouseupエレメントの上でマウスボタンが離されたとき。または、エレメント上で押されたボタンがどこかで離されたとき
mousewheelエレメントの上でマウスホイールが回されたとき
clickマウスボタンまたはタッチが、同じエレメントの上で押されて離されたとき
touchstartエレメント上でタッチが始まったとき
touchmoveエレメント上で始まったタッチが移動したとき。移動先がどこでも発生します
touchleaveエレメント上で始まったタッチがエレメントの外に出たとき。タッチごとに1回発生します
touchendエレメント上で始まったタッチが終わったとき。終わった場所がどこでも発生します
touchcancelエレメント上で始まったタッチがブラウザによってキャンセルされたとき
selectstart, selectend, selectmove, selectenter, selectleaveXRのコントローラーや手がエレメントを指したり、セレクトしたりしたとき。XRのUIを参照してください

イベントのリッスン​

イベントはElementコンポーネントでリッスンします。そのためのコードを実行する場所は、環境ごとに異なります。

card.element.on('mouseenter', () => {
card.element.opacity = 1;
});
card.element.on('mouseleave', () => {
card.element.opacity = 0.6;
});

onはEventHandleを返します。リッスンをやめるには、そのoff()メソッドを呼び出します。

すべてのハンドラーはイベントオブジェクトを受け取ります。event.elementはイベントが発生したエレメントで、ハンドラーがその祖先のエレメントに登録されている場合も同じです。event.eventは、そのイベントの元になったブラウザのイベントです。

イベントオブジェクト対象のイベントプロパティ
ElementMouseEventmouse*と、マウスによるclickxとy(キャンバスの左上からのポインターの位置、CSSピクセル単位)、dxとdy(前回のイベントからの移動量)、button、wheelDelta(-1、0、1のいずれか)、ctrlKey、altKey、shiftKey、metaKey
ElementTouchEventtouch*と、タッチによるclickこのタッチのxとy、touch(ブラウザのタッチ)、touchesとchangedTouches(ブラウザのタッチイベントと同じもの)
ElementSelectEventselect*と、XRのセレクトによるclickinputSource(コントローラーまたは手)

3つとも、element、camera(エレメントがヒットしたときのカメラ)、eventも持っています。

イベントのバブリング​

イベントはまずヒットしたエレメントで発生し、次にその親エレメント、というようにヒエラルキーを上へたどり、エレメントを持たないエンティティに達するまで続きます。祖先のエレメントは入力が有効かどうかに関係なくイベントを受け取るので、メニューに1つリスナーを登録するだけで、そのすべての項目のクリックを処理できます。

menu.element.on('click', (event) => {
console.log(`${event.element.entity.name} was clicked`);
});

イベントをそれより上に伝えないようにするには、event.stopPropagation()を呼び出します。

UIの入力がゲームに届かないようにする​

例えばプレイヤーがクリックしたときに弾を撃つなど、マウスやタッチのデバイスを直接読み取るゲームコードは、UIの上で行われたクリックも検知してしまいます。これもstopPropagation()で対処できます。このメソッドはバブリングを止めるだけでなく、ブラウザのイベントそのものも止めるので、ElementInputのリスナーの後に実行されるはずだったブラウザのリスナーには、そのイベントが届きません。マウスとタッチのデバイスは同じブラウザのイベントをリッスンしているため、ElementInputがそれらより前に作成されていれば、これによってHUD上での押下がapp.mouseに届かなくなり、app.mouse.wasPressed()にも反映されません。

// HUD内の入力が有効なエレメントでの押下は、app.mouseにもapp.touchにも届かない
hud.element.on('mousedown', event => event.stopPropagation());
hud.element.on('touchstart', event => event.stopPropagation());

HUD自体のグループエレメントでは、入力をオフのままにしておきます。子のイベントはいずれにせよバブリングで届きますし、入力をオンにすると、その矩形全体でゲームへの押下が遮られてしまいます。

エディター、React、Web Componentsは、ElementInputを最初に作成します。エンジンのアプリケーションでは、セットアップで示しているように、マウスとタッチのデバイスより前に作成してください。

Input Events

どのエレメントがイベントを受け取るか​

エレメントが重なっているとき、イベントを受け取るのは1つだけです。ElementInputは次の順序でエレメントをテストし、最初にヒットした時点で止まります。

  1. カメラを上から順に。 カメラは最後に描画されるものから最初のものへと順に試されるので、上に描画されるUIが優先されます。エレメントは、そのエレメントのレイヤーのいずれかをレンダリングするカメラを通してのみテストされます。
  2. レイヤーを上から順に。 後に描画されるレイヤー上のエレメントが先に試されます。これが関係するのは、インターフェースで複数のレイヤーを使う場合だけです。
  3. スクリーン空間のエレメントを最初に、次にワールド空間のスクリーン上のエレメント、その次にスクリーンのないエレメントを試します。
  4. 上に描画されるエレメントを最初に。 それぞれのグループの中では、描画順の値が最も大きいエレメントが最初に試されます。これは通常、ヒエラルキーで最も下にあるエレメントです。描画順とパフォーマンスを参照してください。

スクリーン上のエレメントがヒットすると、ほかにヒットするものよりカメラから遠い場合でも、その時点で探索は終わります。ワールド空間のスクリーンが重なっている場合は、優先度の高いスクリーンが優先されます。距離で比較されるのはスクリーンのないエレメントだけで、最も近いものが優先されます。

テストされる領域はエレメントの矩形であり、画像の見えているピクセルやグリフの形ではありません。ボタンのヒットパディングはこの領域を広げ、マスクはこの領域をクリップします。無効化されたエンティティはスキップされます。

クリックとドラッグ​

clickは、マウスボタンが離されるか、タッチが終わったときに、それが押されたのと同じエレメントの上であれば発生します。離す前にエレメントの外に出て戻ってきた場合も、クリックになります。

エレメント上でマウスボタンが押されると、ポインターがどこへ移動しても、そのエレメントがすべてのmousemoveとmouseupを受け取ります。タッチも同様で、touchmoveとtouchendはタッチが始まったエレメントに送られます。これにより、ポインターが外に出ても、スライダーやスクロールビューはドラッグを続けられます。

タッチスクリーン​

タップの後、ブラウザはマウスしか扱わないページのために、エミュレートしたマウスイベントをページに送ります。ElementInputは、同じエレメントでのタッチによるクリックに続く、エミュレートされたclickを無視しますが、それ以外のエミュレートされたイベントは届きます。タップされたエレメントは、mouseenter、mousedown、mouseupも受け取ります。また、ダイアログを閉じるボタンのように、タップされたエレメントがそのタップで非表示になると、エミュレートされたクリックはその後ろにあったものに当たります。ブラウザにマウスイベントをエミュレートさせないようにするには、キャンバスのtouchendイベントをキャンセルします。これはどの環境でも使えます。

app.graphicsDevice.canvas.addEventListener('touchend', (event) => {
event.preventDefault();
});

また、ElementInputはキャンバス上のすべてのtouchmoveをキャンセルするので、キャンバス上で始まったタッチでページがスクロールすることはありません。キャンバスが長いページの一部である場合は、この点に注意してください。

ポインターロック​

一人称視点のゲームのようにポインターがロックされている間、ElementInputはマウスの押下を無視します。メニューを表示する前にapp.mouse.disablePointerLock()でロックを解除し、メニューが閉じたら再びロックしてください。

UI入力の無効化​

  • 特定のエレメントが入力を受け取らないようにするには、useInputをオフにします。無効化されたエンティティが入力を受け取ることはありません。
  • ボタンを表示したまま反応しないようにするには、activeプロパティをfalseに設定します。ボタンを参照してください。
  • メニューがアニメーションで消えていく間など、すべてのUI入力を一時停止するには、app.elementInput.enabledをfalseに設定します。このプロパティはまだAPIリファレンスに記載されていません。

関連情報​