ゲームの入力遅延を減らす:タッチからの反応を速くする
遊びをつくる執筆にClaudeを使用しています。

スマートフォンでゲームを操作したとき、画面をタッチしてからキャラクターが動くまでにわずかなズレを感じたことはないでしょうか。そのズレがほんの数十ミリ秒でも、プレイヤーは「もっさりしている」と感じ、ゲームへの没入感が下がります。特にリズムゲームやアクションゲームでは、入力の遅延は致命的な品質問題になります。 入力遅延には、デバイス側の物理的な遅延(タッチパネルのサンプリングレートなど)と、ゲームのコード側で生まれる遅延の二種類があります。前者は作者が完全には制御できませんが、後者はコードの書き方次第で大幅に改善できます。この記事では、HTMLとJavaScriptで作るブラウザゲームにおいて、コード側の入力遅延を減らすための具体的な判断基準と手順を整理します。
この記事の目次
なぜタッチ入力は遅れやすいのか
ブラウザのタッチイベントには、スクロールなどのデフォルト動作を判断するための「待ち」が組み込まれています。ユーザーが縦にスワイプしようとしているのか、それともタップなのかをブラウザが判定するまでの時間がかかり、これが遅延として現れます。この待ち時間は環境によって異なりますが、体感できるレベルになることがあります。
また、clickイベントは歴史的な経緯からタッチ後に約300ミリ秒の遅延を持つ実装がブラウザに残っており、タッチデバイスでclickを使うだけで顕著な遅れが出ることがあります。ゲームのボタン処理にonclickを使っている場合は、これが最初に疑うべき原因です。
まず確認すべき:イベントの種類を正しく選ぶ
最も効果が大きく、すぐ試せる改善は、clickの代わりにtouchstartまたはpointerdownを使うことです。
- touchstart:指が画面に触れた瞬間に発火するため、clickの遅延がなくなります。ただしマウス操作とは別に扱う必要があります。
- pointerdown:タッチ・マウス・スタイラスをまとめて扱えるPointer Events APIのイベントです。一つのリスナーでタッチとマウスの両方に対応できるため、現在のブラウザゲームではpointerdownが扱いやすい選択肢です。
// 改善前
button.addEventListener('click', handleInput);
// 改善後
button.addEventListener('pointerdown', handleInput);pointerdownに変えるだけで、多くの場面で体感できる差が出ます。
passive オプションと preventDefault の使い方
タッチイベントにpreventDefault()を呼ぶと、ブラウザはスクロールやピンチズームなどのデフォルト動作をキャンセルします。これはゲームの誤操作を防ぐのに有効ですが、ブラウザによってはリスナーがpassiveかどうかを確認するためのオーバーヘッドが生じることがあります。
addEventListenerの第三引数に{ passive: false }を明示することで、ブラウザへの意図を明確に伝えられます。逆に、preventDefault()を呼ばないリスナーには{ passive: true }を指定すると、ブラウザがスクロール処理を並行して最適化できます。
ゲーム全体でスクロールを無効化したい場合は、documentまたはcanvas要素に対してCSSでtouch-action: noneを設定する方法が推奨されています。これによりブラウザがスクロール判定の待ちをスキップするため、Pointer Eventsと組み合わせると遅延をさらに抑えられます。
canvas {
touch-action: none;
}ゲームループと入力処理のタイミング
入力を受け取る処理と、画面を更新するゲームループが分離している場合、入力は受け取ったがフレームがまだ来ていないというタイムラグが生じることがあります。requestAnimationFrame(rAF)を使ったゲームループでは、入力状態をフラグや変数に記録しておき、次のフレームで処理する設計が一般的です。
問題が起きやすいのは、入力を受け取ったその場で重い処理(当たり判定の全再計算など)を同期的に実行してしまうケースです。イベントハンドラ内の処理は最小限にし、状態の更新だけ行い、実際の計算はrAFのコールバック内で行うことで、フレームのスタックを防げます。
一方、setTimeoutやsetIntervalでゲームループを回している場合は、タイマーの精度が低くフレームが不規則になりやすいため、rAFへの移行自体を検討してください。rAFはブラウザの描画サイクルと同期するため、フレームタイミングのばらつきが入力遅延の原因になるリスクを減らせます。
判断基準:どこに遅延があるかを特定する手順
改善前に原因を特定しておくと、効果を正確に評価できます。以下の順番で確認してください。
- clickを使っていないか確認する ゲームのボタンやキャンバスのタップ処理がclickまたはmouseupで実装されている場合、pointerdownへの変更を優先します。
- touch-actionが設定されているか確認する CSSで指定されていない場合、ブラウザがスクロールかタップかを判定するために待機している可能性があります。
- イベントハンドラの中で重い処理をしていないか確認する ハンドラ内でDOMを大量に操作したり、重い計算をしたりしている場合は、rAFへ処理を移します。
- ブラウザのDevToolsのPerformanceパネルで計測する タッチイベントの発火からキャンバスへの描画完了までの時間を記録すると、どのフェーズで時間がかかっているかが視覚的に確認できます。
判断の目安として、入力から視覚フィードバックまでの時間が100ミリ秒以内であれば多くのプレイヤーは「即座」と感じ、200ミリ秒を超えると遅延として意識されやすいという一般的な認識があります。リズムゲームやアクションゲームでは50ミリ秒以下を目標にすることが現実的な基準です。
よくある失敗と対処
失敗1:touchstartとclickを両方登録してしまう 両方登録すると、タッチ操作で処理が二重に走ることがあります。pointerdownに統一するか、touchstartを使う場合はpreventDefault()を呼んでclickの発火を止める必要があります。
失敗2:画像やSVGの上でタッチを受け取ろうとして反応しない 画像やSVGは要素によってPointer Eventsが無効になっている場合があります。CSSでpointer-events: autoを明示的に設定することで解決できます。
失敗3:Safariでの挙動が他のブラウザと異なる iOSのSafariはPointer Eventsの実装がAndroidのChromeと細部で異なることがあります。実機でのテストは必ず複数のOSとブラウザで行い、特にiOS Safariを欠かさず確認してください。
失敗4:requestAnimationFrameの中でさらにsetTimeoutを使う rAFのループ内でタイマーを使うと、フレームタイミングが崩れます。時間ベースのアニメーションはrAFのコールバックが受け取るタイムスタンプ引数を使って計算してください。
次の行動
まず自分のゲームのタップ処理を開き、clickを使っている箇所をpointerdownに変え、キャンバスまたはゲームエリアにtouch-action: noneをCSSで追加してみてください。それだけで体感できる改善が得られる場合が多くあります。変更後はブラウザのDevToolsのPerformanceパネルで前後を比較し、どのフェーズで時間が短縮されたかを確認すると、次の改善箇所も見つけやすくなります。改善したゲームはアソバースに投稿してスマートフォンでの操作感を実際のプレイヤーに試してもらうと、数値では見えない体感の差を素直なフィードバックとして受け取れます。
参考資料
- MDN Web Docs「Pointer events」
- MDN Web Docs「Touch events」
- MDN Web Docs「EventTarget.addEventListener()」(passiveオプションの説明)
- MDN Web Docs「touch-action」(CSS プロパティ)
- Google Chrome Developers「requestAnimationFrame」

