Kui ühendate kontrolleri, lööte nuppe, liigutate pulgakesi, tõmbate päästikuid... ja arendajana ei näe te sellest midagi. Brauser võtab selle kindlasti üles, kuid kui te konsooli numbreid ei logi, on see nähtamatu. See on Gamepad API-ga seotud peavalu. See on olnud juba aastaid ja see on tegelikult päris võimas. Saate lugeda nuppe, pulkasid, päästikuid, teoseid. Kuid enamik inimesi ei puuduta seda. Miks? Sest tagasisidet pole. Arendaja tööriistades pole paneeli. Puudub selge viis teada saada, kas kontroller isegi teeb seda, mida arvate. Tundub, nagu lendaks pimedana. See häiris mind piisavalt, et ehitada väike tööriist: Gamepad Cascade Debugger. Selle asemel, et vaadata konsooli väljundit, saate kontrollerist reaalajas interaktiivse ülevaate. Vajutage midagi ja see reageerib ekraanile. Ja CSS-i kaskaadikihtidega püsivad stiilid korrastatuna, nii et silumine on lihtsam. Selles postituses näitan teile, miks kontrollerite silumine on nii valus, kuidas CSS aitab seda puhastada ja kuidas saate oma projektide jaoks luua korduvkasutatava visuaalse siluri.
Isegi kui suudate need kõik logida, saate kiiresti konsooli loetamatu rämpspostiga. Näiteks: [0,0,1,0,0,0,5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]
Kas oskate öelda, mis nuppu vajutati? Võib-olla, kuid alles pärast silmade pingutamist ja mõne sisendi puudumist. Seega, ei, silumine ei ole sisendite lugemisel lihtne. Probleem 3: struktuuri puudumine Isegi kui koostate kiire visualiseerija, võivad stiilid kiiresti sassi minna. Vaike-, aktiivne- ja silumisolekud võivad kattuda ning ilma selge struktuurita muutub teie CSS rabedaks ja seda on raske laiendada. CSS-i kaskaadikihid võivad aidata. Nad rühmitavad stiilid prioriteedi järgi järjestatud "kihtideks", nii et te lõpetate võitluse spetsiifilisusega ja oletamisega: "Miks minu silumisstiili ei kuvata?" Selle asemel säilitate eraldi mured:
Alus: kontrolleri standardne esialgne välimus. Aktiivne: vajutatud nuppude ja liigutatud pulkade esiletõstmised. Silumine: ülekatted arendajatele (nt numbrilised näidud, juhendid jne).
Kui me määratleksime selle järgi CSS-is kihid, oleks meil: /* madalaimast kõrgeima prioriteedini */ @layer base, aktiivne, silumine;
@layer base { /* ... */ }
@kiht aktiivne { /* ... */ }
@layer silumine { /* ... */ }
Kuna iga kiht virnastab etteaimatavalt, teate alati, millised reeglid võidavad. See prognoositavus muudab silumise mitte ainult lihtsamaks, vaid ka tegelikult juhitavaks. Oleme käsitlenud probleemi (nähtamatu, segane sisend) ja lähenemisviisi (kaskaadkihtidega ehitatud visuaalne silur). Nüüd vaatame siluri loomise samm-sammult läbi. Siluri kontseptsioon Lihtsaim viis peidetud sisendi nähtavaks tegemiseks on see lihtsalt ekraanile joonistada. Seda see silur teeb. Nupud, päästikud ja juhtkangid saavad kõik visuaalselt.
Vajutage A: ring süttib. Tõuge pulgale: ring libiseb ümber. Tõmmake päästik poolenisti: riba täitub poolenisti.
Nüüd ei vaata te 0-d ja 1-sid, vaid tegelikult vaatate kontrollerit otseülekandes. Muidugi, kui hakkate koguma olekuid, nagu vaike-, vajutatud, silumisinfo, võib-olla isegi salvestusrežiim, hakkab CSS muutuma suuremaks ja keerukamaks. Siin tulevad kaskaadikihid kasuks. Siin on eemaldatud näide: @layer base { .button { taust: #222; piiri raadius: 50%; laius: 40 pikslit; kõrgus: 40 pikslit; } }
@kiht aktiivne { .button.pressed { taust: #0f0; /* heleroheline */ } }
@layer silumine { .button::after { sisu: attr(andmeväärtus); fondi suurus: 12 pikslit; värv: #fff; } }
Kihtide järjekord on oluline: baas → aktiivne → silumine.
alus tõmbab kontrollerit. aktiivsed käepidemed pressitud olekud. silumisvisked ülekatetele.
Selle niimoodi lõhkumine tähendab, et te ei võitle veidrate spetsiifika sõdadega. Igal kihil on oma koht ja sa tead alati, mis võidab. Selle välja ehitamine Toome esmalt midagi ekraanile. See ei pea hea välja nägema – see peab lihtsalt eksisteerima, et meil oleks, millega töötada.
Mängupuldi kaskaadisiluja
See on sõna otseses mõttes lihtsalt kastid. Pole veel põnev, kuid see annab meile käepidemed, mida hiljem CSS-i ja JavaScriptiga haarata. Okei, ma kasutan siin kaskaadkihte, sest see hoiab asjad korras, kui lisate rohkem olekuid. Siin on ligikaudne läbimine:
/* ==================================== KASKAADI KIHTE SEADISTAMINE Järjestus on oluline: baas → aktiivne → silumine ==================================== */
/* Määrake kihtide järjekord ette */ @layer base, aktiivne, silumine;
/* Kiht 1: põhistiilid – vaikeilme */ @layer base { .button { taust: #333; piiri raadius: 50%; laius: 70 pikslit; kõrgus: 70 pikslit; ekraan: flex; õigustama-sisu: keskpunkt; joonda-elemendid: keskel; }
.pause { laius: 20 pikslit; kõrgus: 70 pikslit; taust: #333; ekraan: inline-block; } }
/* Kiht 2: aktiivsed olekud – käsitleb vajutatud nuppe */ @kiht aktiivne { .button.active { taust: #0f0; /* Vajutamisel heleroheline */ teisendus: skaala(1.1); /* Suurendab veidi nuppu */ }
.pause.active { taust: #0f0; teisendus: scaleY(1.1); /* Vajutades venib vertikaalselt */ } }
/* 3. kiht: silumisülekatted – arendaja teave */ @layer silumine { .button::after { sisu: attr(andmeväärtus); /* Näitab numbrilist väärtust */ fondi suurus: 12 pikslit; värv: #fff; } }
Selle lähenemisviisi ilu seisneb selles, et igal kihil on selge eesmärk. Aluskiht ei saa kunagi alistada aktiivset ja aktiivne ei saa kunagi alistada silumist, olenemata spetsiifikast. See välistab CSS-i spetsiifilisuse sõjad, mis tavaliselt silumistööriistu vaevavad. Nüüd tundub, et mõned klastrid istuvad tumedal taustal. Ausalt, mitte väga halb.
JavaScripti lisamine JavaScripti aeg. See on koht, kus kontroller tegelikult midagi teeb. Ehitame selle samm-sammult. 1. samm: seadistage riigihaldus Esiteks vajame siluri oleku jälgimiseks muutujaid: // ==================================== // RIIGI JUHTIMINE // ====================================
lase jooksma = false; // Jälgib, kas silur on aktiivne lase rafId; // Salvestab tühistamiseks requestAnimationFrame ID
Need muutujad juhivad animatsioonisilmust, mis loeb pidevalt mängupuldi sisendit. 2. samm: hankige DOM-i viited Järgmisena saame viited kõigile HTML-i elementidele, mida me värskendame: // ==================================== // KUPPELEMENTIDE VIITED // ====================================
const btnA = document.getElementById("btn-a"); const btnB = document.getElementById("btn-b"); const btnX = document.getElementById("btn-x"); const paus1 = document.getElementById("paus1"); const paus2 = document.getElementById("paus2"); const olek = document.getElementById("staatus");
Nende viidete ette salvestamine on tõhusam kui DOM-i korduv päringute esitamine. 3. samm: lisage klaviatuuri varu Ilma füüsilise kontrollerita testimiseks seostame klaviatuuri klahvid nuppudega: // ==================================== // KEYBOARD FALLBACK (testimiseks ilma kontrollerita) // ====================================
const keyMap = { "a": btnA, "b": btnB, "x": btnX, "p": [paus1, paus2] // klahv 'p' juhib mõlemat pausiriba };
See võimaldab meil testida kasutajaliidest, vajutades klaviatuuri klahve. 4. samm: looge peamine värskendustsükkel Siin toimub maagia. See funktsioon töötab pidevalt ja loeb mängupuldi olekut: // ==================================== // MÄNGUPADI PEAMISE VÄRSKENDUSE LOOP // ====================================
function updateGamepad() { // Hangi kõik ühendatud mängupuldid const gamepads = navigator.getGamepads(); if (!gamepads) return;
// Kasutage esimest ühendatud mängupulti const gp = mängupuldid[0];
if (gp) { // Uuenda nupu olekuid, lülitades sisse klassi "aktiivne". btnA.classList.toggle("aktiivne", gp.buttons[0].pressed); btnB.classList.toggle("aktiivne", gp.buttons[1].pressed); btnX.classList.toggle("aktiivne", gp.buttons[2].pressed);
// Käsitsege pausi nuppu (enamikul kontrolleritel nupu register 9) const pausePressed = gp.buttons[9].pressed; pause1.classList.toggle("aktiivne", pausPressed); pause2.classList.toggle("aktiivne", pausPressed);
// Koostage oleku kuvamiseks praegu vajutatud nuppude loend las pressitud = []; gp.buttons.forEach((btn, i) => { kui (btn.pressed)pressed.push("Nupp " + i); });
// Kui vajutate mõnda nuppu, värskendage olekuteksti if (pressed.length > 0) { status.textContent = "Vajutatud: " + pressed.join(", "); } }
// Jätkake silmust, kui silur töötab if (jookseb) { rafId = requestAnimationFrame(värskenda mängupulti); } }
Meetod classList.toggle() lisab või eemaldab aktiivse klassi vastavalt sellele, kas nuppu vajutatakse, mis käivitab meie CSS-kihi stiilid. 5. toiming: käsitlege klaviatuurisündmusi Need sündmusekuulajad panevad klaviatuuri varu toimima: // ==================================== // KLAVIAUURI SÜNDMUSTE KÄSITLIJAD // ====================================
document.addEventListener("keydown", (e) => { if (keyMap[e.key]) { // Käsitleda ühte või mitut elementi if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("aktiivne")); } muu { keyMap[e.key].classList.add("aktiivne"); } status.textContent = "Klahv vajutatud: " + e.key.toUpperCase(); } });
document.addEventListener("võti", (e) => { if (keyMap[e.key]) { // Eemaldage aktiivne olek, kui võti vabastatakse if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("aktiivne")); } muu { keyMap[e.key].classList.remove("aktiivne"); } status.textContent = "Võti vabastatud: " + e.key.toUpperCase(); } });
6. samm: lisage Start/Stop Control Lõpuks vajame siluri sisse- ja väljalülitamiseks viisi: // ==================================== // LÜLITA SILUJA SISSE/VÄLJA // ====================================
document.getElementById("toggle").addEventListener("click", () => { jooksmine = !jooksmine; // Töötava oleku pööramine
if (jookseb) { status.textContent = "Siluja töötab..."; updateGamepad(); // Käivitage värskendustsükkel } muu { status.textContent = "Siluja mitteaktiivne"; cancelAnimationFrame(rafId); // Peatage silmus } });
Nii et jah, vajuta nuppu ja see helendab. Lükka pulka ja see liigub. see on kõik. Veel üks asi: toored väärtused. Mõnikord tahad lihtsalt näha numbreid, mitte tulesid.
Selles etapis peaksite nägema:
Lihtne ekraanikontroller, Nupud, mis reageerivad nendega suhtlemisel ja Valikuline silumisnäit, mis näitab vajutatud nuppude indekseid.
Selle vähem abstraktseks muutmiseks on siin ekraanikontrolleri kiire demo, mis reageerib reaalajas:
Nüüd, kui vajutate nuppu Alusta salvestamist, logitakse kõik, kuni vajutate nuppu Stop Recording. 2. Andmete eksportimine CSV/JSON-vormingusse Kui meil on logi olemas, tahame selle salvestada.
1. samm: looge allalaadimisabiline Esiteks vajame abifunktsiooni, mis haldab failide allalaadimist brauseris: // ==================================== // FAILIDE ALLALAADIMISE ABI // ====================================
function downloadFile(failinimi, sisu, tüüp = "text/plain") { // Looge sisust plekk const blob = new Blob([sisu], {tüüp }); const url = URL.createObjectURL(blob);
// Looge ajutine allalaadimislink ja klõpsake seda const a = document.createElement("a"); a.href = url; a.download = failinimi; a.click();
// Pärast allalaadimist puhastage objekti URL setTimeout(() => URL.revokeObjectURL(url), 100); }
See funktsioon toimib, luues teie andmetest Blobi (suure kahendobjekti), genereerides sellele ajutise URL-i ja klõpsates programmiliselt allalaadimislingil. Puhastamine tagab, et me ei leki mälu. 2. toiming: käsitlege JSON-i eksportimist JSON sobib suurepäraselt täieliku andmestruktuuri säilitamiseks:
// ==================================== // EXPORT AS JSON // ====================================
document.getElementById("export-json").addEventListener("click", () => { // Kontrollige, kas on midagi eksportida if (!frames.length) { console.warn("Eksportimiseks pole salvestist saadaval."); tagastamine; }
// Looge kasulik koormus metaandmete ja raamidega konst kasulik koormus = { CreatedAt: new Date().toISOString(), raamid };
// Laadi alla vormindatud JSON-vormingus downloadFile( "gamepad-log.json", JSON.stringify(kasutav koormus, null, 2), "rakendus/json" ); });
JSON-vorming hoiab kõik struktureeritud ja hõlpsasti sõelutavana, muutes selle ideaalseks arendustööriistadesse tagasilaadimiseks või meeskonnakaaslastega jagamiseks. 3. toiming: tegelege CSV-ekspordiga CSV-ekspordiks peame lamendama hierarhilised andmed ridadeks ja veergudeks.
//==================================== // EXPORT AS CSV // ====================================
document.getElementById("export-csv").addEventListener("click", () => { // Kontrollige, kas on midagi eksportida if (!frames.length) { console.warn("Eksportimiseks pole salvestist saadaval."); tagastamine; }
// Ehita CSV päise rida (ajatempli veerud, kõik nupud, kõik teljed) const headerButtons = frames[0].buttons.map((_, i) => btn${i}); const headerAxes = frames[0].axes.map((_, i) => telg${i}); const header = ["t", ...headerButtons, ...headerAxes].join(",") + "\n";
// Koostage CSV andmeridad const read = frames.map(f => { const btnVals = f.buttons.map(b => b.value); return [f.t, ...btnVals, ...f.axes].join(","); }).join("\n");
// Laadi alla CSV-vormingus downloadFile("gamepad-log.csv", päis + read, "text/csv"); });
CSV on andmeanalüüsi jaoks suurepärane, kuna see avaneb otse Excelis või Google'i arvutustabelites, võimaldades teil luua diagramme, filtreerida andmeid või määrata visuaalselt mustreid. Nüüd, kui ekspordinupud on sees, näete paneelil kahte uut valikut: Ekspordi JSON ja Ekspordi CSV. JSON on tore, kui soovite visata töötlemata logi tagasi oma arendustööriistadesse või tuhnida struktuuri ümber. CSV seevastu avaneb otse Excelisse või Google'i arvutustabelitesse, et saaksite diagramme koostada, filtreerida või sisendeid võrrelda. Järgmine joonis näitab, kuidas paneel nende lisajuhtelementidega välja näeb.
3. Snapshot System Mõnikord pole vaja täielikku salvestust, vaid kiiret "ekraanitõmmist" sisendolekutest. Siin aitab nupp Take Snapshot.
Ja JavaScript:
// ==================================== // TEE SNAPSSHOT // ====================================
document.getElementById("snapshot").addEventListener("click", () => { // Hangi kõik ühendatud mängupuldid const pads = navigator.getGamepads(); const activePads = [];
// Sirvige läbi ja jäädvustage iga ühendatud mängupuldi olekut for (const gp of pads) { if (!gp) jätka; // Jäta tühjad kohad vahele
activePads.push({ id: gp.id, // Kontrolleri nimi/mudel ajatempel: performance.now(), nupud: gp.buttons.map(b => ({ pressitud: b.pressitud, väärtus: b.väärtus })), teljed: [...gp.axes] }); }
// Kontrollige, kas leiti mängupulte if (!activePads.length) { console.warn("Hetktõmmise jaoks pole mängupulte ühendatud."); alert("Ühtegi kontrollerit ei tuvastatud!"); tagastamine; }
// Logi sisse ja teavita kasutajat console.log("Snapshot:", activePads); alert(Hetktõmmis tehtud! Jäädvustatud ${activePads.length} kontroller(id).); });
Snapshots fikseerib teie kontrolleri täpse oleku ühel ajahetkel. 4. Ghost Input Replay Nüüd lõbusamaks: kummitussisendi kordus. See võtab logi ja esitab selle visuaalselt, nagu kasutaks fantoommängija kontrollerit.
JavaScript taasesitamiseks: // ==================================== // KUMMISTUSTE KORDUS // ====================================
document.getElementById("replay").addEventListener("click", () => { // Veenduge, et meil oleks taasesitamiseks salvestis if (!frames.length) { alert("Taasesitamiseks pole salvestust!"); tagastamine; }
console.log("Kummituskorduse alustamine...");
// Loo ajastus sünkroonitud taasesituseks las startTime = performance.now(); olgu frameIndex = 0;
// Animatsioonitsükli taasesitamine function samm() { const now = performance.now(); const möödunud = nüüd - algusaeg;
// Töötle kõik kaadrid, mis oleksid pidanud praeguseks toimuma while (frameIndex < frames.length && frames[frameIndex].t <= kulunud) { const kaader = kaadrid[kaadriindeks];
// Värskendage kasutajaliidest salvestatud nupu olekutega btnA.classList.toggle("active", frame.buttons[0].pressed); btnB.classList.toggle("active", frame.buttons[1].pressed); btnX.classList.toggle("active", frame.buttons[2].pressed);
// Värskenda olekukuva las pressitud = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push("Button " + i); }); if (pressed.length > 0) { status.textContent = "Ghost: " + pressed.join(", "); }
frameIndex++; }
// Jätka tsüklit, kui kaadreid on rohkem if (frameIndex < frames.length) { requestAnimationFrame(samm); } muu { console.log("Esita uuestivalmis."); status.textContent = "Taasesitus on lõpetatud"; } }
// Käivitage kordus samm (); });
Et silumine oleks praktilisem, lisasin kummitusliku korduse. Kui olete seansi salvestanud, võite vajutada taasesitust ja vaadata, kuidas kasutajaliides seda mängib, peaaegu nagu fantoompleier mängib padjaga. Selle jaoks kuvatakse paneelil uus nupp Replay Ghost.
Vajutage nuppu Salvesta, segage natuke kontrolleriga, lõpetage ja esitage uuesti. Kasutajaliides lihtsalt kordab kõike, mida te tegite, nagu kummitus, mis jälgiks teie sisendeid. Milleks nende lisadega vaeva näha?
Salvestamine/eksport võimaldab testijatel lihtsalt näidata, mis juhtus. Hetketõmmised takerduvad hetkeks, mis on ülikasulik, kui jahtite veidraid vigu. Ghost-replay sobib suurepäraselt õpetuste, juurdepääsetavuse kontrollimiseks või lihtsalt juhtimisseadistuste kõrvuti võrdlemiseks.
Praegu pole see enam lihtsalt puhas demo, vaid midagi, mida saate tegelikult tööle panna. Reaalses maailmas kasutatavad juhtumid Nüüd on meil see silur, mis suudab palju ära teha. See näitab otsesisendit, salvestab logisid, ekspordib need ja isegi taasesitab asju. Kuid tegelik küsimus on: keda see tegelikult huvitab? Kellele see kasulik on? Mängude arendajad Kontrollerid on osa tööst, kuid nende silumine? Tavaliselt valu. Kujutage ette, et testite võitlusmängu kombinatsiooni, näiteks ↓ → + löök. Palvetamise asemel vajutasite seda kaks korda samal viisil, salvestate selle üks kord ja esitate uuesti. Valmis. Või vahetate JSON-logid meeskonnakaaslasega, et kontrollida, kas teie mitme mängijaga kood reageerib nende masinas samamoodi. See on tohutu. Juurdepääsetavuse praktikud See on mulle südamelähedane. Mitte igaüks ei mängi "standardse" kontrolleriga. Kohanduvad kontrollerid annavad mõnikord välja imelikke signaale. Selle tööriistaga näete täpselt, mis toimub. Õpetajad, teadlased, kes iganes. Nad saavad haarata logisid, võrrelda neid või taasesitada sisendeid kõrvuti. Järsku muutuvad nähtamatud asjad ilmseks. Kvaliteedi tagamise testimine Testijad kirjutavad tavaliselt selliseid märkmeid nagu "Purustasin siin nupud ja see läks katki." Ei ole väga kasulik. Nüüd? Nad suudavad tabada täpsed pressid, eksportida logi ja saata selle ära. Ei mingit oletamist. Kasvatajad Kui teete õpetusi või YouTube'i videoid, on kummituste taasesitus kuldne. Võite sõna otseses mõttes öelda: "Seda ma tegin kontrolleriga", samal ajal kui kasutajaliides näitab, et see juhtub. Teeb selgitused palju selgemaks. Väljaspool mänge Ja jah, see ei puuduta ainult mänge. Inimesed on kasutanud kontrollereid robotite, kunstiprojektide ja juurdepääsetavuse liideste jaoks. Iga kord sama probleem: mida brauser tegelikult näeb? Sellega ei pea te arvama. Järeldus Kontrolleri sisendi silumine on alati tundunud pimedana. Erinevalt DOM-ist või CSS-ist pole mängupuldi jaoks sisseehitatud inspektorit; need on lihtsalt toored numbrid konsoolis, mis lähevad mürasse kergesti kaduma. Mõnesaja rea HTML-i, CSS-i ja JavaScriptiga lõime midagi muud:
Visuaalne silur, mis muudab nähtamatud sisendid nähtavaks. Kihiline CSS-süsteem, mis hoiab kasutajaliidese puhta ja silutava. Täiustuste komplekt (salvestamine, eksportimine, hetktõmmised, kummituste taasesitus), mis tõstavad selle demost arendaja tööriistaks.
See projekt näitab, kui kaugele võite minna, kui kombineerite veebiplatvormi võimsust vähese loovusega CSS-i kaskaadikihtides. Tööriist, mida just selgitasin tervikuna, on avatud lähtekoodiga. Saate kloonida GitHubi repo ja proovida seda ise. Kuid mis veelgi olulisem, saate selle ise teha. Lisage oma kihid. Looge oma kordusloogika. Integreerige see oma mängu prototüübiga. Või isegi kasutada seda viisil, mida ma pole ette kujutanud. Õpetamiseks, juurdepääsetavuseks või andmete analüüsiks. Lõppkokkuvõttes pole see ainult mängupuldi silumine. Selle eesmärk on valgustada peidetud sisendeid ja anda arendajatele kindlustunne töötada riistvaraga, mida veeb ikka veel täielikult omaks ei võta. Seega ühendage oma kontroller, avage redaktor ja alustage katsetamist. Võite olla üllatunud, mida teie brauser ja CSS tõeliselt suudavad.