コントローラーを接続すると、ボタンを連打したり、スティックを動かしたり、トリガーを引いたりしますが、開発者にはそのどれも見えません。確かにブラウザはそれを認識しますが、コンソールに数値を記録しない限り、それは目に見えません。それがゲームパッド API の頭の痛い問題です。 何年も前から存在しており、実際には非常に強力です。ボタン、スティック、トリガー、動作を読み取ることができます。しかし、ほとんどの人は触れません。なぜ?フィードバックがないからです。開発者ツールにパネルがありません。コントローラーがあなたの考えたとおりに動作しているかどうかを知る明確な方法はありません。盲目で飛んでいるような気分です。 これは私を悩ませたので、Gamepad Cascade Debugger という小さなツールを作成しました。コンソール出力を見つめる代わりに、コントローラーのライブでインタラクティブなビューが得られます。何かを押すと画面上で反応します。また、CSS カスケード レイヤーを使用すると、スタイルが整理された状態に保たれるため、デバッグがより簡単になります。 この投稿では、コントローラーのデバッグがなぜそれほど面倒なのか、CSS がそれをクリーンアップするのにどのように役立つのか、そして独自のプロジェクト用に再利用可能なビジュアル デバッガーを構築する方法を説明します。

たとえすべてをログに記録できたとしても、コンソールにはすぐに判読不能なスパムが発生することになります。たとえば: [0,0,1,0,0,0.5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]

どのボタンが押されたかわかりますか?おそらくですが、目を酷使し、いくつかの入力を見逃した場合に限ります。いいえ、入力の読み取りに関しては、デバッグは簡単ではありません。 問題 3: 構造の欠如 簡単なビジュアライザーをまとめたとしても、スタイルはすぐに乱雑になる可能性があります。デフォルト、アクティブ、およびデバッグの状態は重複する可能性があり、明確な構造がないと CSS は脆弱になり、拡張するのが困難になります。 CSS カスケード レイヤーが役に立ちます。スタイルを優先順位に従って「レイヤー」にグループ化するので、特異性と戦ったり、「なぜ私のデバッグ スタイルが表示されないのか?」と推測したりする必要がなくなります。代わりに、別の懸念を維持します。

ベース: コントローラーの標準的な初期の外観。 アクティブ: 押されたボタンと移動されたスティックをハイライト表示します。 デバッグ: 開発者用のオーバーレイ (数値の読み出し、ガイドなど)。

これに従って CSS でレイヤーを定義すると、次のようになります。 /* 優先度の低いものから高いものまで */ @layer ベース、アクティブ、デバッグ;

@レイヤーベース { /* ... */ }

@layer アクティブ { /* ... */ }

@レイヤーデバッグ { /* ... */ }

各レイヤーは予測どおりにスタックされるため、どのルールが優先されるかが常にわかります。この予測可能性により、デバッグが簡単になるだけでなく、実際に管理しやすくなります。 問題 (目に見えず乱雑な入力) とそのアプローチ (カスケード レイヤーで構築されたビジュアル デバッガー) について説明しました。次に、デバッガーを構築するプロセスを段階的に説明します。 デバッガの概念 非表示の入力を表示する最も簡単な方法は、それを画面上に描画することです。それがこのデバッガの機能です。ボタン、トリガー、ジョイスティックはすべてビジュアル化されます。

A を押す: 円が点灯します。 スティックを軽く動かす: 円がスライドします。 トリガーを半分まで引く: バーが半分まで埋まります。

