Cando conectas un controlador, aplastas botóns, moves os sticks, tiras os gatillos... e como desenvolvedor, non ves nada diso. O navegador está a recollelo, claro, pero a menos que esteas rexistrando números na consola, é invisible. Ese é o quebradizo coa API Gamepad. Leva anos, e en realidade é bastante poderoso. Podes ler botóns, paus, disparadores, as obras. Pero a maioría da xente non o toca. Por que? Porque non hai comentarios. Non hai panel nas ferramentas para programadores. Non hai unha forma clara de saber se o controlador está facendo o que pensas. Parece como voar cego. Iso provocoume o suficiente como para construír unha pequena ferramenta: Gamepad Cascade Debugger. En lugar de mirar a saída da consola, obtén unha vista en directo e interactiva do controlador. Preme algo e reacciona na pantalla. E con CSS Cascade Layers, os estilos mantéñense organizados, polo que é máis limpo depurar. Nesta publicación, mostrarei por que depurar controladores é tan doloroso, como CSS axuda a limpalo e como podes construír un depurador visual reutilizable para os teus propios proxectos.

Aínda que poidas rexistralos todos, axiña acabarás con spam de consola ilexible. Por exemplo: [0,0,1,0,0,0,5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]

Podes dicir que botón se premeu? Quizais, pero só despois de esforzar os ollos e perder algunhas entradas. Polo tanto, non, a depuración non é fácil cando se trata de ler entradas. Problema 3: Falta de Estrutura Aínda que xunte un visualizador rápido, os estilos poden desordenarse rapidamente. Os estados predeterminados, activos e de depuración poden superpoñerse e, sen unha estrutura clara, o teu CSS vólvese fráxil e difícil de estender. As capas en cascada CSS poden axudar. Agrupan os estilos en "capas" que están ordenadas por prioridade, polo que deixas de loitar contra a especificidade e adiviñas: "Por que non se mostra o meu estilo de depuración?" Pola contra, mantén preocupacións separadas:

Base: aspecto inicial estándar do controlador. Activo: destaca os botóns presionados e os paus movidos. Depuración: superposicións para desenvolvedores (por exemplo, lecturas numéricas, guías, etc.).

Se definimos capas en CSS segundo isto, teríamos: /* de menor a maior prioridade */ @capa base, activo, depurar;

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

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

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

Debido a que cada capa se acumula de forma previsible, sempre sabes cales regras gañan. Esa previsibilidade fai que a depuración non só sexa máis fácil, senón que sexa realmente manexable. Cubrimos o problema (entrada invisible e desordenada) e o enfoque (un depurador visual construído con Cascade Layers). Agora percorreremos o proceso paso a paso para construír o depurador. O concepto depurador A forma máis sinxela de facer visible a entrada oculta é debuxala na pantalla. Iso é o que fai este depurador. Todos os botóns, disparadores e joysticks teñen unha imaxe visual.

Prema A: un círculo ilumina. Empuña o pau: o círculo deslízase ao redor. Pull un gatillo á metade: unha barra énchese á metade.

