Kada priključite kontroler, gnječite dugmad, pomičete štapove, povlačite okidače... i kao programer, ne vidite ništa od toga. Pregledač to preuzima, naravno, ali osim ako ne bilježite brojeve u konzoli, nevidljiv je. To je glavobolja s Gamepad API-jem. Postoji godinama i zapravo je prilično moćan. Možete čitati dugmad, štapove, okidače, djela. Ali većina ljudi to ne dira. Zašto? Jer nema povratnih informacija. Nema panela u alatima za programere. Nema jasnog načina da saznate da li kontrolor uopće radi ono što mislite. Osećaj se kao da letiš na slepo. To me je dovoljno uznemirilo da napravim mali alat: Gamepad Cascade Debugger. Umjesto da buljite u izlaz konzole, dobijate živi, interaktivni pogled na kontroler. Pritisnite nešto i ono reaguje na ekranu. A sa CSS Cascade Layers, stilovi ostaju organizirani, tako da je čišće otklanjanje grešaka. U ovom postu ću vam pokazati zašto je otklanjanje grešaka u kontrolerima tolika muka, kako CSS pomaže u čišćenju i kako možete napraviti višekratni vizualni debugger za svoje projekte.
Čak i ako ste u mogućnosti da ih sve prijavite, brzo ćete završiti s nečitljivom neželjenom poštom na konzoli. na primjer: [0,0,1,0,0,0.5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]
Možete li reći koje je dugme pritisnuto? Možda, ali tek nakon što napregnete oči i propustite nekoliko unosa. Dakle, ne, otklanjanje grešaka ne dolazi lako kada je u pitanju čitanje ulaza. Problem 3: Nedostatak strukture Čak i ako sastavite brzi vizualizator, stilovi se mogu brzo zabrljati. Zadano, aktivno i stanje za otklanjanje grešaka mogu se preklapati, a bez jasne strukture vaš CSS postaje krhak i teško ga je proširiti. CSS kaskadni slojevi mogu pomoći. Oni grupišu stilove u "slojeve" koji su poredani po prioritetu, tako da prestanete da se borite protiv specifičnosti i nagađate: "Zašto se moj stil otklanjanja grešaka ne prikazuje?" Umjesto toga, održavate odvojene brige:
Baza: standardni, početni izgled kontrolera. Aktivno: Istaknuto za pritisnute dugmad i pomaknute palice. Otklanjanje grešaka: Prekrivači za programere (npr. numerička očitavanja, vodiči i tako dalje).
Ako bismo prema ovome definirali slojeve u CSS-u, imali bismo: /* od najnižeg do najvišeg prioriteta */ @baza sloja, aktivan, debug;
@baza sloja { /* ... */ }
@sloj aktivan { /* ... */ }
@layer debug { /* ... */ }
Budući da se svaki sloj slaže predvidljivo, uvijek znate koja pravila pobjeđuju. Ta predvidljivost čini otklanjanje grešaka ne samo lakšim, već i upravljivim. Pokrili smo problem (nevidljiv, neuredan unos) i pristup (vizualni debager izgrađen sa Cascade Layers). Sada ćemo proći kroz proces korak po korak za izgradnju debuggera. Koncept debuggera Najlakši način da skriveni unos učinite vidljivim je da ga jednostavno nacrtate na ekranu. To je ono što ovaj debuger radi. Dugmad, okidači i džojstici dobijaju vizuelnu sliku.
Pritisnite A: krug svijetli. Gurnite štap: krug klizi okolo. Povucite okidač do pola: šipka se puni do pola.
Sada ne buljite u 0s i 1s, već zapravo gledate kako kontroler reagira uživo. Naravno, kada počnete da gomilate stanja kao što su podrazumevano, pritisnuto, informacije o otklanjanju grešaka, možda čak i način snimanja, CSS postaje sve veći i složeniji. Tu dobro dolaze kaskadni slojevi. Evo skraćenog primjera: @baza sloja { .button { pozadina: #222; radijus granice: 50%; širina: 40px; visina: 40px; } }
@sloj aktivan { .button.pressed { pozadina: #0f0; /* svijetlo zelena */ } }
@layer debug { .button::after { sadržaj: attr(podatak-vrijednost); veličina fonta: 12px; boja: #fff; } }
Redoslijed slojeva je bitan: baza → aktivno → otklanjanje grešaka.
baza crta kontroler. aktivno upravlja pritisnutim stanjima. debug baca na preklapanja.
Ovako raskid znači da se ne borite sa čudnim ratovima specifičnosti. Svaki sloj ima svoje mjesto i uvijek znate šta pobjeđuje. Building It Out Hajdemo prvo da prikažemo nešto na ekranu. Ne mora da izgleda dobro – samo treba da postoji da bismo imali sa čime da radimo.
Gamepad Cascade Debugger
To su bukvalno samo kutije. Nije još uzbudljivo, ali nam daje ručice koje možemo kasnije uhvatiti sa CSS-om i JavaScript-om. U redu, ovdje koristim kaskadne slojeve jer održava stvari organiziranima kada dodate još stanja. Evo grubog prolaza:
/* ==================================== POSTAVLJANJE KASKADNIH SLOJEVA Redoslijed je važan: baza → aktivno → otklanjanje grešaka ==================================== */
/* Definiraj redoslijed slojeva unaprijed */ @baza sloja, aktivan, debug;
/* Sloj 1: Osnovni stilovi - zadani izgled */ @baza sloja { .button { pozadina: #333; radijus granice: 50%; širina: 70px; visina: 70px; displej: flex; justify-content: centar; align-items: centar; }
.pause { širina: 20px; visina: 70px; pozadina: #333; prikaz: inline-block; } }
/* Sloj 2: Aktivna stanja - upravlja pritisnutim dugmadima */ @sloj aktivan { .button.active { pozadina: #0f0; /* Svijetlo zeleno kada se pritisne */ transformacija: skala(1.1); /* Blago povećava dugme */ }
.pause.active { pozadina: #0f0; transformacija: scaleY(1.1); /* Rasteže se okomito kada se pritisne */ } }
/* Sloj 3: Prekrivači za otklanjanje grešaka - informacije o programeru */ @layer debug { .button::after { sadržaj: attr(podatak-vrijednost); /* Prikazuje numeričku vrijednost */ veličina fonta: 12px; boja: #fff; } }
Ljepota ovog pristupa je u tome što svaki sloj ima jasnu svrhu. Osnovni sloj nikada ne može nadjačati aktivno, a aktivni nikada ne može nadjačati debug, bez obzira na specifičnost. Ovo eliminiše ratove specifičnosti CSS-a koji obično muče alate za otklanjanje grešaka. Sada izgleda kao da neki klasteri sjede na tamnoj pozadini. Iskreno, nije tako loše.
Dodavanje JavaScripta JavaScript vrijeme. Ovo je mjesto gdje kontroler zapravo nešto radi. Izgradićemo ovo korak po korak. Korak 1: Postavite upravljanje stanjem Prvo, potrebne su nam varijable za praćenje stanja debuggera: // ==================================== // DRŽAVNO UPRAVLJANJE // ====================================
neka radi = false; // Prati da li je debugger aktivan let rafId; // Pohranjuje ID requestAnimationFramea za otkazivanje
Ove varijable kontroliraju petlju animacije koja kontinuirano čita unos gamepada. Korak 2: Zgrabite DOM reference Zatim dobijamo reference na sve HTML elemente koje ćemo ažurirati: // ==================================== // DOM ELEMENT REFERENCE // ====================================
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");
Spremanje ovih referenci unaprijed je efikasnije od stalnog upita DOM-a. Korak 3: Dodajte rezervnu tastaturu Za testiranje bez fizičkog kontrolera, mapiraćemo tastere tastature na dugmad: // ==================================== // NAZAD NA TASTATURU (za testiranje bez kontrolera) // ====================================
const keyMap = { "a": btnA, "b": btnB, "x": btnX, "p": [pause1, pause2] // 'p' tipka kontrolira obje trake za pauzu };
Ovo nam omogućava da testiramo korisnički interfejs pritiskom na tastere na tastaturi. Korak 4: Kreirajte glavnu petlju ažuriranja Evo gdje se magija dešava. Ova funkcija radi kontinuirano i čita stanje gamepada: // ==================================== // GLAVNA PETLJA ZA AŽURIRANJE GAMEPAD-a // ====================================
funkcija updateGamepad() { // Nabavite sve povezane gamepadove const gamepads = navigator.getGamepads(); if (!gamepads) se vraćaju;
// Koristi prvi povezani gamepad const gp = gamepads[0];
if (gp) { // Ažuriraj stanja gumba uključivanjem "aktivne" klase btnA.classList.toggle("aktivan", gp.buttons[0].pritisnut); btnB.classList.toggle("aktivan", gp.buttons[1].pritisnut); btnX.classList.toggle("aktivan", gp.buttons[2].pritisnut);
// Rukovanje dugmetom za pauzu (indeks dugmeta 9 na većini kontrolera) const pausePressed = gp.buttons[9].pritisnut; pause1.classList.toggle("active", pausePressed); pause2.classList.toggle("active", pausePressed);
// Napravite listu trenutno pritisnutih dugmadi za prikaz statusa pustiti pritisnut = []; gp.buttons.forEach((btn, i) => { ako (btn.pritisnuto)pressed.push("Dugme " + i); });
// Ažuriraj tekst statusa ako se pritisne bilo koje dugme if (pritisnuta.dužina > 0) { status.textContent = "Pritisnuto: " + pressed.join(", "); } }
// Nastavak petlje ako je debuger pokrenut if (trčanje) { rafId = requestAnimationFrame(updateGamepad); } }
Metoda classList.toggle() dodaje ili uklanja aktivnu klasu na osnovu toga da li je dugme pritisnuto, što pokreće naše stilove CSS slojeva. Korak 5: Rukovanje događajima na tastaturi Ovi slušatelji događaja omogućavaju funkcioniranje zamjenske tastature: // ==================================== // UPRAVLJAČI DOGAĐAJA NA TASTATURI // ====================================
document.addEventListener("keydown", (e) => { if (keyMap[e.key]) { // Rukovanje jednim ili više elemenata if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("active")); } ostalo { keyMap[e.key].classList.add("active"); } status.textContent = "Taster pritisnut: " + e.key.toUpperCase(); } });
document.addEventListener("keyup", (e) => { if (keyMap[e.key]) { // Ukloni aktivno stanje kada se otpusti ključ if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("active")); } ostalo { keyMap[e.key].classList.remove("active"); } status.textContent = "Ključ oslobođen: " + e.key.toUpperCase(); } });
Korak 6: Dodajte Start/Stop kontrolu Konačno, potreban nam je način da uključimo i isključimo debugger: // ==================================== // UKLJUČI/ISKLJUČI DEBUGGER // ====================================
document.getElementById("toggle").addEventListener("click", () => { trčanje = !trčanje; // Okreni stanje rada
if (trčanje) { status.textContent = "Debuger radi..."; updateGamepad(); // Pokreni petlju ažuriranja } ostalo { status.textContent = "Debuger neaktivan"; cancelAnimationFrame(rafId); // Zaustavi petlju } });
Dakle, da, pritisnite dugme i ono zasvetli. Gurnite štap i on se pomera. To je to. Još jedna stvar: sirove vrijednosti. Ponekad samo želite da vidite brojeve, a ne svetla.
U ovoj fazi, trebali biste vidjeti:
Jednostavan kontroler na ekranu, Dugmad koja reaguju dok ste u interakciji s njima, i Opciono očitavanje za otklanjanje grešaka koje prikazuje indekse pritisnutog dugmeta.
Da ovo bude manje apstraktno, evo kratke demonstracije kontrolera na ekranu koji reaguje u realnom vremenu:
Sada, pritiskom na Pokreni snimanje sve se evidentira dok ne pritisnete Zaustavi snimanje. 2. Izvoz podataka u CSV/JSON Kada dobijemo dnevnik, htjet ćemo ga sačuvati.
Korak 1: Kreirajte pomoćnika za preuzimanje Prvo, potrebna nam je pomoćna funkcija koja upravlja preuzimanjima datoteka u pretraživaču: // ==================================== // POMOĆNIK ZA PREUZIMANJE DATOTEKA // ====================================
funkcija downloadFile(naziv datoteke, sadržaj, tip = "tekst/običan") { // Kreirajte mrlju od sadržaja const blob = novi Blob([sadržaj], { tip }); const url = URL.createObjectURL(blob);
// Kreirajte privremenu vezu za preuzimanje i kliknite na nju const a = document.createElement("a"); a.href = url; a.download = ime datoteke; a.click();
// Očistite URL objekta nakon preuzimanja setTimeout(() => URL.revokeObjectURL(url), 100); }
Ova funkcija radi tako što kreira Blob (binarni veliki objekt) od vaših podataka, generira privremeni URL za njega i programski kliknete na vezu za preuzimanje. Čišćenje osigurava da ne propuštamo memoriju. Korak 2: Rukovanje JSON izvozom JSON je savršen za očuvanje kompletne strukture podataka:
// ==================================== // IZVOZ KAO JSON // ====================================
document.getElementById("export-json").addEventListener("click", () => { // Provjerite postoji li nešto za izvoz if (!frames.length) { console.warn("Snimak nije dostupan za izvoz."); povratak; }
// Kreirajte korisni teret sa metapodacima i okvirima const nosivost = { createdAt: novi datum().toISOString(), okviri };
// Preuzmi kao formatiran JSON downloadFile( "gamepad-log.json", JSON.stringify (korisno opterećenje, null, 2), "aplikacija/json" ); });
JSON format održava sve strukturiranim i lako raščlanjivim, što ga čini idealnim za ponovno učitavanje u dev alate ili dijeljenje sa saigračima. Korak 3: Rukovanje CSV izvozom Za izvoz CSV-a, moramo da sravnimo hijerarhijske podatke u redove i stupce:
//==================================== // IZVOZ KAO CSV // ====================================
document.getElementById("export-csv").addEventListener("click", () => { // Provjerite postoji li nešto za izvoz if (!frames.length) { console.warn("Snimak nije dostupan za izvoz."); povratak; }
// Napravi CSV red zaglavlja (kolone za vremensku oznaku, sva dugmad, sve ose) const headerButtons = okviri[0].buttons.map((_, i) => btn${i}); const headerAxes = frames[0].axes.map((_, i) => axis${i}); const header = ["t", ...headerButtons, ...headerAxes].join(",") + "\n";
// Napravi CSV redove podataka const rows = frames.map(f => { const btnVals = f.buttons.map(b => b.value); return [f.t, ...btnVals, ...f.axes].join(","); }).join("\n");
// Preuzmi kao CSV downloadFile("gamepad-log.csv", zaglavlje + redovi, "text/csv"); });
CSV je sjajan za analizu podataka jer se otvara direktno u Excelu ili Google tablicama, omogućavajući vam da kreirate grafikone, filtrirate podatke ili vizualno uočite uzorke. Sada kada su gumbi za izvoz uključeni, vidjet ćete dvije nove opcije na ploči: Izvezi JSON i Izvezi CSV. JSON je dobar ako želite da bacite neobrađenu evidenciju nazad u svoje programerske alate ili da provirujete po strukturi. CSV se, s druge strane, otvara direktno u Excel ili Google Sheets tako da možete grafikonirati, filtrirati ili upoređivati unose. Sljedeća slika prikazuje kako ploča izgleda s tim dodatnim kontrolama.
3. Sistem snimka Ponekad vam nije potreban kompletan snimak, samo brzi „snimak ekrana“ stanja unosa. Tu pomaže dugme Snimi Snapshot.
I JavaScript:
// ==================================== // TAKE SNAPSHOT // ====================================
document.getElementById("snapshot").addEventListener("click", () => { // Nabavite sve povezane gamepadove const pads = navigator.getGamepads(); const activePads = [];
// Prođite kroz petlju i snimite stanje svakog povezanog gamepada for (const gp of pads) { if (!gp) nastavi; // Preskoči prazna mjesta
activePads.push({ id: gp.id, // Ime/model kontrolera vremenska oznaka: performance.now(), dugmad: gp.buttons.map(b => ({ pritisnuto: b.pritisnuto, vrijednost: b.vrijednost })), osi: [...gp.axes] }); }
// Provjerite jesu li pronađeni gamepadi if (!activePads.length) { console.warn("Nijedan gamepad nije povezan za snimak."); alert("Nije otkriven kontroler!"); povratak; }
// Prijavite se i obavijestite korisnika console.log("Snapshot:", activePads); upozorenje(Snimak napravljen! Snimljeni ${activePads.length} kontroler(i).); });
Snimci zamrzavaju tačno stanje vašeg kontrolera u jednom trenutku. 4. Ghost Input Replay Sada za ono zabavno: ponavljanje unosa duhova. Ovo uzima dnevnik i reprodukuje ga vizuelno kao da fantomski igrač koristi kontroler.
JavaScript za replay: // ==================================== // GHOST REPLAY // ====================================
document.getElementById("replay").addEventListener("click", () => { // Osigurajte da imamo snimak za reproduciranje if (!frames.length) { alert("Nema snimanja za ponavljanje!"); povratak; }
console.log("Pokretanje reprize duha...");
// Praćenje vremena za sinhronizovanu reprodukciju neka startTime = performance.now(); neka frameIndex = 0;
// Replay animacija petlje function step() { const sada = performance.now(); const elapsed = sada - vrijeme početka;
// Obradi sve okvire koji su se do sada trebali pojaviti while (frameIndex < frames.length && frames[frameIndex].t <= proteklo) { const okvir = okviri[indeks okvira];
// Ažurirajte korisničko sučelje sa snimljenim stanjima gumba btnA.classList.toggle("aktivan", frame.buttons[0].pritisnut); btnB.classList.toggle("aktivan", frame.buttons[1].pritisnut); btnX.classList.toggle("aktivan", frame.buttons[2].pritisnut);
// Ažuriraj prikaz statusa pustiti pritisnut = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push("Button " + i); }); if (pritisnuta.dužina > 0) { status.textContent = "Ghost: " + pressed.join(", "); }
frameIndex++; }
// Nastavak petlje ako ima više okvira if (frameIndex < frames.length) { requestAnimationFrame(korak); } ostalo { console.log("Replaygotovo."); status.textContent = "Repriza je završena"; } }
// Pokreni reprizu korak(); });
Kako bih otklanjanje grešaka učinio malo praktičnijim, dodao sam ponavljanje duhova. Nakon što snimite sesiju, možete pritisnuti replay i gledati kako UI to glumi, gotovo kao da fantomski igrač pokreće pad. Novo dugme Replay Ghost se pojavljuje na panelu za ovo.
Pritisnite Snimi, malo se zabrljajte s kontrolerom, zaustavite se, pa ponovite. Korisničko sučelje samo odražava sve što ste radili, poput duha koji prati vaše unose. Zašto se mučiti sa ovim dodacima?
Snimanje/izvoz olakšava testerima da pokažu šta se tačno dogodilo. Snimci se na trenutak zamrznu, super korisno kada jurite čudne greške. Ghost replay je odličan za tutorijale, provjere pristupačnosti ili samo usporedbu postavki kontrole jedna pored druge.
U ovom trenutku, to više nije samo uredan demo, već nešto što biste zapravo mogli staviti na posao. Slučajevi korištenja u stvarnom svijetu Sada imamo ovaj debuger koji može mnogo toga. Prikazuje unos uživo, snima dnevnike, izvozi ih, pa čak i reproducira stvari. Ali pravo pitanje je: koga je zapravo briga? Kome je ovo korisno? Game Developers Kontroleri su dio posla, ali njihovo otklanjanje grešaka? Obično bol. Zamislite da testirate kombinaciju borbene igre, kao što je ↓ → + udarac. Umjesto da se molite, dvaput ste ga pritisnuli na isti način, jednom ga snimite i ponovite. Gotovo. Ili zamijenite JSON zapisnike sa saigračem da provjerite da li vaš kod za više igrača isto reagira na njihovoj mašini. To je ogromno. Praktičari pristupačnosti Ovaj mi je prirastao srcu. Ne igraju se svi sa “standardnim” kontrolerom. Adaptivni kontroleri ponekad izbacuju čudne signale. Pomoću ovog alata možete vidjeti šta se tačno dešava. Nastavnici, istraživači, bilo ko. Oni mogu hvatati zapise, upoređivati ih ili reproducirati ulaze jedan pored drugog. Odjednom, nevidljive stvari postaju očigledne. Ispitivanje kvaliteta Testeri obično pišu bilješke poput „Izgnječio sam dugmad ovdje i pokvarilo se“. Nije od velike pomoći. Sada? Oni mogu snimiti tačne pritiske, izvesti dnevnik i poslati ga. Bez nagađanja. Edukatori Ako pravite tutorijale ili YouTube videe, ghost replay je zlato. Možete doslovno reći: „Evo šta sam uradio sa kontrolerom“, dok korisnički interfejs pokazuje da se to dešava. Čini objašnjenja jasnijim. Beyond Games I da, ne radi se samo o igricama. Ljudi su koristili kontrolere za robote, umjetničke projekte i sučelja pristupačnosti. Svaki put isti problem: šta pretraživač zapravo vidi? Uz ovo, ne morate da pogađate. Zaključak Otklanjanje grešaka na ulazu kontrolera oduvijek je izgledalo kao da letite naslijepo. Za razliku od DOM-a ili CSS-a, nema ugrađenog inspektora za gamepadove; to su samo neobrađeni brojevi u konzoli, koji se lako gube u buci. Sa nekoliko stotina linija HTML-a, CSS-a i JavaScript-a, napravili smo nešto drugačije:
Vizuelni debager koji nevidljive ulaze čini vidljivim. Slojeviti CSS sistem koji održava korisničko sučelje čistim i otklanjanjem grešaka. Skup poboljšanja (snimanje, izvoz, snimke, ponavljanje duhova) koja ga podižu od demo-a do alata za razvojne programere.
Ovaj projekat pokazuje koliko daleko možete ići miješanjem moći Web platforme s malo kreativnosti u CSS Cascade Layers. Alat koji sam upravo objasnio u cijelosti je otvorenog koda. Možete klonirati GitHub repo i isprobati ga sami. Ali što je još važnije, možete ga sami napraviti. Dodajte svoje slojeve. Izgradite vlastitu logiku ponavljanja. Integrirajte ga sa svojim prototipom igre. Ili ga čak koristiti na načine koje nisam zamislio. Za nastavu, pristupačnost ili analizu podataka. Na kraju krajeva, ne radi se samo o otklanjanju grešaka na gamepadu. Radi se o rasvjetljavanju skrivenih ulaza i davanju samopouzdanja programerima da rade s hardverom koji web još uvijek ne prihvaća u potpunosti. Dakle, priključite svoj kontroler, otvorite uređivač i počnite eksperimentirati. Možda ćete biti iznenađeni šta vaš pretraživač i vaš CSS zaista mogu postići.