Apabila anda memalamkan pengawal, anda tumbuk butang, gerakkan kayu, tarik picu… dan sebagai pembangun, anda tidak melihatnya. Penyemak imbas mengambilnya, sudah tentu, tetapi melainkan anda mengelog nombor dalam konsol, ia tidak kelihatan. Itulah yang menyakitkan dengan API Gamepad. Ia telah wujud selama bertahun-tahun, dan ia sebenarnya cukup kuat. Anda boleh membaca butang, tongkat, pencetus, karya. Tetapi kebanyakan orang tidak menyentuhnya. kenapa? Kerana tiada maklum balas. Tiada panel dalam alat pembangun. Tiada cara yang jelas untuk mengetahui sama ada pengawal melakukan apa yang anda fikirkan. Rasa macam terbang buta. Itu cukup mengganggu saya untuk membina alat kecil: Gamepad Cascade Debugger. Daripada menatap keluaran konsol, anda mendapat paparan langsung dan interaktif pengawal. Tekan sesuatu dan ia bertindak balas pada skrin. Dan dengan CSS Cascade Layers, gaya kekal teratur, jadi lebih bersih untuk dinyahpepijat. Dalam siaran ini, saya akan menunjukkan kepada anda sebab pengawal penyahpepijatan amat menyusahkan, cara CSS membantu membersihkannya dan cara anda boleh membina penyahpepijat visual boleh guna semula untuk projek anda sendiri.
Walaupun anda boleh log semuanya, anda akan mendapat spam konsol yang tidak boleh dibaca dengan cepat. Contohnya: [0,0,1,0,0,0.5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]
Bolehkah anda tahu butang apa yang ditekan? Mungkin, tetapi hanya selepas menegangkan mata anda dan kehilangan beberapa input. Jadi, tidak, penyahpepijatan tidak datang dengan mudah semasa membaca input. Masalah 3: Kekurangan Struktur Walaupun anda menyusun visualizer pantas, gaya boleh menjadi kemas dengan cepat. Keadaan lalai, aktif dan nyahpepijat boleh bertindih, dan tanpa struktur yang jelas, CSS anda menjadi rapuh dan sukar untuk dikembangkan. Lapisan Lata CSS boleh membantu. Mereka mengumpulkan gaya ke dalam "lapisan" yang disusun mengikut keutamaan, jadi anda berhenti melawan kekhususan dan meneka, "Mengapa gaya nyahpepijat saya tidak ditunjukkan?" Sebaliknya, anda mengekalkan kebimbangan yang berasingan:
Pangkalan: Standard pengawal, penampilan awal. Aktif: Sorotan untuk butang yang ditekan dan kayu yang dialihkan. Nyahpepijat: Tindanan untuk pembangun (cth., bacaan angka, panduan dan sebagainya).
Jika kita mentakrifkan lapisan dalam CSS mengikut ini, kita akan mempunyai: /* keutamaan terendah hingga tertinggi */ @lapisan asas, aktif, nyahpepijat;
@lapisan asas { /* ... */ }
@lapisan aktif { /* ... */ }
@layer debug { /* ... */ }
Oleh kerana setiap lapisan disusun mengikut jangkaan, anda sentiasa tahu peraturan mana yang menang. Kebolehramalan itu menjadikan penyahpepijatan bukan sahaja lebih mudah, tetapi sebenarnya boleh diurus. Kami telah membincangkan masalah (input yang tidak kelihatan, tidak kemas) dan pendekatan (penyahpepijat visual yang dibina dengan Lapisan Lata). Sekarang kita akan melalui proses langkah demi langkah untuk membina penyahpepijat. Konsep Penyahpepijat Cara paling mudah untuk membuat input tersembunyi kelihatan adalah dengan hanya melukisnya pada skrin. Itulah yang dilakukan oleh penyahpepijat ini. Butang, pencetus dan kayu bedik semuanya mendapat visual.
Tekan A: Bulatan menyala. Mendorong kayu: Bulatan meluncur ke sekeliling. Tarik picu separuh jalan: Bar mengisi separuh jalan.
Kini anda tidak melihat 0s dan 1s, tetapi sebenarnya menonton pengawal bertindak balas secara langsung. Sudah tentu, sebaik sahaja anda mula menimbun keadaan seperti lalai, ditekan, maklumat nyahpepijat, mungkin juga mod rakaman, CSS mula menjadi lebih besar dan lebih kompleks. Di situlah lapisan lata berguna. Berikut ialah contoh yang dilucutkan: @lapisan asas { .butang { latar belakang: #222; jejari sempadan: 50%; lebar: 40px; ketinggian: 40px; } }
@lapisan aktif { .button.pressed { latar belakang: #0f0; /* hijau terang */ } }
@pepijat lapisan { .button::selepas { kandungan: attr(nilai-data); saiz fon: 12px; warna: #fff; } }
Urutan lapisan penting: asas → aktif → nyahpepijat.
asas menarik pengawal. pemegang aktif keadaan ditekan. debug membuang pada tindanan.
Memecahkannya seperti ini bermakna anda tidak melawan perang kekhususan yang aneh. Setiap lapisan mempunyai tempatnya, dan anda sentiasa tahu apa yang menang. Membinanya Mari dapatkan sesuatu pada skrin dahulu. Ia tidak perlu kelihatan baik — hanya perlu wujud supaya kita mempunyai sesuatu untuk diusahakan.
Penyahpepijat Cascade Pad Permainan
Itu betul-betul hanya kotak. Belum menarik lagi, tetapi ia memberi kami pegangan untuk diambil kemudian dengan CSS dan JavaScript. Okey, saya menggunakan lapisan lata di sini kerana ia memastikan bahan teratur sebaik sahaja anda menambah lebih banyak keadaan. Berikut adalah hantaran kasar:
/* ================================================== PERSEDIAAN LAPISAN TATA Urusan pesanan: asas → aktif → nyahpepijat =================================================== */
/* Tentukan susunan lapisan di muka */ @lapisan asas, aktif, nyahpepijat;
/* Lapisan 1: Gaya asas - penampilan lalai */ @lapisan asas { .butang { latar belakang: #333; jejari sempadan: 50%; lebar: 70px; ketinggian: 70px; paparan: flex; justify-content: pusat; align-item: tengah; }
.jeda { lebar: 20px; ketinggian: 70px; latar belakang: #333; paparan: inline-block; } }
/* Lapisan 2: Keadaan aktif - mengendalikan butang yang ditekan */ @lapisan aktif { .button.active { latar belakang: #0f0; /* Hijau terang apabila ditekan */ transform: skala (1.1); /* Membesarkan sedikit butang */ }
.pause.active { latar belakang: #0f0; transform: skalaY(1.1); /* Diregangkan secara menegak apabila ditekan */ } }
/* Lapisan 3: Tindanan nyahpepijat - maklumat pembangun */ @pepijat lapisan { .button::selepas { kandungan: attr(nilai-data); /* Menunjukkan nilai angka */ saiz fon: 12px; warna: #fff; } }
Keindahan pendekatan ini ialah setiap lapisan mempunyai tujuan yang jelas. Lapisan asas tidak boleh menimpa aktif, dan aktif tidak boleh mengatasi nyahpepijat, tanpa mengira kekhususan. Ini menghapuskan perang kekhususan CSS yang biasanya melanda alat penyahpepijatan. Kini nampaknya beberapa gugusan berada pada latar belakang gelap. Secara jujur, tidak terlalu teruk.
Menambah JavaScript masa JavaScript. Di sinilah pengawal sebenarnya melakukan sesuatu. Kami akan membina langkah demi langkah ini. Langkah 1: Sediakan Pengurusan Negeri Pertama, kita memerlukan pembolehubah untuk menjejaki keadaan penyahpepijat: // ================================================== // PENGURUSAN NEGERI // ==================================================
biarkan berlari = palsu; // Menjejaki sama ada penyahpepijat aktif biarkan rafId; // Menyimpan ID requestAnimationFrame untuk pembatalan
Pembolehubah ini mengawal gelung animasi yang membaca input pad permainan secara berterusan. Langkah 2: Dapatkan Rujukan DOM Seterusnya, kami mendapat rujukan kepada semua elemen HTML yang akan kami kemas kini: // ================================================== // RUJUKAN ELEMEN 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"); status const = document.getElementById("status");
Menyimpan rujukan ini di hadapan adalah lebih cekap daripada menanyakan DOM berulang kali. Langkah 3: Tambahkan Sandaran Papan Kekunci Untuk ujian tanpa pengawal fizikal, kami akan memetakan kekunci papan kekunci kepada butang: // ================================================== // KEYBOARD FALLBACK (untuk ujian tanpa pengawal) // ==================================================
const keyMap = { "a": btnA, "b": btnB, "x": btnX, "p": [pause1, pause2] // kekunci 'p' mengawal kedua-dua bar jeda };
Ini membolehkan kami menguji UI dengan menekan kekunci pada papan kekunci. Langkah 4: Buat Gelung Kemas Kini Utama Di sinilah keajaiban berlaku. Fungsi ini berjalan secara berterusan dan membaca keadaan pad permainan: // ================================================== // GULUNG KEMASKINI GAMEPAD UTAMA // ==================================================
function updateGamepad() { // Dapatkan semua pad permainan yang disambungkan const gamepads = navigator.getGamepads(); jika (!gamepads) kembali;
// Gunakan gamepad pertama yang disambungkan const gp = gamepads[0];
jika (gp) { // Kemas kini keadaan butang dengan menogol kelas "aktif". btnA.classList.toggle("aktif", gp.buttons[0].tekan); btnB.classList.toggle("aktif", gp.buttons[1].tekan); btnX.classList.toggle("aktif", gp.buttons[2].tekan);
// Kendalikan butang jeda (butang indeks 9 pada kebanyakan pengawal) const pausePressed = gp.buttons[9].pressed; pause1.classList.toggle("aktif", pausePressed); pause2.classList.toggle("aktif", pausePressed);
// Bina senarai butang yang sedang ditekan untuk paparan status biarkan ditekan = []; gp.buttons.forEach((btn, i) => { jika (btn.tekan)ditekan.push("Butang " + i); });
// Kemas kini teks status jika sebarang butang ditekan jika (ditekan.panjang > 0) { status.textContent = "Ditekan: " + pressed.join(", "); } }
// Teruskan gelung jika penyahpepijat sedang berjalan jika (berjalan) { rafId = requestAnimationFrame(updateGamepad); } }
Kaedah classList.toggle() menambah atau mengalih keluar kelas aktif berdasarkan sama ada butang ditekan, yang mencetuskan gaya lapisan CSS kami. Langkah 5: Mengendalikan Acara Papan Kekunci Pendengar acara ini menjadikan sandaran papan kekunci berfungsi: // ================================================== // PENGENDALI ACARA PAPAN KEkunci // ==================================================
document.addEventListener("keydown", (e) => { if (keyMap[e.key]) { // Mengendalikan elemen tunggal atau berbilang if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("aktif")); } lain { keyMap[e.key].classList.add("aktif"); } status.textContent = "Kekunci ditekan: " + e.key.toUpperCase(); } });
document.addEventListener("keyup", (e) => { if (keyMap[e.key]) { // Alih keluar keadaan aktif apabila kunci dilepaskan if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("aktif")); } lain { keyMap[e.key].classList.remove("aktif"); } status.textContent = "Kunci dikeluarkan: " + e.key.toUpperCase(); } });
Langkah 6: Tambah Kawalan Mula/Berhenti Akhir sekali, kita memerlukan cara untuk menghidupkan dan mematikan penyahpepijat: // ================================================== // TOGGLE DEBUGGER HIDUP/MATIKAN // ==================================================
document.getElementById("togol").addEventListener("klik", () => { berlari = !running; // Balikkan keadaan berjalan
jika (berjalan) { status.textContent = "Penyahpepijat berjalan..."; updateGamepad(); // Mulakan gelung kemas kini } lain { status.textContent = "Penyahpepijat tidak aktif"; cancelAnimationFrame(rafId); // Hentikan gelung } });
Jadi ya, tekan butang dan ia bersinar. Tolak kayu dan ia bergerak. itu sahaja. Satu perkara lagi: nilai mentah. Kadang-kadang anda hanya mahu melihat nombor, bukan lampu.
Pada peringkat ini, anda harus melihat:
Pengawal pada skrin yang mudah, Butang yang bertindak balas semasa anda berinteraksi dengannya, dan Bacaan nyahpepijat pilihan menunjukkan indeks butang ditekan.
Untuk menjadikan ini kurang abstrak, berikut ialah demo pantas pengawal pada skrin yang bertindak balas dalam masa nyata:
Sekarang, menekan Mula Rakaman mencatatkan segala-galanya sehingga anda menekan Hentikan Rakaman. 2. Mengeksport Data ke CSV/JSON Sebaik sahaja kami mempunyai log, kami akan mahu menyimpannya.
Langkah 1: Cipta Pembantu Muat Turun Pertama, kami memerlukan fungsi pembantu yang mengendalikan muat turun fail dalam penyemak imbas: // ================================================== // PEMBANTU MUAT TURUN FAIL // ==================================================
function downloadFile(nama fail, kandungan, jenis = "teks/plain") { // Buat gumpalan daripada kandungan gumpalan const = gumpalan baharu([kandungan], { jenis }); const url = URL.createObjectURL(blob);
// Cipta pautan muat turun sementara dan klik padanya const a = document.createElement("a"); a.href = url; a.muat turun = nama fail; a.klik();
// Bersihkan URL objek selepas muat turun setTimeout(() => URL.revokeObjectURL(url), 100); }
Fungsi ini berfungsi dengan mencipta Blob (objek besar binari) daripada data anda, menjana URL sementara untuknya dan mengklik pautan muat turun secara pemrograman. Pembersihan memastikan kami tidak membocorkan memori. Langkah 2: Kendalikan Eksport JSON JSON sesuai untuk mengekalkan struktur data yang lengkap:
// ================================================== // EKSPORT SEBAGAI JSON // ==================================================
document.getElementById("export-json").addEventListener("klik", () => { // Semak sama ada terdapat apa-apa untuk dieksport jika (!frames.length) { console.warn("Tiada rakaman tersedia untuk dieksport."); kembali; }
// Buat muatan dengan metadata dan bingkai muatan const = { createdAt: new Date().toISOString(), bingkai };
// Muat turun sebagai JSON yang diformatkan muat turunFail( "gamepad-log.json", JSON.stringify(muatan, null, 2), "aplikasi/json" ); });
Format JSON memastikan segala-galanya berstruktur dan mudah dihuraikan, menjadikannya sesuai untuk memuatkan semula ke dalam alat pembangun atau berkongsi dengan rakan sepasukan. Langkah 3: Kendalikan Eksport CSV Untuk eksport CSV, kami perlu meratakan data hierarki ke dalam baris dan lajur:
//================================================== // EKSPORT SEBAGAI CSV // ==================================================
document.getElementById("eksport-csv").addEventListener("klik", () => { // Semak sama ada terdapat apa-apa untuk dieksport jika (!frames.length) { console.warn("Tiada rakaman tersedia untuk dieksport."); kembali; }
// Bina baris pengepala CSV (lajur untuk cap masa, semua butang, semua paksi) 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";
// Bina baris data CSV baris const = frames.map(f => { const btnVals = f.buttons.map(b => b.value); kembali [f.t, ...btnVals, ...f.axes].join(","); }).join("\n");
// Muat turun sebagai CSV muat turunFail("gamepad-log.csv", pengepala + baris, "teks/csv"); });
CSV sangat bagus untuk analisis data kerana ia dibuka terus dalam Excel atau Helaian Google, membolehkan anda membuat carta, menapis data atau melihat corak secara visual. Memandangkan butang eksport sudah masuk, anda akan melihat dua pilihan baharu pada panel: Eksport JSON dan Eksport CSV. JSON bagus jika anda ingin membuang log mentah kembali ke dalam alat dev anda atau mencucuk sekitar struktur. CSV, sebaliknya, membuka terus ke dalam Excel atau Helaian Google supaya anda boleh carta, menapis atau membandingkan input. Rajah berikut menunjukkan rupa panel dengan kawalan tambahan tersebut.
3. Sistem Syot Kilat Kadangkala anda tidak memerlukan rakaman penuh, hanya "tangkapan skrin" pantas keadaan input. Di situlah butang Ambil Syot Kilat membantu.
Dan JavaScript:
// ================================================== // AMBIL GAMBAR // ==================================================
document.getElementById("snapshot").addEventListener("klik", () => { // Dapatkan semua pad permainan yang disambungkan pad const = navigator.getGamepads(); const activePads = [];
// Gelung dan tangkap keadaan setiap pad permainan yang disambungkan untuk (const gp pad) { jika (!gp) diteruskan; // Langkau slot kosong
activePads.push({ id: gp.id, // Nama/model pengawal cap waktu: performance.now(), butang: gp.buttons.map(b => ({ ditekan: b.ditekan, nilai: b.nilai })), paksi: [...gp.axes] }); }
// Semak sama ada sebarang pad permainan ditemui jika (!activePads.length) { console.warn("Tiada pad permainan disambungkan untuk syot kilat."); alert("Tiada pengawal dikesan!"); kembali; }
// Log dan maklumkan pengguna console.log("Snapshot:", activePads); makluman(Snapshot diambil! Ditangkap ${activePads.length} controller(s.); });
Syot kilat membekukan keadaan sebenar pengawal anda pada satu masa. 4. Main Semula Input Hantu Sekarang untuk yang menyeronokkan: main semula input hantu. Ini mengambil log dan memainkannya semula secara visual seolah-olah pemain hantu menggunakan pengawal.
JavaScript untuk ulang tayang: // ================================================== // LATAR SEMULA HANTU // ==================================================
document.getElementById("replay").addEventListener("click", () => { // Pastikan kami mempunyai rakaman untuk dimainkan semula jika (!frames.length) { alert("Tiada rakaman untuk dimainkan semula!"); kembali; }
console.log("Memulakan ulang tayang hantu...");
// Jejaki masa untuk main balik yang disegerakkan biarkan startTime = performance.now(); biarkan frameIndex = 0;
// Main semula gelung animasi fungsi langkah() { const now = performance.now(); const elapsed = now - startTime;
// Proses semua bingkai yang sepatutnya berlaku sekarang manakala (frameIndex < frames.length && frames[frameIndex].t <= elapsed) { bingkai const = bingkai[frameIndex];
// Kemas kini UI dengan keadaan butang yang dirakam btnA.classList.toggle("aktif", frame.buttons[0].tekan); btnB.classList.toggle("aktif", frame.buttons[1].tekan); btnX.classList.toggle("aktif", frame.buttons[2].tekan);
// Kemas kini paparan status biarkan ditekan = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push("Button " + i); }); jika (ditekan.panjang > 0) { status.textContent = "Ghost: " + pressed.join(", "); }
frameIndex++; }
// Teruskan gelung jika terdapat lebih banyak bingkai jika (frameIndex < frames.length) { requestAnimationFrame(langkah); } lain { console.log("Main semulaselesai."); status.textContent = "Main semula selesai"; } }
// Mulakan ulang tayang langkah(); });
Untuk menjadikan penyahpepijatan lebih praktikal, saya menambah main semula hantu. Sebaik sahaja anda telah merakam sesi, anda boleh menekan main semula dan menonton UI melakonkannya, hampir seperti pemain hantu sedang menjalankan pad. Butang Replay Ghost baharu muncul dalam panel untuk ini.
Tekan Rekod, main-main dengan pengawal sedikit, berhenti, kemudian main semula. UI hanya menggemakan semua yang anda lakukan, seperti hantu yang mengikuti input anda. Mengapa perlu bersusah payah dengan tambahan ini?
Rakaman/eksport memudahkan penguji untuk menunjukkan dengan tepat apa yang berlaku. Syot kilat membeku seketika, sangat berguna apabila anda mengejar pepijat ganjil. Main semula hantu bagus untuk tutorial, semakan kebolehaksesan atau hanya membandingkan tetapan kawalan sebelah menyebelah.
Pada ketika ini, ia bukan sekadar demo yang kemas lagi, tetapi sesuatu yang sebenarnya boleh anda lakukan. Kes Penggunaan Dunia Sebenar Kini kami mempunyai penyahpepijat ini yang boleh melakukan banyak perkara. Ia menunjukkan input langsung, merekodkan log, mengeksportnya, dan juga memainkan semula barangan. Tetapi persoalan sebenar ialah: siapa sebenarnya yang peduli? Untuk siapa ini berguna? Pembangun Permainan Pengawal adalah sebahagian daripada tugas, tetapi menyahpepijatnya? Selalunya sakit. Bayangkan anda sedang menguji kombo permainan pertarungan, seperti ↓ → + pukulan. Daripada berdoa, anda menekannya dengan cara yang sama dua kali, anda merakamnya sekali, dan memainkannya semula. Selesai. Atau anda menukar log JSON dengan rakan sepasukan untuk memeriksa sama ada kod berbilang pemain anda bertindak balas yang sama pada mesin mereka. Itu besar. Pengamal Kebolehcapaian Yang ini dekat di hati saya. Tidak semua orang bermain dengan pengawal "standard". Pengawal penyesuaian kadangkala membuang isyarat pelik. Dengan alat ini, anda boleh melihat dengan tepat apa yang berlaku. Guru-guru, penyelidik, sesiapa sahaja. Mereka boleh merebut log, membandingkannya atau memainkan semula input secara bersebelahan. Tiba-tiba, perkara yang tidak kelihatan menjadi jelas. Ujian Jaminan Kualiti Penguji biasanya menulis nota seperti "Saya tumbuk butang di sini dan ia pecah." Tidak sangat membantu. Sekarang? Mereka boleh menangkap penekan yang tepat, mengeksport log dan menghantarnya. Tak sangka. Pendidik Jika anda membuat tutorial atau video YouTube, main semula hantu adalah emas. Anda benar-benar boleh berkata, "Inilah yang saya lakukan dengan pengawal," sementara UI menunjukkan ia berlaku. Menjadikan penerangan lebih jelas. Beyond Games Dan ya, ini bukan hanya tentang permainan. Orang ramai telah menggunakan pengawal untuk robot, projek seni dan antara muka kebolehaksesan. Isu yang sama setiap kali: apakah yang sebenarnya dilihat oleh penyemak imbas? Dengan ini, anda tidak perlu meneka. Kesimpulan Menyahpepijat input pengawal sentiasa terasa seperti terbang buta. Tidak seperti DOM atau CSS, tiada pemeriksa terbina dalam untuk pad permainan; ia hanya nombor mentah dalam konsol, mudah hilang dalam bunyi bising. Dengan beberapa ratus baris HTML, CSS dan JavaScript, kami membina sesuatu yang berbeza:
Penyahpepijat visual yang menjadikan input halimunan kelihatan. Sistem CSS berlapis yang memastikan UI bersih dan boleh nyahpepijat. Satu set peningkatan (rakaman, eksport, syot kilat, main semula hantu) yang meningkatkannya daripada alat tunjuk cara kepada pembangun.
Projek ini menunjukkan sejauh mana anda boleh pergi dengan mencampurkan kuasa Platform Web dengan sedikit kreativiti dalam CSS Cascade Layers. Alat yang baru saya jelaskan secara keseluruhannya adalah sumber terbuka. Anda boleh mengklon repo GitHub dan mencubanya sendiri. Tetapi yang lebih penting, anda boleh membuatnya sendiri. Tambah lapisan anda sendiri. Bina logik ulang tayang anda sendiri. Sepadukannya dengan prototaip permainan anda. Atau menggunakannya dengan cara yang saya tidak bayangkan. Untuk pengajaran, kebolehcapaian atau analisis data. Pada penghujung hari, ini bukan hanya tentang menyahpepijat pad permainan. Ia mengenai menyerlahkan input tersembunyi dan memberi keyakinan kepada pembangun untuk bekerja dengan perkakasan yang masih belum diterima oleh web sepenuhnya. Jadi, pasangkan alat kawalan anda, buka editor anda dan mulakan percubaan. Anda mungkin terkejut dengan perkara yang benar-benar boleh dicapai oleh penyemak imbas anda dan CSS anda.