Agora non estás mirando os 0 e os 1, senón que estás vendo como reacciona o controlador en directo. Por suposto, unha vez que comeza a acumular estados como o predeterminado, presionado, información de depuración, quizais mesmo un modo de gravación, o CSS comeza a ser máis grande e complexo. Aí é onde as capas en cascada son útiles. Aquí tes un exemplo reducido: @base de capa { .botón { fondo: #222; borde-raio: 50%; ancho: 40px; altura: 40px; } }

@capa activa { .botón.premido { fondo: #0f0; /* verde brillante */ } }

depuración de @layer { .button::despois de { contido: attr(datos-valor); tamaño da fonte: 12px; cor: #fff; } }

A orde das capas importa: base → activa → depurar.

base debuxa o controlador. activo manexa estados presionados. lanzamentos de depuración en superposicións.

Romperlo así significa que non estás loitando contra estrañas guerras de especificidade. Cada capa ten o seu lugar, e sempre sabes o que gaña. Construíndoo Primeiro vexamos algo na pantalla. Non ten que ter un bo aspecto, só ten que existir para que teñamos algo co que traballar.

Depurador de Cascade de Gamepad

A
B
X

Depurador inactivo

Iso son literalmente só caixas. Aínda non é emocionante, pero ofrécenos manexos para coller máis tarde con CSS e JavaScript. Está ben, estou usando capas en cascada aquí porque mantén as cousas organizadas unha vez que engades máis estados. Aquí tes un pase aproximado:

/* ===================================== CONFIGURACIÓN DE CAPAS EN CASCADA A orde importa: base → activo → depurar =====================================*/

/* Definir a orde das capas por adiantado */ @capa base, activo, depurar;

/* Capa 1: estilos base - aspecto predeterminado */ @base de capa { .botón { fondo: #333; borde-raio: 50%; ancho: 70px; altura: 70px; visualización: flex; xustificar-contido: centro; elementos de aliñamento: centro; }

.pausa { ancho: 20px; altura: 70px; fondo: #333; visualización: bloque en liña; } }

/* Capa 2: estados activos: xestiona os botóns presionados */ @capa activa { .botón.activo { fondo: #0f0; /* Verde brillante cando se preme */ transformar: escala (1.1); /* Amplía lixeiramente o botón */ }

.pausa.activa { fondo: #0f0; transformar: escalaY(1.1); /* Esténdese verticalmente cando se presiona */ } }

/* Capa 3: depurar superposicións - información do desenvolvedor */ depuración de @layer { .button::despois de { contido: attr(datos-valor); /* Mostra o valor numérico */ tamaño da fonte: 12px; cor: #fff; } }

A beleza deste enfoque é que cada capa ten un propósito claro. A capa base nunca pode anular a activación, e a activa nunca pode anular a depuración, independentemente da especificidade. Isto elimina as guerras de especificidade CSS que adoitan afectar ás ferramentas de depuración. Agora parece que algúns grupos están sentados sobre un fondo escuro. Sinceramente, non está nada mal.

Engadindo o JavaScript Hora de JavaScript. Aquí é onde o controlador realmente fai algo. Construiremos isto paso a paso. Paso 1: configurar a xestión do estado En primeiro lugar, necesitamos variables para rastrexar o estado do depurador: // ===================================== // XESTIÓN ESTATAL // =====================================

deixar correr = falso; // Rastrexa se o depurador está activo deixar rafId; // Almacena o ID de requestAnimationFrame para a cancelación

Estas variables controlan o bucle de animación que le continuamente a entrada do gamepad. Paso 2: colle referencias DOM A continuación, obtemos referencias a todos os elementos HTML que actualizaremos: // ===================================== // REFERENCIAS DO ELEMENTO 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("estado");

Gardar estas referencias de antemán é máis eficiente que consultar o DOM repetidamente. Paso 3: Engade o teclado alternativo Para probar sen un controlador físico, asignaremos as teclas do teclado aos botóns: // ===================================== // TECLADO FALLBACK (para probar sen controlador) // =====================================

const keyMap = { "a": btnA, "b": btnB, "x": btnX, "p": [pausa1, pausa2] // A tecla 'p' controla ambas as barras de pausa };

Isto permítenos probar a IU premendo as teclas dun teclado. Paso 4: cree o bucle de actualización principal Aquí é onde ocorre a maxia. Esta función execútase continuamente e le o estado do gamepad: // ===================================== // BUCLE DE ACTUALIZACIÓN DO GAMEPAD PRINCIPAL // =====================================

función updateGamepad () { // Obter todos os gamepads conectados const gamepads = navigator.getGamepads(); se (! gamepads) volve;

// Usa o primeiro gamepad conectado const gp = gamepads[0];

se (gp) { // Actualizar os estados dos botóns alternando a clase "activa". btnA.classList.toggle("activo", gp.buttons[0].pressed); btnB.classList.toggle("activo", gp.buttons[1].pressed); btnX.classList.toggle("activo", gp.buttons[2].pressed);

// Manexa o botón de pausa (botón índice 9 na maioría dos controladores) const pausePressed = gp.buttons[9].pressed; pause1.classList.toggle("activo", pausePressed); pause2.classList.toggle("activo", pausePressed);

// Crea unha lista dos botóns presionados actualmente para mostrar o estado deixar presionado = []; gp.buttons.forEach((btn, i) => { se (btn.pressed)pressed.push("Botón" + i); });

// Actualiza o texto de estado se se preme algún botón if (longitud.presionada > 0) { status.textContent = "Presionado: " + pressed.join(", "); } }

// Continúa o bucle se o depurador está en execución if (correndo) { rafId = requestAnimationFrame (actualizarGamepad); } }

O método classList.toggle() engade ou elimina a clase activa en función de se se preme o botón, o que activa os nosos estilos de capa CSS. Paso 5: xestionar eventos do teclado Estes oíntes de eventos fan que o teclado alternativo funcione: // ===================================== // XESTIONADORES DE EVENTOS DE TECLADO // =====================================

document.addEventListener("keydown", (e) => { if (keyMap[e.key]) { // Manexa elementos únicos ou múltiples if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("activo")); } máis { keyMap[e.key].classList.add("activo"); } status.textContent = "Tecla presionada: " + e.key.toUpperCase(); } });

document.addEventListener("keyup", (e) => { if (keyMap[e.key]) { // Elimina o estado activo cando se solta a tecla if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("activo")); } máis { keyMap[e.key].classList.remove("activo"); } status.textContent = "Chave liberada: " + e.key.toUpperCase(); } });

Paso 6: Engade o control de inicio/parada Finalmente, necesitamos un xeito de activar e desactivar o depurador: // ===================================== // ACTIVAR/DESACTIVAR O DEBUGGER // =====================================

document.getElementById("alternar").addEventListener("clic", () => { correndo = !correndo; // Cambia o estado de execución

if (correndo) { status.textContent = "Depurador en execución..."; actualizarGamepad(); // Inicia o ciclo de actualización } máis { status.textContent = "Depurador inactivo"; cancelAnimationFrame(rafId); // Deter o bucle } });

Entón, si, preme un botón e brilla. Empurra o pau e móvese. Iso é. Unha cousa máis: valores brutos. Ás veces só queres ver números, non luces.

Nesta fase, deberías ver:

Un simple controlador en pantalla, Botóns que reaccionan mentres interactúas con eles, e Unha lectura de depuración opcional que mostra os índices dos botóns presionados.

Para facer isto menos abstracto, aquí tes unha demostración rápida do controlador en pantalla que reacciona en tempo real:

Agora, ao premer Iniciar gravación, rexistra todo ata que prema Deter a gravación. 2. Exportando datos a CSV/JSON Unha vez que teñamos un rexistro, queremos gardalo.

Paso 1: crea o axudante de descarga En primeiro lugar, necesitamos unha función auxiliar que xestione as descargas de ficheiros no navegador: // ===================================== // AXUDANTE DE DESCARGA DE FICHEIRO // =====================================

función downloadFile (nome do ficheiro, contido, tipo = "texto/sen formato") { // Crea un blob a partir do contido const blob = new Blob ([contido], { tipo }); const url = URL.createObjectURL(blob);

// Crea unha ligazón de descarga temporal e fai clic nel const a = document.createElement("a"); a.href = url; a.download = nome do ficheiro; a.click();

// Limpar o URL do obxecto despois da descarga setTimeout(() => URL.revokeObjectURL(url), 100); }

Esta función funciona creando un Blob (obxecto binario grande) a partir dos teus datos, xerando un URL temporal para el e facendo clic nunha ligazón de descarga mediante programación. A limpeza garante que non perdamos memoria. Paso 2: xestionar a exportación JSON JSON é perfecto para preservar a estrutura de datos completa:

// ===================================== // EXPORTAR COMO JSON // =====================================

document.getElementById("export-json").addEventListener("clic", () => { // Comproba se hai algo que exportar se (!fotogramas.lonxitude) { console.warn("Non hai ningunha gravación dispoñible para exportar."); volver; }

// Crea unha carga útil con metadatos e marcos carga útil constante = { createdAt: data nova().toISOString(), cadros };

// Descarga como formato JSON descargar ficheiro( "gamepad-log.json", JSON.stringify(carga útil, nulo, 2), "aplicación/json" ); });

O formato JSON mantén todo estruturado e facilmente analizable, polo que é ideal para cargar de novo nas ferramentas de desenvolvemento ou para compartir cos compañeiros de equipo. Paso 3: xestionar a exportación CSV Para as exportacións CSV, necesitamos aplanar os datos xerárquicos en filas e columnas:

//==================================== // EXPORTAR COMO CSV // =====================================

document.getElementById("export-csv").addEventListener("clic", () => { // Comproba se hai algo que exportar se (!fotogramas.lonxitude) { console.warn("Non hai ningunha gravación dispoñible para exportar."); volver; }

// Crear fila de cabeceira CSV (columnas para marca de tempo, todos os botóns, todos os eixes) const headerButtons = frames[0].buttons.map((_, i) => btn${i}); const headerAxes = frames[0].axes.map((_, i) => eixe${i}); const header = ["t", ...headerButtons, ...headerAxes].join(",") + "\n";

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

// Descarga como CSV downloadFile("gamepad-log.csv", encabezado + filas, "text/csv"); });

CSV é excelente para a análise de datos porque se abre directamente en Excel ou Google Sheets, o que che permite crear gráficos, filtrar datos ou detectar patróns visualmente. Agora que están activados os botóns de exportación, verás dúas novas opcións no panel: Exportar JSON e Exportar CSV. JSON é bo se queres devolver o rexistro en bruto ás túas ferramentas de desenvolvemento ou explorar a estrutura. CSV, por outra banda, ábrese directamente en Excel ou Google Sheets para que poidas trazar, filtrar ou comparar entradas. A seguinte figura mostra o aspecto do panel con eses controis adicionais.

3. Sistema de instantáneas Ás veces non precisa unha gravación completa, só unha "captura de pantalla" rápida dos estados de entrada. Aí é onde un botón Tomar instantánea axuda.

E o JavaScript:

// ===================================== // TOMAR INSTANTÁNEAS // =====================================

document.getElementById("instantánea").addEventListener("clic", () => { // Obter todos os gamepads conectados const pads = navigator.getGamepads(); const activePads = [];

// Recorre e captura o estado de cada gamepad conectado for (const gp of pads) { se (!gp) continúa; // Saltar espazos baleiros

activePads.push({ id: gp.id, // Nome/modelo do controlador marca de tempo: performance.now(), botóns: gp.buttons.map(b => ({ prensado: b.pressado, valor: b.valor })), eixes: [...gp.axes] }); }

// Comproba se se atopou algún gamepad if (!activePads.length) { console.warn("Non hai gamepads conectados para a instantánea."); alert("Non se detectou ningún controlador!"); volver; }

// Rexistrar e notificar ao usuario console.log("Instantánea:", activePads); alerta(Fotouse unha instantánea! Capturáronse os controladores ${activePads.length}); });

As instantáneas conxelan o estado exacto do teu controlador nun momento. 4. Reprodución de entrada de Ghost Agora para o divertido: repetición de entrada de pantasmas. Isto leva un rexistro e reproduceo visualmente coma se un reprodutor fantasma estivese a usar o controlador.

JavaScript para a reprodución: // ===================================== // REPETICIÓN DE FANTASMAS // =====================================

document.getElementById("reprodución").addEventListener("clic", () => { // Asegúrate de ter unha gravación para reproducir se (!fotogramas.lonxitude) { alert("Non hai gravación para reproducir!"); volver; }

console.log("Iniciando a reprodución de pantasmas...");

// Temporización da pista para a reprodución sincronizada let startTime = performance.now(); deixe frameIndex = 0;

// Reprodución do bucle de animación paso de función () { const agora = performance.now(); const elapsed = now - startTime;

// Procesa todos os fotogramas que deberían ter ocorrido ata agora while (Índice de cadros < frames.length && frames[frameIndex].t <= transcorrido) { const frame = cadros[índice de cadros];

// Actualiza a IU cos estados dos botóns gravados btnA.classList.toggle("activo", frame.buttons[0].pressed); btnB.classList.toggle("activo", frame.buttons[1].pressed); btnX.classList.toggle("activo", frame.buttons[2].pressed);

// Actualizar a visualización de estado deixar presionado = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push ("Botón " + i); }); if (longitud.presionada > 0) { status.textContent = "Pantasma: " + pressed.join(", "); }

frameIndex++; }

// Continuar o bucle se hai máis cadros if (índice de cadros < frames.length) { requestAnimationFrame(paso); } máis { console.log("Reproduciónrematado."); status.textContent = "Reprodución completada"; } }

// Comeza a reprodución paso (); });

Para facer a depuración un pouco máis práctica, engadín unha repetición de pantasmas. Despois de gravar unha sesión, podes pulsar en repetición e ver a IU actuando, case como un reprodutor fantasma executando o pad. Para iso, aparece un novo botón Replay Ghost no panel.

Preme Gravar, xoga un pouco co controlador, para e despois volve reproducir. A IU só fai eco de todo o que fixeches, como unha pantasma seguindo as túas entradas. Por que molestarse con estes extras?

A gravación/exportación facilita aos probadores mostrar exactamente o que pasou. As instantáneas conxélanse nun momento, moi útiles cando buscas erros estraños. A reprodución de pantasmas é excelente para titoriais, comprobacións de accesibilidade ou só para comparar as configuracións de control.

Neste punto, xa non é só unha demostración, senón algo que realmente poderías poñer a traballar. Casos de uso do mundo real Agora temos este depurador que pode facer moito. Mostra entradas en directo, rexistra rexistros, expórtaos e mesmo reproduce cousas. Pero a verdadeira pregunta é: a quen lle importa realmente? Para quen é útil isto? Desenvolvedores de xogos Os controladores forman parte do traballo, pero depuralos? Normalmente unha dor. Imaxina que estás probando unha combinación de xogos de loita, como ↓ → + punch. En lugar de rezar, presionábao do mesmo xeito dúas veces, grávao unha vez e reprodúceo. Feito. Ou intercambias rexistros JSON cun compañeiro para comprobar se o teu código multixogador reacciona igual na súa máquina. Iso é enorme. Practicantes da accesibilidade Este está preto do meu corazón. Non todos xogan cun controlador "estándar". Os controladores adaptativos lanzan sinais estraños ás veces. Con esta ferramenta, podes ver exactamente o que está a suceder. Profesores, investigadores, quen sexa. Poden coller rexistros, comparalos ou reproducir entradas en paralelo. De súpeto, as cousas invisibles fanse evidentes. Probas de garantía de calidade Os probadores adoitan escribir notas como "Empecei botóns aquí e rompeuse". Non moi útil. Agora? Poden capturar as prensas exactas, exportar o rexistro e envialo. Sen adiviñar. Educadores Se estás facendo titoriais ou vídeos de YouTube, a reprodución de pantasmas é ouro. Podes dicir literalmente: "Aquí está o que fixen co controlador", mentres a IU mostra que está a suceder. Fai as explicacións moito máis claras. Máis aló dos Xogos E si, non se trata só de xogos. A xente usou controladores para robots, proxectos artísticos e interfaces de accesibilidade. O mesmo problema cada vez: que ve realmente o navegador? Con isto, non tes que adiviñar. Conclusión Depurar unha entrada de controlador sempre pareceu estar a cego. A diferenza do DOM ou CSS, non hai un inspector incorporado para os gamepads; son só números en bruto na consola, que se perden facilmente no ruído. Cuns centos de liñas de HTML, CSS e JavaScript, creamos algo diferente:

Un depurador visual que fai visibles as entradas invisibles. Un sistema CSS en capas que mantén a IU limpa e depurable. Un conxunto de melloras (gravación, exportación, instantáneas, reprodución de pantasmas) que o elevan de demostración a ferramenta para desenvolvedores.

Este proxecto mostra ata onde podes chegar mesturando o poder da plataforma web cun pouco de creatividade en CSS Cascade Layers. A ferramenta que acabo de explicar na súa totalidade é de código aberto. Podes clonar o repositorio de GitHub e probalo por ti mesmo. Pero o máis importante é que podes facelo teu. Engade as túas propias capas. Crea a túa propia lóxica de reprodución. Intégralo co teu prototipo de xogo. Ou incluso usalo de xeitos que non imaxinei. Para a docencia, accesibilidade ou análise de datos. Ao final, non se trata só de depurar gamepads. Trátase de iluminar as entradas ocultas e de dar aos desenvolvedores a confianza necesaria para traballar con hardware que a web aínda non acepta por completo. Entón, conecta o teu controlador, abre o teu editor e comeza a experimentar. Podes sorprenderte do que o teu navegador e o teu CSS realmente poden lograr.

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