當你插入控制器時,你會按下按鈕、移動操縱桿、扣動板機…而身為開發人員,你看不到這些。當然,瀏覽器會識別它,但除非您在控制台中記錄數字,否則它是不可見的。這就是 Gamepad API 令人頭痛的地方。 它已經存在很多年了,而且實際上非常強大。你可以閱讀按鈕、操縱桿、觸發器和作品。但大多數人都不會碰它。為什麼?因為沒有回饋。開發者工具中沒有面板。沒有明確的方法可以知道控制器是否按照您的想法進行操作。感覺就像盲目飛行。 這讓我很煩惱,因此我建立了一個小工具:Gamepad Cascade Debugger。您無需盯著控制台輸出,而是可以看到控制器的即時互動視圖。按下某個東西,它就會在螢幕上做出反應。透過 CSS Cascade Layers,樣式保持井井有條,因此調試起來更加乾淨。 在這篇文章中,我將向您展示為什麼偵錯控制器如此痛苦,CSS 如何幫助清理它,以及如何為您自己的專案建立可重複使用的視覺化偵錯器。

即使您能夠將它們全部記錄下來,您很快也會收到無法讀取的控制台垃圾郵件。例如: [0,0,1,0,0,0.5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]

看得出來按下的是哪個按鈕嗎?也許吧,但前提是你睜大眼睛並錯過了一些輸入。所以,不,在讀取輸入時調試並不容易。 問題三:缺乏結構 即使您組合了一個快速的視覺化工具,樣式也會很快變得混亂。預設、活動和偵錯狀態可能會重疊,如果沒有清晰的結構,您的 CSS 就會變得脆弱且難以擴展。 CSS 級聯層可以提供協助。他們將樣式分組為按優先級排序的“層”,因此您無需再與特異性作鬥爭並猜測“為什麼我的調試樣式沒有顯示?”相反,您維護單獨的關注點:

底座:控制器的標準初始外觀。 活動:突出顯示按下的按鈕和移動的操縱桿。 調試:為開發人員提供的疊加層(例如數位讀數、指南等)。

如果我們根據這個在 CSS 中定義圖層,我們會有: /* 優先權從低到高 */ @層基礎,活動,調試;

@層基礎{ /* ... */ }

@層活躍{ /* ... */ }

@層調試{ /* ... */ }

由於每一層的堆疊都是可預測的,因此您始終知道哪些規則獲勝。這種可預測性不僅使調試變得更加容易,而且實際上易於管理。 我們已經討論了問題(不可見、混亂的輸入)和方法(使用級聯層構建的可視化調試器)。現在我們將逐步完成建置偵錯器的過程。 偵錯工具概念 使隱藏的輸入可見的最簡單方法是將其繪製在螢幕上。這就是這個調試器的作用。按鈕、觸發器和操縱桿都具有視覺效果。

按 A:一個圓圈亮起。 輕推棍子:圓圈會滑動。 半扣下板機:條形填滿一半。

