【ITニュース解説】A game portal rejected my game with one line - "The WASD keys aren't working". They were right, and all my tests were green.
2026年10月03日に「Dev.to」が公開したITニュース「A game portal rejected my game with one line - "The WASD keys aren't working". They were right, and all my tests were green.」について初心者にもわかりやすく解説しています。
ITニュース概要
ゲームがポータルで拒否された。`preventDefault()`使用により、iframe内でキーボードのフォーカスが取得できず、WASDキーが効かないバグだった。開発環境と異なる本番環境、そしてプレイヤーの行動を想定したテストの重要性を学んだ。
ITニュース解説
あるゲーム開発者が作成したブラウザゲーム「Void Parry」が、ゲームポータルに提出された際に「WASDキーが動作しない」という一文で却下された。開発者の手元の環境や数十もの自動テストでは問題なく動作していたため、この結果は開発者にとって意外なものだった。しかし、ポータルサイト上で確認すると、実際にキーボード入力が機能しないことが判明した。
この問題の根本原因は、ウェブ開発における「フォーカス」と「イベント処理」の相互作用にあった。開発者はゲームのキャンバス要素(ゲーム画面が描画される領域)に対して、マウスのクリックやタッチイベント(pointerdown)が発生した際に実行されるイベントリスナーを設定していた。このリスナーの処理の中にe.preventDefault()というコードが含まれていた。
e.preventDefault()は、イベントが引き起こすブラウザの標準的な動作をキャンセルするための命令である。キャンバスゲームでは、ゲーム画面をクリックした際にテキストが選択されたり、タッチ操作でページ全体がスクロールしたりするのを防ぐ目的で、このe.preventDefault()を呼び出すことは一般的だ。しかし、この命令には「クリックされた要素にキーボードフォーカスを移動する」というブラウザのデフォルト動作もキャンセルしてしまう副作用があった。
開発者の自身のウェブサイトでは、ゲームがページ全体のコンテンツとして直接読み込まれていたため、ドキュメント自体が常にフォーカスを持っていた。そのため、クリックの有無にかかわらずキーボード入力はゲームに問題なく到達していた。しかし、ゲームポータルサイトでは、ゲームは通常「iframe」という別のウェブページを埋め込むためのHTML要素の中に表示される。iframeは、それ自体が独立したウェブページのようなものであり、キーボードイベントを受け取るためには、そのiframe自身がフォーカスを持っている必要がある。通常、ユーザーがiframe内のコンテンツをクリックすると、そのiframeがフォーカスを得る。ところが、開発者のコードに含まれていたe.preventDefault()が、この「クリックによってiframeがフォーカスを得る」という重要な動作をキャンセルしてしまっていたのだ。結果として、マウスの動きやボタン入力(pointerイベント)はゲームに届くものの、キーボード入力(keydownイベント)は永遠に親のポータルページに流れ続けていた。開発ツールでゲームのiframe内でdocument.hasFocus()を実行しても、クリック後も常にfalse(フォーカスがない状態)であることが確認された。
この問題を解決するために、開発者が最初に試みたのは、pointerdownイベント発生時にe.preventDefault()を呼び出した後、明示的にwindow.focus()を呼び出してゲーム自身にフォーカスを与えるという修正だった。この修正を適用したビルドを自身の環境でテストすると、ゲーム内をクリックした後にWASDキーが正常に機能することを確認できたため、自信を持って再提出した。
しかし、数時間後、開発者はレビュー担当者がどのような操作をしたのかを冷静に考え直した。開発者のゲームは、新規プレイヤーがいきなり最初のボス戦に直行するデザインで、マウスは照準合わせにのみ使用され、ゲームを開始するためにクリックする必要がなかった。つまり、レビュー担当者はゲームを開いた後、一度もクリックせずにWASDキーを押してしまい、何も反応がなかったため「キーが動かない」と報告したのだ。最初の修正は、「クリックした後にキー入力をする」という、レビュー担当者が全く通らなかった経路を修復したに過ぎなかった。これは「報告された問題を解決した」という思い込みが、実際には問題の本質を見落としていた典型的な例だった。
真の解決策は、ゲームが「いつ、どのような状況でフォーカスを失う可能性があるか」を網羅的に考慮し、明示的にフォーカスを取り戻すことにあった。最終的に適用された修正は以下の三段階で構成された。
- ゲームがロードされた直後に
window.focus()を呼び出す。 - ゲーム開始時、例えばボス戦が始まる
startFightのような関数内でもwindow.focus()を呼び出す。 - プレイヤーがゲーム画面上にマウスカーソルを移動させた際(
pointermoveイベント)に、もしゲームがフォーカスを持っていない場合はwindow.focus()を呼び出す。 さらに、ゲームポータルで広告が表示される場合、広告表示中にキーボードフォーカスが奪われ、広告が閉じてもゲームに戻ってこないケースも考慮し、広告が閉じた後にもwindow.focus()を呼び出す必要があった。
この経験から、開発者はテストの重要性について深く考えさせられた。開発者の既存の自動テストは、ゲームのindex.htmlファイルを直接ロードする形式だったため、iframe内での動作や、ユーザーがクリックしない状況を全くカバーできていなかった。適切なテストは、ゲームをiframe内に埋め込んだホストページを作成し、さらにそのホストページを別のiframeに埋め込むといった、ポータルサイトの実際の環境を模倣する必要があった。そして、そのテストシナリオには、クリックせずにキー入力を試みるケースを含めるべきだった。具体的には、iframeで埋め込んだテストページ上で、ゲームをクリックせずにWASDキーをプレスし、ゲームのiframe内でキーイベントが捕捉されているかを確認するようなテストが必要だった。
この一連の問題とその解決にかかった時間は、開発者にとって大きなコストだった。ゲームは二つのポータルで却下され、そのうち一つは「全体的な品質」という曖昧な理由だったが、その中にキーボードが動作しないという致命的な問題が含まれていた可能性が高い。レビュー担当者がゲーム内で移動すらできなかったとすれば、そのゲームの「品質」は著しく低いと評価されても仕方ない。
この経験から得られた最も重要な教訓は以下の通りである。
- テストは、ユーザーが実際に製品を利用する方法をシミュレートするべきだ。もしゲームがiframe内で提供されるならiframe内でテストし、新しいプレイヤーがクリックしないなら、テストもクリックするべきではない。
preventDefault()は、イベントのデフォルト動作だけでなく、要素へのフォーカスの移動もキャンセルする可能性がある。この点を理解し、もしキーボード入力が必要な場合は、明示的にwindow.focus()を呼び出してフォーカスを取得する必要がある。- 「修正した」とは、報告者が辿った正確な問題経路が解消されたことを意味する。似たような経路がパスするだけでは不十分であり、問題を報告した人の行動を正確に再現し、それが解決されたことを確認する必要がある。
- 自動テストは、開発者の盲点を共有することがある。自動テストがすべてグリーンであっても、実際の環境での手動テストは、自動テストでは見つけられない問題を発見する上で非常に価値がある。わずか一分の手動操作で、数十の自動テストが見抜けなかった致命的なバグが明らかになることもある。
これらの教訓は、システムエンジニアを目指す初心者にとって、デバッグ、テスト、そしてユーザー体験の重要性を理解する上で非常に役立つだろう。コードを書くだけでなく、それがどのような環境で、どのように利用されるかを深く洞察することが、高品質なソフトウェア開発には不可欠である。