Three.js Cannon.es 調査資料 - 複数キー押下時のイベントリスナー
この記事のスナップショット
(スナップショットは特にありません..)
関連するソースは以前の記事「ギア(MT)の導入/トルク曲線(エンジン性能曲線)にそった挙動」のものになります。
ソース
概要
キー押下時のイベント処理を誤解していました。
キーを押しっぱなしで、さらに他のキーをプッシュ・リリースしたときに、押しっぱなしにしているキーの挙動を正しく実施します。
やったこと
今回の車のアクセルとハンドル操作がちょうどそうなのですが、アクセルボタン(矢印上)を押したまま、ハンドル操作(矢印右や矢印左)を押すことがよくあります。
今までの(一見正しい)コードでは、キーイベントが発生したときに、「その場で」そのアクションに応じた挙動を紐づけていました。
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'ArrowUp':
vehicle.keydown_ArrowUp(); // アクセル操作
break
case 'ArrowDown':
vehicle.keydown_ArrowDown(); // バック操作
break
case 'ArrowLeft':
vehicle.keydown_ArrowLeft(); // ハンドルを左にきる
break
case 'ArrowRight':
vehicle.keydown_ArrowRight(); // ハンドルを右にきる
break
...
}
この場合に上記のようなキー操作をすると、押しっぱなしにしていたキー操作(アクセル操作)が呼ばれなくなります。こちらとしては、(他のボタンを割り込みで押したとしても)いま加速を押しているのだから、アクセル操作の関数を読んで欲しいのですがそうはならず。
一応、加速ボタンを一旦releaseして、再度pushすると再びアクセル操作が呼ばれます。
[ユーザ操作] [イベントリスナー]
| (加速ボタンpush) |
|]-------------------->|
|] | (アクセル操作)
|] |------------------->
|] | (アクセル操作) .. ここは押しっぱなしで何度も呼ばれるのに
|] |------------------->
|](ハンドル左push) |
|]]------------------->| (ハンドル左操作)
|]] |------------------->
|]] | (ハンドル左操作)
|]] |------------------->
|]](ハンドル左release) |
|]]------------------->| (ハンドル戻す操作)
|] |------------------->
|] |
|] | (アクセル操作)が呼ばれない!!
|] |- - - - - - - - - ->
|] |
|](加速ボタンrelease) |
|]-------------------->| (アクセルを戻す操作)
| |------------------->
| |
| (加速ボタンpush) |
|]-------------------->|
|] | (アクセル操作)
|] |------------------->
|] | (アクセル操作)
|] |------------------->
イベントリスナーはどうやら、このような挙動を示す(スタック機能は期待できない)仕様のようです。
物理モデルの内部状態に依存せずに定数で変更する場合は、今ままでのコードでも問題なかったのですが、前回の記事「ギア(MT)の導入/トルク曲線(エンジン性能曲線)にそった挙動」のときのように、「ボタンを押下している間は常に関数を呼び出したい場合」には今までと違う実装が必要になります。
具体的には、イベントリスナ―ではキー押下のフラグを取得しておいて、物理モデル更新・レンダリング時に「押下フラグに応じた挙動」を施す必要があります。
今回はイベントリスナーのところでフラグを設定しておいて、..
var keyEvnt = {
forwards:false,
backwards:false,
left:false,
right:false,
brake:false,
sidebrake:false
};
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'ArrowUp':
keyEvnt.forwards = true; // アクセル操作のフラグ
break
case 'ArrowDown':
keyEvnt.backwards = true; // バック操作のフラグ
break
case 'ArrowLeft':
keyEvnt.left = true; // ハンドルを左にきるフラグ
break
case 'ArrowRight':
keyEvnt.right = true; // ハンドルを右にきるフラグ
break
...
}
物理モデル更新・レンダリング時に「押下フラグに応じた挙動(関数)」を呼び出します。
function animate() {
// 車関連(押しっぱなし/同時押しの key-event処理)
{
if (keyEvnt.forwards) {
vehicle.keydown_ArrowUp(); // アクセル操作
} else if (keyEvnt.backwards) {
vehicle.keydown_ArrowDown(); // バック操作
} else {
vehicle.keyup_ArrowUp(); // アクセル・バックを戻す操作
}
if (keyEvnt.left) {
vehicle.keydown_ArrowLeft(); // ハンドルを左にきる
} else if (keyEvnt.right) {
vehicle.keydown_ArrowRight(); // ハンドルを右にきる
} else {
vehicle.keyup_ArrowLeft(); // ハンドルを戻す
}
}
// 物理エンジンの系を進める
world.step(timeStep)
...
感想
サンプルのような簡素な例だとなかなか遭遇しない不具合でした。
以前にも同じようなコードを見かけて「なんでこのような面倒なことを..」と思ってましたが、奥が深いです。
Discussion