現在您不再盯著 0 和 1,而是實際觀看控制器的即時反應。 當然,一旦你開始堆積預設、按下、偵錯訊息,甚至記錄模式等狀態,CSS 就會開始變得更大、更複雜。這就是級聯層派上用場的地方。這是一個精簡的範例: @層基礎{ .按鈕{ 背景:#222; 邊界半徑:50%; 寬度:40px; 高度:40px; } }

@層活躍{ .button.pressed { 背景:#0f0; /* 亮綠色 */ } }

@層調試{ .button::之後{ 內容:attr(數據值); 字體大小:12px; 顏色:#fff; } }

層順序很重要:基礎→活動→調試。

底座繪製控制器。 活動處理按下狀態。 調試會拋出覆蓋層。

像這樣分解它意味著你不會打奇怪的特異性戰爭。每一層都有它的位置,你總是知道什麼會贏。 建構它 讓我們先在螢幕上顯示一些內容。它不需要看起來很好——只需要存在,這樣我們就有了可以使用的東西。

遊戲手把級聯調試器

A
B
X

偵錯器不活動

這實際上只是盒子。雖然還不令人興奮,但它為我們提供了稍後使用 CSS 和 JavaScript 取得的句柄。 好的,我在這裡使用級聯層,因為一旦添加更多狀態,它就能使內容保持井井有條。這是一個粗略的過程:

/* ======================================= 級聯層設定 順序很重要:基礎 → 活動 → 調試 ===================================== */

/* 預先定義圖層順序 */ @層基礎,活動,調試;

/* 第 1 層:基本樣式 - 預設外觀 */ @層基礎{ .按鈕{ 背景:#333; 邊界半徑:50%; 寬度:70 像素; 高度:70 像素; 顯示:柔性; 調整內容:居中; 對齊項目:居中; }

.暫停{ 寬度:20px; 高度:70 像素; 背景:#333; 顯示:內聯塊; } }

/* 第 2 層:活動狀態 - 處理按下的按鈕 */ @層活躍{ .button.active { 背景:#0f0; /* 按下時呈現亮綠色 */ 變換:縮放(1.1); /* 稍微放大按鈕 */ }

.pause.active { 背景:#0f0; 變換:scaleY(1.1); /* 按下時垂直拉伸 */ } }

/* 第 3 層:偵錯覆蓋 - 開發人員資訊 */ @層調試{ .button::之後{ 內容:attr(數據值); /* 顯示數值 */ 字體大小:12px; 顏色:#fff; } }

這種方法的優點在於每一層都有明確的目的。無論特殊性如何,基礎層永遠不能覆蓋 active,並且 active 永遠不能覆蓋 debug。這消除了通常困擾調試工具的 CSS 特異性之爭。 現在看起來有些簇位於黑暗的背景上。老實說,還不錯。

新增 JavaScript JavaScript 時間。這是控制器實際做某事的地方。我們將逐步建立這個。 第 1 步:設定狀態管理 首先,我們需要變數來追蹤調試器的狀態: // ======================================= // 狀態管理 // =======================================

讓運行= false; // 追蹤偵錯器是否處於活動狀態 讓拉菲爾德; // 儲存用於取消的 requestAnimationFrame ID

這些變數控制連續讀取遊戲手把輸入的動畫循環。 第 2 步:取得 DOM 引用 接下來,我們取得要更新的所有 HTML 元素的參考: // ======================================= // DOM 元素引用 // =======================================

const btnA = document.getElementById("btn-a"); const btnB = document.getElementById("btn-b"); const btnX = document.getElementById("btn-x"); constpause1 = document.getElementById("pause1"); constpause2 = document.getElementById("pause2"); const status = document.getElementById("status");

預先儲存這些參考比重複查詢 DOM 更有效。 第 3 步:新增鍵盤後備 為了在沒有實體控制器的情況下進行測試,我們將鍵盤按鍵映射到按鈕: // ======================================= // 鍵盤回退(用於沒有控制器的測試) // =======================================

常量鍵映射 = { “a”:btnA, “b”:btnB, “x”:btnX, "p": [pause1,pause2] // 'p'鍵控制兩個暫停條 };

這讓我們可以透過按下鍵盤上的按鍵來測試 UI。 第 4 步:建立主更新循環 這就是奇蹟發生的地方。此函數連續運行並讀取遊戲手把狀態: // ======================================= // 主遊戲手把更新循環 // =======================================

函數更新遊戲手把(){ // 取得所有連接的遊戲手柄 const gamepads = navigator.getGamepads(); if (!gamepads) 返回;

// 使用第一個連接的遊戲手柄 const gp = 遊戲手把[0];

如果(GP){ // 透過切換「活動」類別來更新按鈕狀態 btnA.classList.toggle("活動", gp.buttons[0].pressed); btnB.classList.toggle("活動", gp.buttons[1].pressed); btnX.classList.toggle("active", gp.buttons[2].pressed);

// 處理暫停按鈕(大多數控制器上的按鈕索引為 9) constpausePressed = gp.buttons[9].pressed; 暫停1.classList.toggle(“活動”,pausePressed); 暫停2.classList.toggle(“活動”,pausePressed);

// 建立目前按下的按鈕清單以用於狀態顯示 讓按下= []; gp.buttons.forEach((btn, i) => { if (btn.pressed)Pressed.push("按鈕" + i); });

// 如果按下任何按鈕則更新狀態文本 if (pressed.length > 0) { status.textContent = "按下:" + Pressed.join(", "); } }

// 如果偵錯器正在運行則繼續循環 如果(運行){ 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("active"); } 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("toggle").addEventListener("點擊", () => { 運行=!運轉; // 翻轉運轉狀態

如果(運行){ status.textContent = "偵錯器正在運作..."; 更新遊戲手把(); // 啟動更新循環 } 否則{ status.textContent = "偵錯器處於非活動狀態"; 取消動畫影格(rafId); // 停止循環 } });

所以是的,按下一個按鈕,它就會發光。推動棍子,它就會移動。就是這樣。 還有一件事:原始值。有時您只想看到數字,而不是燈光。

在此階段,您應該會看到:

一個簡單的螢幕控制器, 當您與按鈕互動時會做出反應的按鈕,以及 顯示按下按鈕索引的可選調試讀數。

為了讓這個不那麼抽象,這裡有一個螢幕控制器即時反應的快速演示:

現在,按「開始錄製」會記錄所有內容,直到按一下「停止錄製」為止。 2. 將資料匯出為 CSV/JSON 一旦我們有了日誌,我們就會想要保存它。

第 1 步:建立下載助手 首先,我們需要一個幫助函數來處理瀏覽器中的文件下載: // ======================================= // 檔案下載助手 // =======================================

函數 downloadFile(檔案名稱, 內容, 類型 = "text/plain") { // 根據內容建立一個 blob const blob = new Blob([內容], { 型態 }); const url = URL.createObjectURL(blob);

// 建立一個臨時下載連結並點擊它 const a = document.createElement("a"); a.href = 網址; a.下載=檔名; a.點擊();

// 下載後清理物件URL setTimeout(() => URL.revokeObjectURL(url), 100); }

此函數的工作原理是根據資料建立 Blob(二進位大物件),為其產生臨時 URL,然後以程式設計方式按一下下載連結。清理確保我們不會洩漏記憶體。 第 2 步:處理 JSON 導出 JSON 非常適合保留完整的資料結構:

// ======================================= // 匯出為 JSON // =======================================

document.getElementById("export-json").addEventListener("click", () => { // 檢查是否有需要匯出的內容 if (!frames.length) { console.warn("沒有可導出的錄音。"); 返回; }

// 使用元資料和幀建立有效負載 常數負載 = { 建立時間:new Date().toISOString(), 框架 };

// 下載為 JSON 格式 下載檔案( “遊戲手把-log.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 =frames[0].buttons.map((_, i) => btn${i}); const headerAxes =frames[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); 返回 [f.t, ...btnVals, ...f.axes].join(","); }).join("\n");

// 下載為 CSV downloadFile("gamepad-log.csv", header + rows, "text/csv"); });

CSV 非常適合資料分析,因為它可以直接在 Excel 或 Google Sheets 中打開,讓您可以直觀地建立圖表、篩選資料或發現模式。 現在匯出按鈕已出現,您將在面板上看到兩個新選項:匯出 JSON 和匯出 CSV。如果您想將原始日誌返回到您的開發工具或探索結構,那麼 JSON 是很好的選擇。另一方面,CSV 可以直接開啟 Excel 或 Google Sheets,以便您可以繪製圖表、篩選或比較輸入。下圖顯示了具有這些額外控制的面板的外觀。

3. 快照系統 有時您不需要完整的記錄,只需要輸入狀態的快速「螢幕截圖」。這就是「拍攝快照」按鈕的用處。

和 JavaScript:

// ======================================= // 拍攝快照 // =======================================

document.getElementById("快照").addEventListener("點擊", () => { // 取得所有連接的遊戲手柄 const pads = navigator.getGamepads(); 常數 activePads = [];

// 循環並捕捉每個連接的遊戲手把的狀態 for (const gp of pads) { 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); 警報(已拍攝快照!捕獲 ${activePads.length} 控制器。); });

快照會凍結控制器在某一時刻的確切狀態。 4. 幽靈輸入重播 現在有趣的是:幽靈輸入重播。這需要一個日誌並以視覺方式回放它,就像幻影玩家正在使用控制器一樣。

用於重播的 JavaScript: // ======================================= // 幽靈重播 // =======================================

document.getElementById("重播").addEventListener("點擊", () => { // 確保我們有錄音可以重播 if (!frames.length) { Alert("沒有錄音可重播!"); 返回; }

console.log("正在開始幽靈重播...");

// 追蹤同步播放的時間 讓 startTime = Performance.now(); 讓幀索引 = 0;

// 重播動畫循環 函數步驟() { const now = Performance.now(); const elapsed = 現在 - 開始時間;

// 處理現在應該發生的所有幀 while (frameIndex

// 使用記錄的按鈕狀態更新 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 < 幀長度) { 請求動畫幀(步驟); } 否則{ console.log("重播完成了。 ”); status.textContent = "重播完成"; } }

// 開始重播 步驟(); });

為了使調試更加實際,我添加了幽靈重播。錄製完會話後,您可以點擊重播並觀看 UI 表演,就像幻影玩家正在運行鍵盤一樣。為此,面板中會顯示一個新的「重播幽靈」按鈕。

點擊“錄製”,稍微調整一下控制器,停止,然後重播。使用者介面只會回顯您所做的一切,就像幽靈跟隨您的輸入一樣。 為什麼要費心這些額外的事情呢?

記錄/匯出使測試人員可以輕鬆準確顯示發生的情況。 快照會凍結某個時刻,當您追蹤奇怪的錯誤時非常有用。 幽靈重播非常適合教程、可訪問性檢查或只是並排比較控制設定。

現在,它不再只是一個簡潔的演示,而是您可以真正投入使用的東西。 現實世界的用例 現在我們有了這個可以做很多事情的調試器。它顯示即時輸入、記錄日誌、匯出日誌,甚至重播內容。但真正的問題是:誰真正關心?這對誰有用? 遊戲開發商 控制器是工作的一部分,但是調試它們呢?通常是一種疼痛。想像一下,您正在測試格鬥遊戲組合,例如 ↓ → + 拳擊。你不用祈禱,而是以同樣的方式按兩次,記錄一次,然後重播。完成。或者,您與隊友交換 JSON 日誌,以檢查您的多人遊戲程式碼在他們的電腦上是否有相同的反應。那是巨大的。 無障礙從業者 這很貼近我的心。並非每個人都使用“標準”控制器進行遊戲。自適應控制器有時會發出奇怪的訊號。使用此工具,您可以準確地看到正在發生的情況。教師、研究人員,無論是誰。他們可以抓取日誌,進行比較,或並排重播輸入。突然間,看不見的東西變得顯而易見。 品質保證測試 測試人員通常會寫下諸如“我在這裡搗碎了按鈕,它壞了”之類的註釋。不是很有幫助。現在?他們可以捕獲準確的按下情況、匯出日誌並將其發送出去。無需猜測。 教育工作者 如果您正在製作教程或 YouTube 視頻,幽靈重播就是黃金。你可以從字面上說,“這就是我對控制器所做的事情”,而 UI 則顯示它正在發生。使解釋更加清晰。 超越遊戲 是的,這不僅僅是遊戲。人們已經將控制器用於機器人、藝術專案和無障礙介面。每次都會遇到同樣的問題:瀏覽器實際看到的是什麼?有了這個,你就不必猜測了。 結論 調試控制器輸入總是感覺就像盲目飛行。與 DOM 或 CSS 不同,遊戲手把沒有內建檢查器;它只是控制台中的原始數字,很容易在噪音中丟失。 我們用幾百行 HTML、CSS 和 JavaScript 建立了一些不同的東西:

可視化偵錯器,使不可見的輸入變得可見。 分層 CSS 系統,保持 UI 乾淨且可偵錯。 一組增強功能(記錄、匯出、快照、重播)將其從演示提升為開發人員工具。

該專案展示了透過將 Web 平台的強大功能與 CSS 級聯層中的一點創造力相結合,您可以走多遠。 我剛剛完整解釋的工具是開源的。您可以克隆 GitHub 儲存庫並親自嘗試。 但更重要的是,您可以將其變成自己的。新增您自己的圖層。建立您自己的重播邏輯。將其與您的遊戲原型整合。或甚至以我想像不到的方式使用它。用於教學、可訪問性或數據分析。 歸根結底,這不僅僅是調試遊戲手把。它旨在揭示隱藏的輸入,並讓開發人員有信心使用網路尚未完全接受的硬體。 因此,插入控制器,打開編輯器,然後開始試驗。您可能會對您的瀏覽器和 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