Quan connecteu un controlador, feu un puré de botons, moveu els pals, premeu els disparadors... i com a desenvolupador, no en veieu res. El navegador ho recull, és clar, però tret que registreu números a la consola, és invisible. Aquest és el mal de cap amb l'API Gamepad. Fa anys que existeix i, de fet, és força potent. Podeu llegir botons, pals, disparadors, les obres. Però la majoria de la gent no el toca. Per què? Perquè no hi ha comentaris. No hi ha cap panell a les eines per a desenvolupadors. No hi ha una manera clara de saber si el controlador està fent el que penses. Té la sensació de volar a cegues. Això em va molestar prou com per crear una petita eina: Gamepad Cascade Debugger. En lloc de mirar la sortida de la consola, obteniu una visió en directe i interactiva del controlador. Premeu alguna cosa i reacciona a la pantalla. I amb CSS Cascade Layers, els estils es mantenen organitzats, de manera que és més net depurar-los. En aquesta publicació, us mostraré per què la depuració dels controladors és tan difícil, com CSS ajuda a netejar-lo i com podeu crear un depurador visual reutilitzable per als vostres propis projectes.

Fins i tot si podeu registrar-los tots, ràpidament acabareu amb correu brossa il·legible de la consola. Per exemple: [0,0,1,0,0,0,5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]

Pots dir quin botó es va prémer? Potser, però només després d'esforçar els ulls i perdre algunes entrades. Per tant, no, la depuració no és fàcil quan es tracta de llegir les entrades. Problema 3: Falta d'estructura Fins i tot si ajunteu un visualitzador ràpid, els estils es poden desordenar ràpidament. Els estats predeterminats, actius i de depuració es poden solapar, i sense una estructura clara, el vostre CSS es torna fràgil i difícil d'estendre. Les capes en cascada CSS poden ajudar. Agrupen els estils en "capes" que s'ordenen per prioritat, de manera que deixeu de lluitar contra l'especificitat i endevineu: "Per què no es mostra el meu estil de depuració?" En comptes d'això, manteniu preocupacions separades:

Base: aspecte inicial estàndard del controlador. Actiu: destaca els botons premuts i els sticks moguts. Depuració: superposicions per a desenvolupadors (p. ex., lectures numèriques, guies, etc.).

Si haguéssim de definir capes en CSS d'acord amb això, tindríem: /* de menor a major prioritat */ @layer base, actiu, depuració;

@base de capa { /* ... */ }

@capa activa { /* ... */ }

depuració de @layer { /* ... */ }

Com que cada capa s'apila de manera previsible, sempre saps quines regles guanyen. Aquesta predictibilitat fa que la depuració no només sigui més fàcil, sinó que també sigui manejable. Hem tractat el problema (entrada invisible i desordenada) i l'enfocament (un depurador visual construït amb Cascade Layers). Ara seguirem el procés pas a pas per crear el depurador. El concepte depurador La manera més senzilla de fer visible l'entrada oculta és dibuixar-la a la pantalla. Això és el que fa aquest depurador. Els botons, els disparadors i els joysticks tenen un aspecte visual.

Premeu A: s'il·lumina un cercle. Empuja el pal: el cercle llisca al voltant. Premeu un gallet a la meitat: una barra s'omple a la meitat.

Ara no esteu mirant els 0 i els 1, sinó que en realitat observeu el controlador reaccionar en directe. Per descomptat, un cop comenceu a acumular estats com ara informació predeterminada, premsada, depuració, potser fins i tot un mode d'enregistrament, el CSS comença a ser més gran i complex. Aquí és on les capes en cascada són útils. Aquí teniu un exemple reduït: @base de capa { .botó { fons: #222; frontera-radi: 50%; amplada: 40 píxels; alçada: 40px; } }