これで、0 と 1 を見つめるのではなく、実際にコントローラーがライブで反応するのを観察することになります。 もちろん、デフォルト、押された状態、デバッグ情報、場合によっては記録モードなどの状態を積み上げ始めると、CSS は大きくなり、より複雑になり始めます。そこでカスケード レイヤーが役に立ちます。必要最低限の例を次に示します。 @レイヤーベース { .ボタン { 背景: #222; 境界半径: 50%; 幅: 40ピクセル; 高さ: 40ピクセル; } }

@layer アクティブ { .ボタンが押されました { 背景: #0f0; /* 明るい緑色 */ } }

@レイヤーデバッグ { .button::after { 内容: 属性(データ値); フォントサイズ: 12px; 色: #fff; } }

レイヤーの順序は重要です: ベース → アクティブ → デバッグ。

Base はコントローラーを描画します。 active は押された状態を処理します。 オーバーレイでデバッグがスローされます。

このように分割することは、奇妙な特異性戦争を戦っていないことを意味します。各レイヤーにはそれぞれの役割があり、何が勝つかは常にわかります。 構築する まず画面上に何かを表示しましょう。見栄えが良い必要はありません。単に存在するだけで、作業できるものがあれば十分です。

ゲームパッド カスケード デバッガー

A
B
X

デバッガーが非アクティブ

それは文字通りただの箱です。まだ魅力的ではありませんが、後で CSS と JavaScript で取得できるハンドルが得られます。 ここではカスケード レイヤーを使用しています。これは、状態を追加すると内容が整理されるからです。大まかなパスは次のとおりです。

/* =================================== カスケードレイヤーのセットアップ 順序は重要です: ベース → アクティブ → デバッグ =================================== */

/* レイヤーの順序を事前に定義します */ @layer ベース、アクティブ、デバッグ;

/* レイヤー 1: 基本スタイル - デフォルトの外観 */ @レイヤーベース { .ボタン { 背景: #333; 境界半径: 50%; 幅: 70ピクセル; 高さ: 70ピクセル; ディスプレイ: フレックス; コンテンツの位置揃え: 中央; 整列項目: 中央; }

.pause { 幅: 20ピクセル; 高さ: 70ピクセル; 背景: #333; 表示: インラインブロック; } }

/* レイヤー 2: アクティブな状態 - 押されたボタンを処理します */ @layer アクティブ { .button.active { 背景: #0f0; /* 押すと明るい緑色 */ 変換: スケール(1.1); /* ボタンを少し拡大します */ }

.pause.active { 背景: #0f0; 変換: スケール Y(1.1); /* 押すと縦に伸びる */ } }

/* レイヤ 3: デバッグ オーバーレイ - 開発者情報 */ @レイヤーデバッグ { .button::after { 内容: 属性(データ値); /* 数値を表示します */ フォントサイズ: 12px; 色: #fff; } }

このアプローチの利点は、各レイヤーに明確な目的があることです。基本層はアクティブをオーバーライドすることはできません。また、アクティブがデバッグをオーバーライドすることは、特異性に関係なくできません。これにより、通常デバッグ ツールを悩ませる CSS の特異性戦争が排除されます。 いくつかのクラスターが暗い背景に座っているように見えます。正直なところ、それほど悪くはありません。

JavaScriptの追加 JavaScriptの時間です。ここはコントローラーが実際に何かを行う場所です。これを段階的に構築していきます。 ステップ 1: 状態管理を設定する まず、デバッガーの状態を追跡するための変数が必要です。 // =================================== // 状態管理 // ===================================

実行中 = false にします。 // デバッガがアクティブかどうかを追跡します rafId を許可します。 // キャンセル用の requestAnimationFrame ID を格納します

これらの変数は、ゲームパッド入力を継続的に読み取るアニメーション ループを制御します。 ステップ 2: DOM 参照を取得する 次に、更新するすべての HTML 要素への参照を取得します。 // =================================== // DOM 要素の参照 // ===================================

const btnA = document.getElementById("btn-a"); const btnB = document.getElementById("btn-b"); const btnX = document.getElementById("btn-x"); const stop1 = document.getElementById("pause1"); const stop2 = document.getElementById("pause2"); const status = document.getElementById("ステータス");

これらの参照を前もって保存しておくと、DOM に繰り返しクエリを実行するよりも効率的です。 ステップ 3: キーボード フォールバックを追加する 物理コントローラーを使用しないテストでは、キーボードのキーをボタンにマップします。 // =================================== // KEYBOARD FALLBACK (コントローラーを使用しないテスト用) // ===================================

const keyMap = { "a": btnA、 "b": btnB、 "x": btnX、 "p": [pause1, stop2] // 'p' キーは両方の一時停止バーを制御します };

これにより、キーボードのキーを押して UI をテストできるようになります。 ステップ 4: メイン更新ループを作成する ここで魔法が起こります。この関数は継続的に実行され、ゲームパッドの状態を読み取ります。 // =================================== // メインのゲームパッド更新ループ // ===================================

関数 updateGamepad() { // 接続されているすべてのゲームパッドを取得します const ゲームパッド = navigator.getGamepads(); if (!gamepads) が返る場合;

// 最初に接続されたゲームパッドを使用する const gp = ゲームパッド[0];

if (gp) { // 「アクティブ」クラスを切り替えてボタンの状態を更新します btnA.classList.toggle("アクティブ", gp.buttons[0].pressed); btnB.classList.toggle("アクティブ", gp.buttons[1].pressed); btnX.classList.toggle("アクティブ", gp.buttons[2].pressed);

// 一時停止ボタンをハンドルします (ほとんどのコントローラーのボタン インデックス 9) const stopPressed = gp.buttons[9].pressed; 一時停止1.classList.toggle("アクティブ", 一時停止押下); 一時停止2.classList.toggle("アクティブ", 一時停止押下);

// ステータス表示用に現在押されているボタンのリストを作成します = []; を押してみましょう。 gp.buttons.forEach((btn, i) => { if (ボタンが押された場合)pressed.push("ボタン " + i); });

// ボタンが押された場合にステータス テキストを更新します if (pressed.length > 0) { status.textContent = "押されました: " + pressed.join(", "); } }

// デバッガが実行中の場合はループを続行します if (実行中) { rafId = requestAnimationFrame(updateGamepad); } }

classList.toggle() メソッドは、ボタンが押されたかどうかに基づいてアクティブなクラスを追加または削除し、CSS レイヤー スタイルをトリガーします。 ステップ 5: キーボード イベントを処理する これらのイベント リスナーにより、キーボード フォールバックが機能します。 // =================================== // キーボードイベントハンドラ // ===================================

document.addEventListener("keydown", (e) => { if (keyMap[e.key]) { // 単一または複数の要素を処理します if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("active")); } それ以外の場合は { keyMap[e.key].classList.add("アクティブ"); } status.textContent = "キーが押されました: " + e.key.toUpperCase(); } });

document.addEventListener("keyup", (e) => { if (keyMap[e.key]) { // キーが放されるとアクティブ状態が解除されます if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("active")); } それ以外の場合は { keyMap[e.key].classList.remove("active"); } status.textContent = "キーが解放されました: " + e.key.toUpperCase(); } });

ステップ 6: 開始/停止コントロールを追加する 最後に、デバッガーのオンとオフを切り替える方法が必要です。 // =================================== // デバッガのオン/オフを切り替えます // ===================================

document.getElementById("トグル").addEventListener("クリック", () => { 実行中 = !実行中; // 実行状態を反転します

if (実行中) { status.textContent = "デバッガーが実行中..."; updateGamepad(); // 更新ループを開始します } それ以外の場合は { status.textContent = "デバッガーが非アクティブ"; cancelAnimationFrame(rafId); // ループを停止します } });

そうそう、ボタンを押すと光ります。スティックを押すと動きます。それでおしまい。 もう 1 つ、生の値です。ライトではなく数字だけを見たい場合もあります。

この段階では、以下が表示されるはずです。

シンプルなオンスクリーンコントローラー、 ユーザーが操作すると反応するボタン、および 押されたボタンのインデックスを示すオプションのデバッグ読み出し。

これを抽象度を下げるために、リアルタイムで反応する画面上のコントローラーの簡単なデモを次に示します。

ここで、「記録の開始」を押すと、「記録の停止」を押すまでのすべてが記録されます。 2. CSV/JSONへのデータのエクスポート ログを取得したら、それを保存します。

ステップ 1: ダウンロード ヘルパーを作成する まず、ブラウザでのファイルのダウンロードを処理するヘルパー関数が必要です。 // =================================== // ファイルダウンロードヘルパー // ===================================

function downloadFile(ファイル名, コンテンツ, タイプ = "text/plain") { // コンテンツから BLOB を作成します const blob = new Blob([コンテンツ], { type }); const url = URL.createObjectURL(blob);

// 一時的なダウンロード リンクを作成してクリックします const a = document.createElement("a"); a.href = URL; a.download = ファイル名; a.click();

// ダウンロード後にオブジェクト URL をクリーンアップします setTimeout(() => URL.revokeObjectURL(url), 100); }

この関数は、データから BLOB (バイナリ ラージ オブジェクト) を作成し、その一時 URL を生成し、プログラムでダウンロード リンクをクリックすることで機能します。クリーンアップにより、メモリリークが発生しないことが保証されます。 ステップ 2: JSON エクスポートを処理する JSON は完全なデータ構造を保存するのに最適です。

// =================================== // JSON としてエクスポート // ===================================

document.getElementById("export-json").addEventListener("click", () => { // エクスポートするものがあるかどうかを確認する if (!frames.length) { console.warn("エクスポートできる録画がありません。"); 戻る; }

// メタデータとフレームを含むペイロードを作成する const ペイロード = { createdAt: new Date().toISOString(), フレーム };

// フォーマットされた JSON としてダウンロード ダウンロードファイル( "ゲームパッドログ.json", JSON.stringify(ペイロード、null、2)、 「アプリケーション/json」 ); });

JSON 形式ではすべてが構造化され、簡単に解析できるため、開発ツールに再度ロードしたり、チームメイトと共有したりするのに最適です。 ステップ 3: CSV エクスポートを処理する CSV エクスポートの場合、階層データを行と列にフラット化する必要があります。

//================================== // CSV としてエクスポート // ===================================

document.getElementById("export-csv").addEventListener("click", () => { // エクスポートするものがあるかどうかを確認する if (!frames.length) { console.warn("エクスポートできる録画がありません。"); 戻る; }

// CSV ヘッダー行を構築します (タイムスタンプ、すべてのボタン、すべての軸の列) const headerButtons = Frame[0].buttons.map((_, i) => btn${i}); const headerAxes = フレーム[0].axes.map((_, i) => axis${i}); const header = ["t", ...headerButtons, ...headerAxes].join(",") + "\n";

// CSV データ行を構築する const rows = Frames.map(f => { const btnVals = f.buttons.map(b => b.value); return [f.t, ...btnVals, ...f.axes].join(","); }).join("\n");

// CSVとしてダウンロード downloadFile("gamepad-log.csv", ヘッダー + 行, "text/csv"); });

CSV は Excel または Google スプレッドシートで直接開くことができるため、データ分析に最適であり、グラフの作成、データのフィルター処理、パターンの特定を視覚的に行うことができます。 エクスポート ボタンが追加されたので、パネルに [JSON のエクスポート] と [CSV のエクスポート] という 2 つの新しいオプションが表示されます。 JSON は、生のログを開発ツールに戻したり、構造を探索したりする場合に便利です。一方、CSV は Excel または Google スプレッドシートに直接開くため、入力をグラフ化、フィルター処理、または比較できます。次の図は、追加のコントロールを備えたパネルがどのように見えるかを示しています。

3. スナップショットシステム 完全な記録は必要なく、入力状態の簡単な「スクリーンショット」だけが必要な場合もあります。そこで役立つのが「スナップショットの作成」ボタンです。

そしてJavaScript:

// =================================== // スナップショットを撮る // ===================================

document.getElementById("スナップショット").addEventListener("クリック", () => { // 接続されているすべてのゲームパッドを取得します const パッド = navigator.getGamepads(); const activePads = [];

// 接続されている各ゲームパッドの状態をループしてキャプチャします。 for (パッドの const gp) { if (!gp) 続行; // 空のスロットをスキップする

activePads.push({ id: gp.id, // コントローラー名/モデル タイムスタンプ: Performance.now()、 ボタン: gp.buttons.map(b => ({ 押された: b.押された、 値: b.値 }))、 軸: [...gp.axes] }); }

// ゲームパッドが見つかったかどうかを確認します if (!activePads.length) { console.warn("スナップショット用にゲームパッドが接続されていません。"); alert("コントローラーが検出されませんでした!"); 戻る; }

// ログを記録してユーザーに通知する console.log("スナップショット:", activePads); alert(スナップショットが取得されました! ${activePads.length} コントローラーをキャプチャしました。); });

スナップショットは、ある瞬間のコントローラーの正確な状態をフリーズします。 4. ゴースト入力リプレイ さて、楽しい話です。ゴースト入力のリプレイです。これはログを取得し、あたかもファントム プレイヤーがコントローラーを使用しているかのように視覚的に再生します。

再生用の JavaScript: // =================================== // ゴーストリプレイ // ===================================

document.getElementById("再生").addEventListener("クリック", () => { // 再生する記録があることを確認します if (!frames.length) { alert("再生する録画がありません!"); 戻る; }

console.log("ゴーストリプレイを開始しています...");

// 同期再生のトラック タイミング startTime = パフォーマンス.now(); にしましょう。 フレームインデックス = 0 にします。

// アニメーションループを再生する 関数ステップ() { const now = パフォーマンス.now(); const 経過 = 今 - 開始時間;

// これまでに発生するはずだったすべてのフレームを処理します while (frameIndex < フレーム.長さ && フレーム[フレームインデックス].t <= 経過) { const フレーム = フレーム[フレームインデックス];

// 記録されたボタンの状態で UI を更新します btnA.classList.toggle("アクティブ", Frame.buttons[0].pressed); btnB.classList.toggle("アクティブ", Frame.buttons[1].pressed); btnX.classList.toggle("アクティブ", Frame.buttons[2].pressed);

// ステータス表示を更新 = []; を押してみましょう。 Frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push("ボタン " + i); }); if (pressed.length > 0) { status.textContent = "ゴースト: " + pressed.join(", "); }

フレームインデックス++; }

// フレームがさらにある場合はループを続行 if (frameIndex < フレームの長さ) { requestAnimationFrame(ステップ); } それ以外の場合は { console.log("リプレイ終了しました。」); status.textContent = "再生完了"; } }

// リプレイを開始します ステップ(); });

デバッグをもう少し実践的にするために、ゴースト リプレイを追加しました。セッションを記録したら、リプレイをクリックすると、まるでファントム プレーヤーがパッドを実行しているかのように、UI がそれを実行するのを確認できます。このための新しい [ゴーストの再生] ボタンがパネルに表示されます。

「録音」をクリックし、コントローラーを少しいじって、停止してから再生します。 UI は、入力を追いかける幽霊のように、ユーザーが行ったすべてのことをエコーするだけです。 なぜわざわざこれらの追加機能を使うのでしょうか?

記録/エクスポートにより、テスターは何が起こったのかを正確に示すことが簡単になります。 スナップショットは瞬間的にフリーズするため、奇妙なバグを追跡しているときに非常に役立ちます。 ゴースト リプレイは、チュートリアル、アクセシビリティ チェック、または単にコントロール設定を並べて比較する場合に最適です。

この時点で、これは単なるきれいなデモではなく、実際に動作させることができるものになります。 実際の使用例 これで、多くのことができるデバッガが完成しました。ライブ入力を表示し、ログを記録し、エクスポートし、さらには再生することもできます。しかし、本当の問題は、誰が実際に気にするのかということです。これは誰にとって役に立ちますか? ゲーム開発者 コントローラーも仕事の一部ですが、それをデバッグするのでしょうか?通常は痛みを伴います。 ↓ → + パンチなどの格闘ゲームのコンボをテストしていると想像してください。祈る代わりに、同じように 2 回押して、1 回録音し、再生しました。完了しました。または、チームメイトと JSON ログを交換して、マルチプレイヤー コードがチームメイトのマシン上で同じように反応するかどうかを確認します。それはとても大きいことです。 アクセシビリティ実践者 これは私の心に近いものです。誰もが「標準」コントローラーでプレイするわけではありません。アダプティブ コントローラーは時々奇妙な信号をスローします。このツールを使用すると、何が起こっているかを正確に確認できます。教師でも研究者でも誰でも。ログを取得し、比較したり、入力を並べて再生したりできます。目に見えないものが突然明らかになります。 品質保証試験 テスターは通常、「ここのボタンを押したら壊れた」などのメモを書きます。あまり役に立ちません。今?正確なプレスをキャプチャし、ログをエクスポートして送信できます。推測はできません。 教育者 チュートリアルや YouTube 動画を作成している場合、ゴースト リプレイは最適です。文字通り、「これがコントローラーでやったことです」と言うことができ、その様子が UI に表示されます。説明がよりわかりやすくなります。 ゲームを超えて そう、これはゲームに限った話ではありません。人々はロボット、アート プロジェクト、アクセシビリティ インターフェイスにコントローラーを使用してきました。毎回同じ問題です。ブラウザには実際に何が表示されているのでしょうか?これなら、推測する必要はありません。 結論 コントローラー入力のデバッグは、常に盲目的に飛んでいるような気分でした。 DOM や CSS とは異なり、ゲームパッド用の組み込みインスペクターはありません。それはコンソール内の単なる生の数値であり、ノイズの中に簡単に失われてしまいます。 数百行の HTML、CSS、JavaScript を使用して、これまでとは異なるものを構築しました。

目に見えない入力を見えるようにするビジュアル デバッガー。 UI をクリーンでデバッグ可能に保つ、階層化された CSS システム。 デモから開発者ツールに昇格する一連の機能強化 (記録、エクスポート、スナップショット、ゴースト リプレイ)。

このプロジェクトは、Web プラットフォームのパワーと CSS カスケード レイヤーの少しの創造性を組み合わせることでどこまでできるかを示します。 今説明したツール全体はオープンソースです。 GitHub リポジトリのクローンを作成して、自分で試してみることができます。 しかし、もっと重要なのは、それを自分のものにできるということです。独自のレイヤーを追加します。独自の再生ロジックを構築します。それをゲームのプロトタイプと統合します。あるいは、私が想像していなかった方法でそれを使用することさえあります。教育、アクセシビリティ、またはデータ分析用。 結局のところ、これはゲームパッドのデバッグだけではありません。これは、隠れた入力に光を当て、Web がまだ完全には受け入れていないハードウェアを開発者が安心して使えるようにすることです。 したがって、コントローラーを接続し、エディターを開いて実験を開始してください。ブラウザと CSS が本当に実現できることに驚くかもしれません。

You May Also Like

Enjoyed This Article?

Get weekly tips on growing your audience and monetizing your content — straight to your inbox.

No spam. Join 138,000+ creators. Unsubscribe anytime.

Create Your Free Bio Page

Join 138,000+ creators on Seemless.

Get Started Free