Ko priključite krmilnik, pritiskate gumbe, premikate palice, vlečete sprožilce ... in kot razvijalec ne vidite ničesar od tega. Brskalnik ga seveda pobere, vendar je neviden, če ne beležite številk v konzolo. To je glavobol z API-jem Gamepad. Prisoten je že leta in je pravzaprav precej močan. Preberete lahko gumbe, palice, sprožilce, dela. Toda večina ljudi se tega ne dotakne. Zakaj? Ker ni povratne informacije. V orodjih za razvijalce ni plošče. Ni jasnega načina, da bi vedeli, ali upravljavec sploh počne, kar mislite. Občutek je, kot bi letel na slepo. To me je dovolj zmotilo, da sem ustvaril majhno orodje: Gamepad Cascade Debugger. Namesto strmenja v izhod konzole dobite interaktivni pogled na krmilnik v živo. Pritisnite nekaj in reagira na zaslonu. S kaskadnimi plastmi CSS pa slogi ostanejo organizirani, zato je odpravljanje napak čistejše. V tej objavi vam bom pokazal, zakaj je odpravljanje napak v krmilnikih tako mučno, kako CSS pomaga pri čiščenju in kako lahko sestavite vizualni razhroščevalnik za večkratno uporabo za svoje projekte.
Tudi če jih lahko vse zabeležite, boste hitro končali z neberljivo vsiljeno pošto na konzoli. Na primer: [0,0,1,0,0,0,5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]
Ali lahko poveste, kateri gumb je bil pritisnjen? Mogoče, a šele po naprezanju oči in zamudi nekaj vnosov. Torej, ne, odpravljanje napak ni enostavno, ko gre za branje vnosov. Problem 3: Pomanjkanje strukture Tudi če sestavite hiter vizualizator, lahko slogi hitro postanejo neurejeni. Privzeta, aktivna stanja in stanja za odpravljanje napak se lahko prekrivajo in brez jasne strukture postane vaš CSS krhek in ga je težko razširiti. CSS Cascade Layers vam lahko pomaga. Sloge združijo v »plasti«, ki so razvrščene po prioriteti, tako da se nehate boriti proti specifičnosti in ugibati: »Zakaj se moj slog odpravljanja napak ne prikaže?« Namesto tega skrbite ločeno:
Osnova: standardni krmilnik, začetni videz. Aktivno: poudarki za pritisnjene gumbe in premaknjene palice. Odpravljanje napak: prekrivni elementi za razvijalce (npr. številčni odčitki, vodniki itd.).
Če bi v skladu s tem definirali plasti v CSS, bi imeli: /* od najnižje do najvišje prioritete */ @layer base, active, debug;
@layer base { /* ... */ }
@plast aktivna { /* ... */ }
@layer debug { /* ... */ }
Ker se vsak sloj zlaga predvidljivo, vedno veste, katera pravila zmagajo. Zaradi te predvidljivosti je odpravljanje napak ne samo lažje, ampak tudi dejansko obvladljivo. Pokrili smo težavo (neviden, neurejen vnos) in pristop (vizualni razhroščevalnik, zgrajen s kaskadnimi plastmi). Zdaj se bomo sprehodili skozi postopek po korakih za izdelavo razhroščevalnika. Koncept razhroščevalnika Skriti vnos najlažje naredite viden tako, da ga preprosto narišete na zaslon. To počne ta razhroščevalnik. Gumbi, sprožilci in igralne palice so vsi vidni.
Pritisnite A: zasveti krog. Potisnite palico: krog drsi naokoli. Povlecite sprožilec do polovice: vrstica se napolni do polovice.
Zdaj ne strmite v 0 in 1, ampak dejansko opazujete odziv krmilnika v živo. Seveda, ko začnete kopičiti stanja, kot so privzeto, pritisnjeno, informacije o odpravljanju napak, morda celo način snemanja, začne CSS postajati večji in bolj zapleten. Tu pridejo prav kaskadne plasti. Tukaj je skrajšan primer: @layer base { .button { ozadje: #222; polmer obrobe: 50 %; širina: 40px; višina: 40px; } }
@plast aktivna { .button.pressed { ozadje: #0f0; /* svetlo zelena */ } }
@layer debug { .button::after { vsebina: attr(podatki-vrednost); velikost pisave: 12px; barva: #fff; } }
Vrstni red plasti je pomemben: osnova → aktivno → razhroščevanje.
baza nariše krmilnik. aktivno obravnava pritisnjena stanja. debug vrže prekrivke.
Če ga tako razdelite, pomeni, da se ne borite s čudnimi vojnami specifičnosti. Vsaka plast ima svoje mesto in vedno veste, kaj zmaga. Izgradnja Najprej poglejmo nekaj na zaslon. Ni treba, da je videti dobro - samo mora obstajati, da imamo s čim delati.
Gamepad Cascade Debugger
To so dobesedno samo škatle. Ni še vznemirljivo, vendar nam s CSS in JavaScript daje roke, ki jih lahko kasneje zgrabimo. V redu, tukaj uporabljam kaskadne sloje, ker ohranjajo stvari organizirane, ko dodate več stanj. Tukaj je grob prehod:
/* ==================================== NAMESTITEV KASKADNIH PLASTEV Vrstni red je pomemben: osnova → aktivno → odpravljanje napak ===================================== */
/* Določite vrstni red plasti vnaprej */ @layer base, active, debug;
/* Layer 1: Osnovni slogi - privzeti videz */ @layer base { .button { ozadje: #333; polmer obrobe: 50 %; širina: 70px; višina: 70px; zaslon: flex; justify-content: center; align-items: center; }
.pause { širina: 20px; višina: 70px; ozadje: #333; zaslon: inline-block; } }
/* 2. sloj: aktivna stanja - obravnava pritisnjene gumbe */ @plast aktivna { .button.active { ozadje: #0f0; /* Svetlo zelena ob pritisku */ transformacija: lestvica (1.1); /* Rahlo poveča gumb */ }
.pause.active { ozadje: #0f0; transformacija: scaleY(1.1); /* Ob pritisku se raztegne navpično */ } }
/* 3. plast: prekrivni elementi za odpravljanje napak - informacije o razvijalcu */ @layer debug { .button::after { vsebina: attr(podatki-vrednost); /* Prikazuje številsko vrednost */ velikost pisave: 12px; barva: #fff; } }
Lepota tega pristopa je, da ima vsaka plast jasen namen. Osnovni sloj ne more nikoli preglasiti aktivnega in aktivni nikoli ne more preglasiti odpravljanja napak, ne glede na specifičnost. To odpravlja vojne glede specifičnosti CSS, ki običajno pestijo orodja za odpravljanje napak. Zdaj je videti, kot da nekaj grozdov sedi na temnem ozadju. Iskreno povedano, ni tako slabo.
Dodajanje JavaScripta čas JavaScript. Tukaj krmilnik dejansko nekaj naredi. To bomo gradili korak za korakom. 1. korak: Nastavite upravljanje stanja Najprej potrebujemo spremenljivke za sledenje stanju razhroščevalnika: // ==================================== // UPRAVLJANJE DRŽAVE // ====================================
naj teče = false; // Sledi, ali je razhroščevalnik aktiven naj rafId; // Shrani ID requestAnimationFrame za preklic
Te spremenljivke nadzirajo animacijsko zanko, ki neprekinjeno bere vnos igralne ploščice. 2. korak: Zgrabite reference DOM Nato dobimo sklice na vse elemente HTML, ki jih bomo posodobili: // ==================================== // REFERENCE ELEMENTOV DOM // ====================================
const btnA = document.getElementById("btn-a"); const btnB = document.getElementById("btn-b"); const btnX = document.getElementById("btn-x"); const pause1 = document.getElementById("pause1"); const pause2 = document.getElementById("pause2"); const status = document.getElementById("status");
Shranjevanje teh sklicev vnaprej je bolj učinkovito kot ponavljajoče se poizvedovanje po DOM. 3. korak: dodajte rezervno tipkovnico Za testiranje brez fizičnega krmilnika bomo tipke tipkovnice preslikali v gumbe: // ==================================== // NADOMESTNA TIPKOVNICA (za testiranje brez krmilnika) // ====================================
const keyMap = { "a": btnA, "b": btnB, "x": btnX, "p": [pavza1, pavza2] // Tipka 'p' nadzoruje obe vrstici za premor };
To nam omogoča preizkus uporabniškega vmesnika s pritiskanjem tipk na tipkovnici. 4. korak: Ustvarite glavno posodobitveno zanko Tukaj se zgodi čarovnija. Ta funkcija deluje neprekinjeno in bere stanje igralne ploščice: // ==================================== // GLAVNA ZANKA POSODOBITVE IGRALNE PLOČADICE // ====================================
funkcija updateGamepad() { // Pridobite vse povezane igralne ploščice const gamepads = navigator.getGamepads(); if (!gamepads) return;
// Uporabite prvi povezani igralni plošček const gp = igralne ploščice [0];
če (gp) { // Posodobi stanja gumbov s preklopom razreda "aktivno". btnA.classList.toggle("aktiven", gp.buttons[0].pritisnjen); btnB.classList.toggle("aktiven", gp.buttons[1].pritisnjen); btnX.classList.toggle("aktiven", gp.buttons[2].pritisnjen);
// Upravljanje gumba za premor (indeks gumba 9 na večini krmilnikov) const pausePressed = gp.buttons[9].pressed; pause1.classList.toggle("aktiven", pausePressed); pause2.classList.toggle("aktiven", pausePressed);
// Ustvarite seznam trenutno pritisnjenih gumbov za prikaz stanja pustite pritisnjeno = []; gp.buttons.forEach((btn, i) => { če (btn.pritisnjen)pritisnjeno.push("Gumb " + i); });
// Posodobi besedilo stanja, če je pritisnjen kateri koli gumb if (pressed.length > 0) { status.textContent = "Pritisnjeno: " + pressed.join(", "); } }
// Nadaljuj zanko, če se razhroščevalnik izvaja if (teče) { rafId = requestAnimationFrame(updateGamepad); } }
Metoda classList.toggle() doda ali odstrani aktivni razred glede na to, ali je gumb pritisnjen, kar sproži naše sloge plasti CSS. 5. korak: obravnavajte dogodke na tipkovnici Ti poslušalci dogodkov poskrbijo, da nadomestna tipkovnica deluje: // ==================================== // UPRAVLJALNIKI DOGODKOV NA TIPKOVNICI // ====================================
document.addEventListener("keydown", (e) => { if (keyMap[e.key]) { // Obravnava enega ali več elementov if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("active")); } drugače { keyMap[e.key].classList.add("aktiven"); } status.textContent = "Pritisnjena tipka: " + e.key.toUpperCase(); } });
document.addEventListener("keyup", (e) => { if (keyMap[e.key]) { // Odstrani aktivno stanje, ko je tipka izpuščena if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("active")); } drugače { keyMap[e.key].classList.remove("aktiven"); } status.textContent = "Ključ izdan: " + e.key.toUpperCase(); } });
6. korak: dodajte kontrolnik Start/Stop Končno potrebujemo način za vklop in izklop razhroščevalnika: // ==================================== // VKLOP/IZKLOP RAZHROŠČEVALNIKA // ====================================
document.getElementById("toggle").addEventListener("klik", () => { tek = !tek; // Obrnite tekoče stanje
if (teče) { status.textContent = "Odpravljalnik napak teče..."; posodobiGamepad(); // Zaženite zanko posodabljanja } drugače { status.textContent = "Odpravljalnik napak neaktiven"; cancelAnimationFrame(rafId); // Ustavi zanko } });
Tako da, pritisnite gumb in zasveti. Potisnite palico in premakne se. To je to. Še nekaj: surove vrednosti. Včasih želite videti le številke, ne luči.
Na tej stopnji bi morali videti:
Preprost krmilnik na zaslonu, Gumbi, ki se odzivajo na vašo interakcijo z njimi, in Izbirni odčitek napak, ki prikazuje indekse pritisnjenih gumbov.
Da bo to manj abstraktno, je tukaj hitra predstavitev krmilnika na zaslonu, ki se odziva v realnem času:
Če zdaj pritisnete Začni snemanje, se beleži vse, dokler ne pritisnete Ustavi snemanje. 2. Izvoz podatkov v CSV/JSON Ko imamo dnevnik, ga bomo želeli shraniti.
1. korak: Ustvarite pomočnika za prenos Najprej potrebujemo pomožno funkcijo, ki obravnava prenose datotek v brskalniku: // ==================================== // POMOČNIK ZA PRENOS DATOTEKE // ====================================
funkcija downloadFile(filename, content, type = "text/plain") { // Ustvari blob iz vsebine const blob = new Blob([vsebina], { vrsta }); const url = URL.createObjectURL(blob);
// Ustvarite začasno povezavo za prenos in jo kliknite const a = document.createElement("a"); a.href = url; a.download = ime datoteke; a.klik();
// Očisti URL predmeta po prenosu setTimeout(() => URL.revokeObjectURL(url), 100); }
Ta funkcija deluje tako, da iz vaših podatkov ustvari Blob (binarni velik objekt), ustvari začasni URL zanj in programsko klikne povezavo za prenos. Čiščenje zagotavlja, da nam ne uhaja pomnilnik. 2. korak: Izvedite izvoz JSON JSON je popoln za ohranitev celotne strukture podatkov:
// ==================================== // IZVOZI KOT JSON // ====================================
document.getElementById("export-json").addEventListener("klik", () => { // Preverite, ali obstaja kaj za izvoz if (!frames.length) { console.warn("Ni posnetka na voljo za izvoz."); vrnitev; }
// Ustvari koristni tovor z metapodatki in okvirji const koristni tovor = { createdAt: new Date().toISOString(), okvirji };
// Prenos kot formatiran JSON downloadFile( "gamepad-log.json", JSON.stringify(payload, null, 2), "aplikacija/json" ); });
Format JSON ohranja vse strukturirano in enostavno razčlenljivo, zaradi česar je idealen za nalaganje nazaj v orodja za razvijalce ali skupno rabo s soigralci. 3. korak: Izvoz CSV Za izvoz CSV moramo hierarhične podatke zravnati v vrstice in stolpce:
//==================================== // IZVOZI KOT CSV // ====================================
document.getElementById("export-csv").addEventListener("klik", () => { // Preverite, ali obstaja kaj za izvoz if (!frames.length) { console.warn("Ni posnetka na voljo za izvoz."); vrnitev; }
// Gradi vrstico glave CSV (stolpci za časovni žig, vse gumbe, vse osi) 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";
// Sestavi podatkovne vrstice CSV const rows = frames.map(f => { const btnVals = f.buttons.map(b => b.value); return [f.t, ...btnVals, ...f.axes].join(","); }).pridruži se("\n");
// Prenesi kot CSV downloadFile("gamepad-log.csv", glava + vrstice, "besedilo/csv"); });
CSV je odličen za analizo podatkov, saj se odpre neposredno v Excelu ali Google Preglednicah in vam omogoča ustvarjanje grafikonov, filtriranje podatkov ali vizualno opazovanje vzorcev. Zdaj, ko so gumbi za izvoz vključeni, boste na plošči videli dve novi možnosti: Izvozi JSON in Izvozi CSV. JSON je dober, če želite vrniti neobdelani dnevnik nazaj v svoja orodja za razvijalce ali brskati po strukturi. Po drugi strani pa se CSV odpre neposredno v Excelu ali Google Preglednicah, tako da lahko vnesene podatke razporedite, filtrirate ali primerjate. Naslednja slika prikazuje, kako izgleda plošča s temi dodatnimi kontrolniki.
3. Sistem posnetkov Včasih ne potrebujete celotnega posnetka, le hiter "posnetek zaslona" stanja vnosa. Tu pomaga gumb Posnemi posnetek.
In JavaScript:
// ==================================== // NAREDI POSNETEK // ====================================
document.getElementById("posnetek").addEventListener("klik", () => { // Pridobite vse povezane igralne ploščice const pads = navigator.getGamepads(); const activePads = [];
// Preglejte in zajemite stanje vsake povezane igralne ploščice for (const gp blazinic) { če (!gp) nadaljuj; // Preskoči prazne reže
activePads.push({ id: gp.id, // Ime/model krmilnika časovni žig: performance.now(), gumbi: gp.buttons.map(b => ({ stisnjen: b.stisnjen, vrednost: b.vrednost })), osi: [...gp.osi] }); }
// Preverite, ali so bili najdeni igralni ploščki if (!activePads.length) { console.warn("Noben igralni plošček ni povezan za posnetek."); opozorilo ("Ni zaznanega krmilnika!"); vrnitev; }
// Prijavite in obvestite uporabnika console.log("Posnetek:", activePads); opozorilo (Posnetek narejen! Zajetih krmilnikov ${activePads.length}).); });
Posnetki zamrznejo natančno stanje vašega krmilnika v enem trenutku. 4. Ghost Input Replay Zdaj pa zabavno: ponovitev vnosa duhov. To posname dnevnik in ga vizualno predvaja, kot da bi fantomski predvajalnik uporabljal krmilnik.
JavaScript za ponovno predvajanje: // ==================================== // PONOVITEV DUHOV // ====================================
document.getElementById("replay").addEventListener("klik", () => { // Zagotovite, da imamo posnetek za ponovno predvajanje if (!frames.length) { opozorilo ("Ni posnetka za ponovno predvajanje!"); vrnitev; }
console.log("Začetek ponovitve duhov ...");
// Čas sledenja za sinhronizirano predvajanje let startTime = performance.now(); naj bo frameIndex = 0;
// Ponovno predvajaj zanko animacije funkcija step() { const zdaj = performance.now(); const preteklo = zdaj - začetni čas;
// Obdelaj vse okvirje, ki bi se morali pojaviti do zdaj medtem ko (frameIndex < frames.length && frames[frameIndex].t <= elapsed) { const okvir = okvirji[frameIndex];
// Posodobi uporabniški vmesnik s posnetimi stanji gumbov btnA.classList.toggle("aktiven",frame.buttons[0].pritisnjen); btnB.classList.toggle("aktiven",frame.buttons[1].pritisnjen); btnX.classList.toggle("aktiven",frame.buttons[2].pritisnjen);
// Posodobi prikaz stanja pustite pritisnjeno = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push("Button " + i); }); if (pressed.length > 0) { status.textContent = "Duh: " + pressed.join(", "); }
frameIndex++; }
// Nadaljuj zanko, če je več okvirjev if (frameIndex < frames.length) { requestAnimationFrame(korak); } drugače { console.log("Ponovikončano."); status.textContent = "Ponovno predvajanje končano"; } }
// Začetek ponovitve korak(); });
Da bi bilo odpravljanje napak nekoliko bolj praktično, sem dodal ponovno predvajanje duhov. Ko posnamete sejo, lahko pritisnete Replay in opazujete, kako uporabniški vmesnik to izvaja, skoraj kot fantomski igralec poganja ploščo. Za to se na plošči prikaže nov gumb Replay Ghost.
Pritisnite Snemaj, malo se poigrajte s krmilnikom, ustavite in nato ponovno predvajajte. Uporabniški vmesnik samo odmeva vse, kar ste naredili, kot duh, ki sledi vašim vnosom. Zakaj bi se obremenjevali s temi dodatki?
Snemanje/izvoz preizkuševalcem olajša prikaz, kaj se je točno zgodilo. Posnetki zamrznejo trenutek v času, kar je zelo uporabno, ko preganjate čudne napake. Ponovno predvajanje Ghost je odlično za vadnice, preverjanja dostopnosti ali samo primerjavo nastavitev nadzora.
Na tej točki to ni več le čista predstavitev, ampak nekaj, kar lahko dejansko uporabite. Primeri uporabe iz resničnega sveta Zdaj imamo ta razhroščevalnik, ki zmore marsikaj. Prikazuje vnos v živo, snema dnevnike, jih izvozi in celo ponovno predvaja stvari. Toda pravo vprašanje je: koga pravzaprav briga? Za koga je to koristno? Razvijalci iger Krmilniki so del naloge, a njihovo odpravljanje napak? Običajno bolečina. Predstavljajte si, da preizkušate kombinacijo borilne igre, na primer ↓ → + udarec. Namesto da bi molil, si dvakrat pritisnil na isti način, enkrat posnel in predvajal. Končano. Ali pa zamenjate dnevnike JSON s soigralcem, da preverite, ali se vaša koda za več igralcev enako odziva na njihovem računalniku. To je ogromno. Strokovnjaki za dostopnost Tale mi je pri srcu. Ne igrajo vsi s "standardnim" krmilnikom. Prilagodljivi krmilniki včasih oddajajo čudne signale. S tem orodjem lahko natančno vidite, kaj se dogaja. Učitelji, raziskovalci, kdorkoli. Lahko zgrabijo dnevnike, jih primerjajo ali drug ob drugem predvajajo vnose. Nenadoma postanejo nevidne stvari očitne. Testiranje zagotavljanja kakovosti Preizkuševalci običajno pišejo opombe, kot je "Tukaj sem zmečkal gumbe in se je pokvaril." Ni zelo koristno. Zdaj? Zajamejo lahko natančne stiskalnice, izvozijo dnevnik in ga pošljejo. Brez ugibanja. Vzgojitelji Če ustvarjate vadnice ali videe v YouTubu, je ponovno predvajanje duhov zlato. Lahko dobesedno rečete: "Evo, kar sem naredil s krmilnikom," medtem ko uporabniški vmesnik kaže, da se to dogaja. Naredi razlage bolj jasne. Beyond Games In ja, ne gre samo za igre. Ljudje so krmilnike uporabljali za robote, umetniške projekte in vmesnike za ljudi s posebnimi potrebami. Vsakič ista težava: kaj brskalnik dejansko vidi? Pri tem vam ni treba ugibati. Zaključek Odpravljanje napak pri vhodu krmilnika se je vedno zdelo kot letenje na slepo. Za razliko od DOM ali CSS ni vgrajenega inšpektorja za igralne ploščice; to so samo neobdelane številke v konzoli, ki se zlahka izgubijo v šumu. Z nekaj sto vrsticami HTML, CSS in JavaScript smo zgradili nekaj drugačnega:
Vizualni razhroščevalnik, ki naredi nevidne vnose vidne. Večplastni sistem CSS, ki ohranja uporabniški vmesnik čist in omogoča odpravljanje napak. Nabor izboljšav (snemanje, izvoz, posnetki, ponovno predvajanje duhov), ki ga povzdignejo iz demo v orodje za razvijalce.
Ta projekt prikazuje, kako daleč lahko pridete z mešanjem moči spletne platforme z malo ustvarjalnosti v kaskadnih slojih CSS. Orodje, ki sem ga pravkar v celoti razložil, je odprtokodno. Lahko klonirate repo GitHub in ga preizkusite sami. Še pomembneje pa je, da ga lahko naredite po svoje. Dodajte svoje plasti. Zgradite svojo logiko ponavljanja. Integrirajte ga s svojim prototipom igre. Ali pa ga celo uporabim na načine, ki si jih nisem zamislil. Za poučevanje, dostopnost ali analizo podatkov. Konec koncev ne gre le za odpravljanje napak igralnih ploščic. Gre za osvetljevanje skritih vnosov in vlivanje razvijalcem samozavesti za delo s strojno opremo, ki je splet še vedno ne sprejema v celoti. Torej, priključite krmilnik, odprite urejevalnik in začnite eksperimentirati. Morda boste presenečeni nad tem, kaj resnično lahko dosežeta vaš brskalnik in vaš CSS.