@capa activa { .botó.premut { fons: #0f0; /* verd brillant */ } }

depuració de @layer { .button::després de { contingut: attr(valor de dades); mida de la lletra: 12px; color: #fff; } }

L'ordre de les capes importa: base → actiu → depurar.

base dibuixa el controlador. actiu gestiona els estats pressionats. llançaments de depuració a les superposicions.

Trencar-lo així vol dir que no esteu lluitant contra guerres d'especificitat estranyes. Cada capa té el seu lloc, i sempre saps què guanya. Construint-lo Anem a tenir alguna cosa a la pantalla primer. No cal que es vegi bé, només ha d'existir perquè tinguem alguna cosa per treballar.

Gamepad Cascade Debugger

A
B
X

Depurador inactiu

Això són literalment només caixes. Encara no és emocionant, però ens dóna manetes per agafar més tard amb CSS i JavaScript. D'acord, estic fent servir capes en cascada aquí perquè manté les coses organitzades un cop afegiu més estats. Aquí teniu una passada aproximada:

/* ===================================== CONFIGURACIÓ DE CAPES EN CASCADA L'ordre importa: base → actiu → depurar ==================================== */

/* Definiu l'ordre de les capes per endavant */ @layer base, actiu, depuració;

/* Capa 1: Estils base - aspecte predeterminat */ @base de capa { .botó { fons: #333; frontera-radi: 50%; amplada: 70 píxels; alçada: 70px; pantalla: flex; justificar-contingut: centre; elements d'alineació: centre; }

.pausa { amplada: 20 píxels; alçada: 70px; fons: #333; pantalla: bloc en línia; } }

/* Capa 2: Estats actius: gestiona els botons premuts */ @capa activa { .botó.actiu { fons: #0f0; /* Verd brillant quan es prem */ transformar: escala (1,1); /* Amplia lleugerament el botó */ }

.pausa.activa { fons: #0f0; transformar: escalaY(1,1); /* S'estira verticalment quan es pressiona */ } }

/* Capa 3: superposicions de depuració - informació del desenvolupador */ depuració de @layer { .button::després de { contingut: attr(valor de dades); /* Mostra el valor numèric */ mida de la lletra: 12px; color: #fff; } }

La bellesa d'aquest enfocament és que cada capa té un propòsit clar. La capa base mai pot substituir l'actiu, i l'actiu mai no pot anul·lar la depuració, independentment de l'especificitat. Això elimina les guerres d'especificitat CSS que solen afectar les eines de depuració. Ara sembla que alguns grups estan asseguts sobre un fons fosc. Sincerament, no està gens malament.

Afegint el JavaScript Temps de JavaScript. Aquí és on el controlador fa alguna cosa. Construirem això pas a pas. Pas 1: configureu la gestió de l'estat Primer, necessitem variables per fer un seguiment de l'estat del depurador: // ===================================== // GESTIÓ DE L'ESTAT // =====================================

deixar córrer = fals; // Fa un seguiment de si el depurador està actiu deixar rafId; // Emmagatzema l'ID de requestAnimationFrame per a la cancel·lació

Aquestes variables controlen el bucle d'animació que llegeix contínuament l'entrada del gamepad. Pas 2: agafeu les referències DOM A continuació, obtenim referències a tots els elements HTML que actualitzarem: // ===================================== // REFERÈNCIES D'ELEMENTS DOM // =====================================

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

Emmagatzemar aquestes referències per endavant és més eficient que consultar el DOM repetidament. Pas 3: afegiu una alternativa de teclat Per provar sense un controlador físic, assignarem les tecles del teclat als botons: // ===================================== // FALLBACK DEL TECLAT (per provar sense controlador) // =====================================

const keyMap = { "a": btnA, "b": btnB, "x": btnX, "p": [pausa1, pausa2] // La tecla 'p' controla les dues barres de pausa };

Això ens permet provar la interfície d'usuari prement les tecles d'un teclat. Pas 4: creeu el bucle d'actualització principal Aquí és on passa la màgia. Aquesta funció s'executa contínuament i llegeix l'estat del gamepad: // ===================================== // BUCLE PRINCIPAL D'ACTUALITZACIÓ DEL GAMEPAD // =====================================

funció updateGamepad() { // Obteniu tots els jocs connectats const gamepads = navigator.getGamepads(); si (! gamepads) torna;

// Utilitzeu el primer gamepad connectat const gp = gamepads[0];

si (gp) { // Actualitza els estats del botó canviant la classe "activa". btnA.classList.toggle("actiu", gp.buttons[0].premut); btnB.classList.toggle("actiu", gp.buttons[1].premut); btnX.classList.toggle("actiu", gp.buttons[2].premut);

// Gestionar el botó de pausa (botó índex 9 a la majoria de controladors) const pausePressed = gp.buttons[9].premut; pause1.classList.toggle("actiu", pausePressed); pause2.classList.toggle("actiu", pausePressed);

// Creeu una llista dels botons premuts actualment per mostrar l'estat deixar premut = []; gp.buttons.forEach((btn, i) => { si (premut btn)pressed.push("Botó" + i); });

// Actualitza el text d'estat si es prem algun botó if (longitud.premsa > 0) { status.textContent = "Premut: " + pressed.join(", "); } }

// Continua el bucle si el depurador s'està executant si (córrer) { rafId = requestAnimationFrame(actualitzarGamepad); } }

El mètode classList.toggle() afegeix o elimina la classe activa en funció de si es prem el botó, la qual cosa activa els nostres estils de capa CSS. Pas 5: Gestioneu els esdeveniments del teclat Aquests oients d'esdeveniments fan que el teclat funcioni: // ===================================== // GESTORS D'ESDEVENIMENTS DE TECLAT // =====================================

document.addEventListener("keydown", (e) => { if (keyMap[e.key]) { // Gestiona elements únics o múltiples if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("actiu")); } altrament { keyMap[e.key].classList.add("actiu"); } status.textContent = "Tecla pressionada: " + e.key.toUpperCase(); } });

document.addEventListener("keyup", (e) => { if (keyMap[e.key]) { // Elimina l'estat actiu quan es deixa anar la tecla if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("active")); } altrament { keyMap[e.key].classList.remove("actiu"); } status.textContent = "Clau alliberada: " + e.key.toUpperCase(); } });

Pas 6: afegiu el control d'inici/aturada Finalment, necessitem una manera d'activar i desactivar el depurador: // ===================================== // ACTIVA/DESACTIVA EL DEBUGGER // =====================================

document.getElementById("commutar").addEventListener("clic", () => { corrent = !corrent; // Inverteix l'estat d'execució

si (córrer) { status.textContent = "Depurador en execució..."; actualitzarGamepad(); // Inicia el bucle d'actualització } altrament { status.textContent = "Depurador inactiu"; cancelAnimationFrame(rafId); // Atura el bucle } });

Així que sí, premeu un botó i brillarà. Empènyer el pal i es mou. Això és tot. Una cosa més: els valors en brut. De vegades només vols veure números, no llums.

En aquesta etapa, hauríeu de veure:

Un simple controlador en pantalla, Botons que reaccionen mentre interactues amb ells, i Una lectura de depuració opcional que mostra els índexs de botons premuts.

Per fer-ho menys abstracte, aquí teniu una demostració ràpida del controlador en pantalla que reacciona en temps real:

Ara, si premeu Inicia la gravació, es registra tot fins que premeu Atura la gravació. 2. Exportació de dades a CSV/JSON Un cop tinguem un registre, voldrem desar-lo.

Pas 1: creeu l'ajudant de descàrrega Primer, necessitem una funció d'ajuda que gestioni les descàrregues de fitxers al navegador: // ===================================== // AUXILIAR DE DESCÀRREGA DE FITXES // =====================================

funció downloadFile (nom del fitxer, contingut, tipus = "text/plain") { // Crea un blob a partir del contingut const blob = nou Blob([contingut], { tipus }); const url = URL.createObjectURL(blob);

// Creeu un enllaç de descàrrega temporal i feu-hi clic const a = document.createElement("a"); a.href = url; a.download = nom de fitxer; a.clic();

// Netegeu l'URL de l'objecte després de la descàrrega setTimeout(() => URL.revokeObjectURL(url), 100); }

Aquesta funció funciona creant un blob (objecte binari gran) a partir de les vostres dades, generant-ne un URL temporal i fent clic a un enllaç de baixada mitjançant programació. La neteja garanteix que no perdem memòria. Pas 2: Gestioneu l'exportació JSON JSON és perfecte per preservar l'estructura de dades completa:

// ===================================== // EXPORTA COM JSON // =====================================

document.getElementById("export-json").addEventListener("clic", () => { // Comproveu si hi ha res per exportar if (!frames.length) { console.warn("No hi ha cap gravació disponible per exportar."); tornar; }

// Crea una càrrega útil amb metadades i marcs càrrega útil constant = { creatAt: data nova().toISOString(), marcs };

// Baixeu com a JSON amb format descarregarFitxer( "gamepad-log.json", JSON.stringify(càrrega útil, nul, 2), "aplicació/json" ); });

El format JSON manté tot estructurat i fàcilment analitzable, el que el fa ideal per tornar a carregar-se a les eines de desenvolupament o compartir-lo amb els companys d'equip. Pas 3: Gestioneu l'exportació CSV Per a les exportacions CSV, hem d'aplanar les dades jeràrquiques en files i columnes:

//===================================== // EXPORTA COM CSV // =====================================

document.getElementById("export-csv").addEventListener("clic", () => { // Comproveu si hi ha res per exportar if (!frames.length) { console.warn("No hi ha cap gravació disponible per exportar."); tornar; }

// Crea una fila de capçalera CSV (columnes per a la marca de temps, tots els botons, tots els eixos) 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";

// Crea files de dades CSV const files = frames.map(f => { const btnVals = f.buttons.map (b => b.valor); retornar [f.t, ...btnVals, ...f.axes].join(","); }).join("\n");

// Baixa com a CSV downloadFile("gamepad-log.csv", capçalera + files, "text/csv"); });

CSV és excel·lent per a l'anàlisi de dades perquè s'obre directament a Excel o a Google Sheets, cosa que us permet crear gràfics, filtrar dades o detectar patrons visualment. Ara que els botons d'exportació estan activats, veureu dues opcions noves al tauler: Exporta JSON i Exporta CSV. JSON és bo si voleu tornar a llençar el registre en brut a les vostres eines de desenvolupament o córrer per l'estructura. CSV, d'altra banda, s'obre directament a Excel o Fulls de càlcul de Google perquè pugueu traçar, filtrar o comparar entrades. La figura següent mostra com és el tauler amb aquests controls addicionals.

3. Sistema d'instantànies De vegades no necessiteu una gravació completa, només una "captura de pantalla" ràpida dels estats d'entrada. Aquí és on ajuda un botó Prendre una instantània.

I el JavaScript:

// ===================================== // FES INSTANTANÀRIA // =====================================

document.getElementById("snapshot").addEventListener("clic", () => { // Obteniu tots els jocs connectats const pads = navigator.getGamepads(); const activePads = [];

// Recorre i captura l'estat de cada gamepad connectat per (const gp de pastilles) { si (!gp) continua; // Omet espais buits

activePads.push({ id: gp.id, // Nom/model del controlador marca de temps: performance.now(), botons: gp.buttons.map(b => ({ premsat: b.premut, valor: b.valor })), eixos: [...gp.axes] }); }

// Comproveu si s'han trobat jocs if (!activePads.length) { console.warn("No hi ha cap teclat connectat per a la captura de pantalla."); alert("No s'ha detectat cap controlador!"); tornar; }

// Registrar i notificar a l'usuari console.log("Instantània:", activePads); alerta (captura instantània! S'han capturat ${activePads.length} controladors).); });

Les instantànies congelen l'estat exacte del controlador en un moment determinat. 4. Repetició d'entrada fantasma Ara per la diversió: reproducció d'entrada fantasma. Això pren un registre i el reprodueix visualment com si un reproductor fantasma estigués fent servir el controlador.

JavaScript per a la reproducció: // ===================================== // REPLAY DE FANTASMES // =====================================

document.getElementById("repetició").addEventListener("clic", () => { // Assegureu-vos que tenim una gravació per reproduir if (!frames.length) { alert("No hi ha cap gravació per reproduir!"); tornar; }

console.log("S'està iniciant la reproducció fantasma...");

// Temporització de la pista per a la reproducció sincronitzada let startTime = rendiment.now(); deixa frameIndex = 0;

// Reprodueix el bucle d'animació pas de la funció () { const ara = rendiment.ara(); const transcorregut = ara - hora d'inici;

// Processa tots els fotogrames que s'haurien d'haver produït a hores d'ara while (frameIndex < frames.length && frames[frameIndex].t <= transcorregut) { const frame = frames[frameIndex];

// Actualitza la interfície d'usuari amb els estats dels botons enregistrats btnA.classList.toggle("actiu", frame.buttons[0].premut); btnB.classList.toggle("actiu", frame.buttons[1].premut); btnX.classList.toggle("actiu", frame.buttons[2].premut);

// Actualitza la visualització d'estat deixar premut = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push ("Botó " + i); }); if (longitud.premsa > 0) { status.textContent = "Fantasma: " + pressed.join(", "); }

frameIndex++; }

// Continua el bucle si hi ha més fotogrames if (frameIndex < frames.length) { requestAnimationFrame(pas); } altrament { console.log ("Repeticióacabat."); status.textContent = "Repetició completada"; } }

// Comença la reproducció pas (); });

Per fer que la depuració sigui una mica més pràctica, he afegit una reproducció fantasma. Un cop hàgiu gravat una sessió, podeu prémer la reproducció i veure com la interfície d'usuari l'actua, gairebé com si un reproductor fantasma estigués executant el pad. Apareix un nou botó Replay Ghost al tauler per a això.

Premeu Enregistrament, feu un embolic una mica amb el controlador, atureu-lo i, a continuació, torneu a reproduir-lo. La interfície d'usuari només es fa ressò de tot el que heu fet, com un fantasma que segueix les vostres entrades. Per què molestar-se amb aquests extres?

L'enregistrament/exportació facilita que els verificadors mostrin exactament què ha passat. Les instantànies es bloquegen un moment, molt útils quan perseguiu errors estranys. La reproducció de fantasmes és ideal per a tutorials, comprovacions d'accessibilitat o simplement per comparar les configuracions de control una al costat de l'altra.

En aquest moment, ja no és només una demostració ordenada, sinó una cosa que realment podríeu posar a treballar. Casos d'ús del món real Ara tenim aquest depurador que pot fer molt. Mostra entrada en directe, registra registres, els exporta i fins i tot reprodueix coses. Però la veritable pregunta és: a qui li importa realment? A qui serveix això? Desenvolupadors de jocs Els controladors formen part de la feina, però depurar-los? Normalment un dolor. Imagineu-vos que esteu provant una combinació de jocs de lluita, com ara ↓ → + cop de puny. En lloc de resar, l'has premut de la mateixa manera dues vegades, l'has gravat una vegada i ho has tornat a reproduir. Fet. O intercanvieu els registres JSON amb un company per comprovar si el vostre codi multijugador reacciona igual a la seva màquina. Això és enorme. Professionals de l'accessibilitat Aquesta és a prop del meu cor. No tothom juga amb un controlador "estàndard". Els controladors adaptatius emeten senyals estranys de vegades. Amb aquesta eina, podeu veure exactament què està passant. Professors, investigadors, qui sigui. Poden agafar registres, comparar-los o reproduir les entrades una al costat de l'altra. De sobte, coses invisibles es fan evidents. Proves d'assegurament de la qualitat Els provadors solen escriure notes com "He fet puré de botons aquí i es va trencar". No molt útil. Ara? Poden capturar les premses exactes, exportar el registre i enviar-lo. Sense endevinar. Educadors Si esteu fent tutorials o vídeos de YouTube, la reproducció de fantasmes és or. Podeu dir literalment: "Així és el que vaig fer amb el controlador", mentre la interfície d'usuari mostra que està passant. Fa les explicacions molt més clares. Més enllà dels Jocs I sí, això no es tracta només de jocs. La gent ha utilitzat controladors per a robots, projectes artístics i interfícies d'accessibilitat. El mateix problema cada vegada: què veu realment el navegador? Amb això, no cal endevinar. Conclusió La depuració d'una entrada de controlador sempre ha estat com si volgués a cegues. A diferència del DOM o CSS, no hi ha cap inspector integrat per als gamepads; només són números en brut a la consola, que es perden fàcilment en el soroll. Amb uns quants centenars de línies d'HTML, CSS i JavaScript, vam crear alguna cosa diferent:

Un depurador visual que fa visibles les entrades invisibles. Un sistema CSS en capes que manté la interfície d'usuari neta i depurable. Un conjunt de millores (enregistrament, exportació, instantànies, reproducció fantasma) que l'eleven de demostració a eina de desenvolupador.

Aquest projecte mostra fins on podeu arribar barrejant el poder de la plataforma web amb una mica de creativitat a les capes de cascada CSS. L'eina que acabo d'explicar en la seva totalitat és de codi obert. Podeu clonar el repositori de GitHub i provar-lo vosaltres mateixos. Però el més important és que pots fer-ho teu. Afegiu les vostres pròpies capes. Construeix la teva pròpia lògica de reproducció. Integra'l amb el teu prototip de joc. O fins i tot utilitzar-lo de maneres que no m'he imaginat. Per a l'ensenyament, l'accessibilitat o l'anàlisi de dades. Al cap i a la fi, no es tracta només de depurar els gamepads. Es tracta de donar llum a les entrades ocultes i de donar als desenvolupadors la confiança per treballar amb maquinari que el web encara no accepta del tot. Per tant, connecteu el controlador, obriu el vostre editor i comenceu a experimentar. Potser us sorprendrà el que el vostre navegador i el vostre CSS poden aconseguir realment.

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