Коли ви підключаєте контролер, ви натискаєте кнопки, пересуваєте ручки, натискаєте на курки… і як розробник ви нічого цього не бачите. Звичайно, веб-переглядач підбирає це, але якщо ви не реєструєте числа в консолі, він невидимий. Це головний біль із Gamepad API. Він існує вже багато років, і насправді він досить потужний. Ви можете читати кнопки, палички, тригери, роботи. Але більшість людей до нього не торкаються. чому Тому що немає зворотного зв'язку. Немає панелі в інструментах розробника. Немає чіткого способу дізнатися, чи контролер взагалі робить те, що ви думаєте. Таке відчуття, як літати наосліп. Мене це засмутило настільки, що я створив невеликий інструмент: Gamepad Cascade Debugger. Замість того, щоб дивитися на вихід консолі, ви отримуєте живий інтерактивний вигляд контролера. Натисніть щось, і воно відреагує на екран. А завдяки каскадним шарам CSS стилі залишаються впорядкованими, тож налагоджувати їх легше. У цій публікації я покажу вам, чому налагодження контролерів є таким болісним, як CSS допомагає це очистити, і як ви можете створити багаторазовий візуальний відладчик для власних проектів.
Навіть якщо ви зможете зареєструвати їх усі, ви швидко отримаєте нечитабельний консольний спам. Наприклад: [0,0,1,0,0,0,5,0,...] [0,0,0,0,1,0,0,...] [0,0,1,0,0,0,0,...]
Чи можете ви сказати, яку кнопку натиснули? Можливо, але лише після напруження очей і пропуску кількох входів. Отже, ні, налагодження не дається легко, коли мова йде про читання вхідних даних. Проблема 3: Відсутність структури Навіть якщо ви зібрали швидкий візуалізатор, стилі можуть швидко зіпсуватися. Стандартний, активний і налагоджувальний стани можуть збігатися, і без чіткої структури ваш CSS стає крихким і його важко розширити. Каскадні шари CSS можуть допомогти. Вони групують стилі в «шари», упорядковані за пріоритетом, тож ви перестаєте боротися зі специфічністю та гадати: «Чому мій стиль налагодження не відображається?» Натомість ви зберігаєте окремі проблеми:
Основа: стандарт контролера, початковий вигляд. Активний: виділення для натиснутих кнопок і переміщених джойстиків. Налагодження: накладення для розробників (наприклад, числові показання, посібники тощо).
Якби ми визначили шари в CSS відповідно до цього, ми мали б: /* від нижчого до найвищого пріоритету */ @layer base, active, debug;
@layer base { /* ... */ }
@layer активний { /* ... */ }
@layer debug { /* ... */ }
Оскільки кожен шар складається передбачувано, ви завжди знаєте, які правила виграють. Ця передбачуваність робить налагодження не просто легшим, але й фактично керованим. Ми розглянули проблему (невидиме, брудне введення) і підхід (візуальний налагоджувач, створений за допомогою каскадних шарів). Тепер ми крок за кроком розглянемо процес створення відладчика. Концепція відладчика Найпростіший спосіб зробити прихований вхід видимим — просто намалювати його на екрані. Ось що робить цей налагоджувач. Кнопки, тригери та джойстики отримують візуальний вигляд.
Натисніть A: засвітиться коло. Підштовхніть палицю: коло ковзає. Натисніть на курок наполовину: смужка заповнюється наполовину.
Тепер ви не дивитеся на 0 і 1, а фактично спостерігаєте за реакцією контролера. Звичайно, коли ви починаєте нагромаджувати такі стани, як за замовчуванням, натиснуто, інформацію про налагодження, можливо, навіть режим запису, CSS починає ставати більшим і складнішим. Ось тут стають у пригоді каскадні шари. Ось скорочений приклад: @layer base { .button { фон: #222; border-radius: 50%; ширина: 40 пікселів; висота: 40 пікселів; } }
@layer активний { .button.pressed { тло: #0f0; /* яскраво-зелений */ } }
@layer debug { .button::after { зміст: attr(значення даних); розмір шрифту: 12px; колір: #fff; } }
Порядок шарів має значення: базовий → активний → налагодження.
база малює контролер. активний обробляє натиснуті стани. викиди налагодження накладень.
Розбиваючи це таким чином, ви не боретеся з дивними війнами за специфічність. Кожен шар має своє місце, і ви завжди знаєте, що виграє. Будівництво це Out Давайте спочатку щось покажемо на екрані. Їй не обов’язково добре виглядати — просто потрібно існувати, щоб нам було з чим працювати.
Каскадний налагоджувач геймпада
Це буквально просто коробки. Поки що не захоплююче, але це дає нам можливість використовувати CSS і JavaScript пізніше. Гаразд, я використовую тут каскадні шари, оскільки вони зберігають все впорядкованим, коли ви додаєте більше станів. Ось грубий пас:
/* ==================================== НАЛАШТУВАННЯ КАСКАДНИХ ШАРІВ Порядок має значення: базовий → активний → налагодження ==================================== */
/* Визначте порядок шарів наперед */ @layer base, active, debug;
/* Рівень 1: базові стилі - зовнішній вигляд за замовчуванням */ @layer base { .button { фон: #333; border-radius: 50%; ширина: 70 пікселів; висота: 70px; дисплей: гнучкий; justify-content: центр; align-items: center; }
.pause { ширина: 20 пікселів; висота: 70px; фон: #333; дисплей: inline-block; } }
/* Рівень 2: Активні стани - обробляє натиснуті кнопки */ @layer активний { .button.active { тло: #0f0; /* Яскраво-зелений при натисканні */ перетворення: scale(1.1); /* Злегка збільшує кнопку */ }
.pause.active { тло: #0f0; перетворення: scaleY(1.1); /* Розтягується вертикально при натисканні */ } }
/* Рівень 3: накладення налагодження - інформація розробника */ @layer debug { .button::after { зміст: attr(значення даних); /* Показує числове значення */ розмір шрифту: 12px; колір: #fff; } }
Принадність цього підходу полягає в тому, що кожен шар має чітке призначення. Базовий рівень ніколи не може перевизначати активний, а активний ніколи не може перевизначати налагодження, незалежно від специфіки. Це усуває війни за специфіку CSS, які зазвичай заважають інструментам налагодження. Тепер здається, що деякі кластери сидять на темному тлі. Чесно кажучи, непогано.
Додавання JavaScript час JavaScript. Тут контролер фактично щось робить. Ми будемо будувати це крок за кроком. Крок 1: Налаштуйте управління державою По-перше, нам потрібні змінні для відстеження стану відладчика: // ==================================== // ДЕРЖАВНЕ УПРАВЛІННЯ // ====================================
let running = false; // Відстежує, чи активний налагоджувач нехай rafId; // Зберігає ідентифікатор requestAnimationFrame для скасування
Ці змінні керують циклом анімації, який постійно зчитує вхідні дані геймпада. Крок 2: Візьміть посилання на DOM Далі ми отримуємо посилання на всі елементи HTML, які будемо оновлювати: // ==================================== // ПОСИЛАННЯ НА ЕЛЕМЕНТИ 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("статус");
Зберігання цих посилань наперед ефективніше, ніж повторне запитування DOM. Крок 3: Додайте резервну клавіатуру Для тестування без фізичного контролера ми зіставимо клавіші клавіатури з кнопками: // ==================================== // РЕЗЕРВНА КЛАВІАТУРА (для тестування без контролера) // ====================================
const keyMap = { "a": btnA, "b": btnB, "x": btnX, "p": [pause1, pause2] // Клавіша 'p' керує обома смужками паузи };
Це дозволяє перевірити інтерфейс користувача, натискаючи клавіші на клавіатурі. Крок 4: Створіть головний цикл оновлення Ось де відбувається магія. Ця функція працює постійно та зчитує стан геймпада: // ==================================== // ОСНОВНИЙ ЦИКЛ ОНОВЛЕННЯ ГЕЙМПАДУ // ====================================
функція updateGamepad() { // Отримати всі підключені геймпади const gamepads = navigator.getGamepads(); if (!gamepads) повернення;
// Використовуйте перший підключений геймпад const gp = геймпади[0];
if (gp) { // Оновити стан кнопки, перемикаючи клас "активний". btnA.classList.toggle("активний", gp.buttons[0].натиснуто); btnB.classList.toggle("активний", gp.buttons[1].натиснутий); btnX.classList.toggle("active", gp.buttons[2].pressed);
// Керувати кнопкою паузи (індекс кнопки 9 на більшості контролерів) const pausePressed = gp.buttons[9].pressed; pause1.classList.toggle("активний", pausePressed); pause2.classList.toggle("активний", pausePressed);
// Створення списку натиснутих кнопок для відображення стану нехай натиснуто = []; gp.buttons.forEach((btn, i) => { якщо (btn.pressed)pressed.push("Кнопка " + i); });
// Оновити текст статусу, якщо натиснуто будь-яку кнопку if (pressed.length > 0) { status.textContent = "Натиснуто: " + pressed.join(", "); } }
// Продовжити цикл, якщо налагоджувач запущено якщо (запущено) { rafId = requestAnimationFrame(updateGamepad); } }
Метод classList.toggle() додає або видаляє активний клас залежно від того, чи натиснуто кнопку, що запускає наші стилі шару CSS. Крок 5: Обробка подій клавіатури Ці прослуховувачі подій забезпечують роботу резервної клавіатури: // ==================================== // ОБРОБНИКИ ПОДІЙ КЛАВІАТУРИ // ====================================
document.addEventListener("keydown", (e) => { if (keyMap[e.key]) { // Обробка одного або кількох елементів if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("active")); } ще { keyMap[e.key].classList.add("активний"); } status.textContent = "Натиснуто клавішу: " + e.key.toUpperCase(); } });
document.addEventListener("keyup", (e) => { if (keyMap[e.key]) { // Видалити активний стан, коли клавішу відпущено if (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("active")); } ще { keyMap[e.key].classList.remove("активний"); } status.textContent = "Ключ випущено: " + e.key.toUpperCase(); } });
Крок 6: Додайте елемент керування «Пуск/Зупинка». Нарешті, нам потрібен спосіб увімкнути та вимкнути відладчик: // ==================================== // УВІМК./ВИМК // ====================================
document.getElementById("toggle").addEventListener("click", () => { біг = !біг; // Перевернути запущений стан
якщо (запущено) { status.textContent = "Налагоджувач працює..."; updateGamepad(); // Запуск циклу оновлення } ще { status.textContent = "Налагоджувач неактивний"; cancelAnimationFrame(rafId); // Зупинка циклу } });
Так, натисніть кнопку, і вона засвітиться. Натисніть на палицю, і вона посунеться. Це все. Ще одна річ: необроблені значення. Іноді хочеться бачити лише цифри, а не світло.
На цьому етапі ви повинні побачити:
Простий екранний контролер, Кнопки, які реагують на вашу взаємодію з ними, і Додаткове зчитування налагодження, що показує індекси натиснутих кнопок.
Щоб зробити це менш абстрактним, ось коротка демонстрація екранного контролера, який реагує в реальному часі:
Тепер, натиснувши «Почати запис», все реєструється, доки ви не натиснете «Зупинити запис». 2. Експорт даних у CSV/JSON Коли у нас буде журнал, ми захочемо його зберегти.
Крок 1: Створіть програму Download Helper По-перше, нам потрібна допоміжна функція, яка оброблятиме завантаження файлів у браузері: // ==================================== // ПОМІЧНИК ЗАВАНТАЖУВАННЯ ФАЙЛУ // ====================================
function downloadFile(filename, content, type = "text/plain") { // Створення blob із вмісту const blob = new Blob([вміст], {тип}); const url = URL.createObjectURL(blob);
// Створіть тимчасове посилання для завантаження та натисніть його const a = document.createElement("a"); a.href = url; a.download = назва файлу; a.click();
// Очистити URL-адресу об’єкта після завантаження setTimeout(() => URL.revokeObjectURL(url), 100); }
Ця функція працює, створюючи Blob (великий бінарний об’єкт) із ваших даних, генеруючи для нього тимчасову URL-адресу та програмно клацаючи посилання для завантаження. Очищення гарантує, що ми не втратимо пам’ять. Крок 2. Керуйте експортом JSON JSON ідеально підходить для збереження повної структури даних:
// ==================================== // ЕКСПОРТ ЯК JSON // ====================================
document.getElementById("export-json").addEventListener("click", () => { // Перевірте, чи є щось для експорту if (!frames.length) { console.warn("Немає записів, доступних для експорту."); повернення; }
// Створення корисного навантаження з метаданими та кадрами const корисне навантаження = { createdAt: нова дата().toISOString(), кадрів };
// Завантажити у форматі JSON завантажити файл ( "gamepad-log.json", JSON.stringify(корисне навантаження, null, 2), "програма/json" ); });
Формат JSON зберігає все структуроване та легко аналізується, що робить його ідеальним для завантаження в інструменти розробника або обміну з колегами по команді. Крок 3. Керуйте експортом CSV Для експорту CSV нам потрібно звести ієрархічні дані в рядки та стовпці:
//==================================== // ЕКСПОРТ ЯК CSV // ====================================
document.getElementById("export-csv").addEventListener("click", () => { // Перевірте, чи є щось для експорту if (!frames.length) { console.warn("Немає записів, доступних для експорту."); повернення; }
// Побудова рядка заголовка CSV (стовпці для позначки часу, усіх кнопок, усіх осей) 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";
// Побудова рядків даних CSV const rows = frames.map(f => { const btnVals = f.buttons.map(b => b.value); return [f.t, ...btnVals, ...f.axes].join(","); }).join("\n");
// Завантажити як CSV downloadFile("gamepad-log.csv", заголовок + рядки, "текст/csv"); });
CSV чудово підходить для аналізу даних, оскільки відкривається безпосередньо в Excel або Google Таблицях, дозволяючи створювати діаграми, фільтрувати дані або візуально виявляти шаблони. Тепер, коли кнопки експорту з’явилися, ви побачите на панелі дві нові опції: Експорт JSON та Експорт CSV. JSON добре підходить, якщо ви хочете повернути необроблений журнал назад у ваші інструменти розробника або покопатися в структурі. З іншого боку, CSV відкривається безпосередньо в Excel або Google Таблицях, щоб ви могли створити діаграми, фільтрувати або порівнювати введені дані. На наступному малюнку показано, як виглядає панель із цими додатковими елементами керування.
3. Система знімків Іноді вам не потрібен повний запис, лише короткий «скріншот» стану введення. Тут вам допоможе кнопка «Зробити знімок».
І JavaScript:
// ==================================== // ЗРОБИТИ ЗНІМОК // ====================================
document.getElementById("snipshot").addEventListener("click", () => { // Отримати всі підключені геймпади const pads = navigator.getGamepads(); const activePads = [];
// Перегляд і фіксація стану кожного підключеного геймпада for (const gp колодок) { якщо (!gp) продовжити; // Пропустити порожні слоти
activePads.push({ id: gp.id, // Назва/модель контролера мітка часу: performance.now(), кнопки: gp.buttons.map(b => ({ натиснутий: b.натиснутий, значення: b.value })), осі: [...gp.axes] }); }
// Перевірте, чи знайдено геймпади if (!activePads.length) { console.warn("Немає геймпадів, підключених для знімка."); alert("Контролер не виявлено!"); повернення; }
// Реєстрація та сповіщення користувача console.log("Знімок:", activePads); alert(Зроблено знімок! Захоплено контролер(и) ${activePads.length}.); });
Знімки фіксують точний стан вашого контролера в певний момент часу. 4. Повтор введення привидів Тепер найцікавіше: повторне відтворення введення привидів. Це бере журнал і відтворює його візуально, ніби фантомний гравець використовує контролер.
JavaScript для відтворення: // ==================================== // ПОВТОР ПРИВИДА // ====================================
document.getElementById("replay").addEventListener("click", () => { // Переконайтеся, що у нас є запис для відтворення if (!frames.length) { alert("Немає запису для відтворення!"); повернення; }
console.log("Початок відтворення привидів...");
// Відстеження часу для синхронізованого відтворення let startTime = performance.now(); нехай frameIndex = 0;
// Відтворення циклу анімації функція step() { const зараз = performance.now(); const elapsed = зараз - час початку;
// Обробляємо всі кадри, які повинні були відбутися на даний момент while (frameIndex < frames.length && frames[frameIndex].t <= elapsed) { const frame = frames[frameIndex];
// Оновлення інтерфейсу користувача записаними станами кнопок btnA.classList.toggle("active", frame.buttons[0].pressed); btnB.classList.toggle("активний", frame.buttons[1].натиснутий); btnX.classList.toggle("active", frame.buttons[2].pressed);
// Відображення статусу оновлення нехай натиснуто = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) pressed.push("Кнопка " + i); }); if (pressed.length > 0) { status.textContent = "Привид: " + pressed.join(", "); }
frameIndex++; }
// Продовження циклу, якщо є більше кадрів if (frameIndex < frames.length) { requestAnimationFrame(крок); } ще { console.log("Повторитизакінчено."); status.textContent = "Повтор завершено"; } }
// Початок повтору крок(); });
Щоб зробити налагодження більш практичним, я додав повторне відтворення. Записавши сеанс, ви можете натиснути «Повторити» та спостерігати, як користувальницький інтерфейс розігрує його, майже як фантомний гравець керує планшетом. Для цього на панелі з’являється нова кнопка Replay Ghost.
Натисніть «Запис», трохи повозіться з контролером, зупиніться, а потім відтворіть. Інтерфейс користувача просто повторює все, що ви робили, як привид, який стежить за вашими введеннями. Навіщо турбуватись цими додатками?
Запис/експорт дозволяє тестувальникам легко показати, що саме сталося. Знімки завмирають на мить у часі, дуже корисні, коли ви переслідуєте дивні помилки. Відтворення Ghost чудово підходить для навчальних посібників, перевірки доступності або просто для порівняння налаштувань керування.
На даний момент це вже не просто гарна демонстрація, а те, що ви дійсно можете застосувати. Реальні випадки використання Тепер у нас є цей налагоджувач, який може багато чого. Він показує живий вхід, записує журнали, експортує їх і навіть відтворює матеріал. Але справжнє питання: кого насправді це хвилює? Кому це корисно? Розробники ігор Контролери є частиною роботи, але налагодити їх? Зазвичай біль. Уявіть, що ви тестуєте комбінацію бойових дій, наприклад ↓ → + удар. Замість того, щоб молитися, ви двічі натискаєте так само, записуєте один раз і відтворюєте. Готово. Або ви обмінюєтеся журналами JSON з товаришем по команді, щоб перевірити, чи ваш код багатокористувацької гри однаково реагує на їхній машині. Це величезно. Практики доступності Це мені близько до серця. Не всі грають зі «стандартним» контролером. Адаптивні контролери іноді видають дивні сигнали. За допомогою цього інструменту ви можете точно бачити, що відбувається. Викладачі, дослідники, хто завгодно. Вони можуть захоплювати журнали, порівнювати їх або відтворювати вхідні дані пліч-о-пліч. Раптом невидимі речі стають очевидними. Перевірка якості Тестери зазвичай пишуть замітки на кшталт «Я натиснув тут кнопки, і вони зламалися». Не дуже корисно. Зараз? Вони можуть зафіксувати точні преси, експортувати журнал і відправити його. Без здогадок. Вихователі Якщо ви створюєте навчальні посібники чи відео на YouTube, відтворення привидів — це золото. Ви можете буквально сказати: «Ось що я зробив із контролером», тоді як інтерфейс користувача показує, що це відбувається. Робить пояснення більш зрозумілими. За межами ігор І так, це стосується не лише ігор. Люди використовували контролери для роботів, мистецьких проектів та інтерфейсів доступності. Щоразу та сама проблема: що насправді бачить браузер? З цим вам не доведеться гадати. Висновок Налагодження вхідних даних контролера завжди було схоже на політ наосліп. На відміну від DOM або CSS, тут немає вбудованого інспектора для геймпадів; це просто необроблені цифри в консолі, які легко загубитися в шумі. З кількома сотнями рядків HTML, CSS і JavaScript ми створили щось інше:
Візуальний налагоджувач, який робить невидимі вхідні дані видимими. Багаторівнева система CSS, яка забезпечує чистоту інтерфейсу користувача та можливість її налагодження. Набір удосконалень (запис, експорт, миттєві знімки, відтворення привидів), що перетворює його з демонстраційного на інструмент розробника.
Цей проект показує, як далеко ви можете зайти, поєднавши потужність веб-платформи з трохи креативності в CSS Cascade Layers. Інструмент, який я щойно описав повністю, є відкритим кодом. Ви можете клонувати сховище GitHub і спробувати це на собі. Але що важливіше, ви можете зробити це самостійно. Додайте власні шари. Створіть власну логіку відтворення. Інтегруйте його з прототипом гри. Або навіть використовувати його так, як я не уявляв. Для навчання, доступності чи аналізу даних. Зрештою, це стосується не лише налагодження геймпадів. Йдеться про те, щоб пролити світло на приховані вхідні дані та дати розробникам впевненість у роботі з апаратним забезпеченням, яке Інтернет все ще не повністю охоплює. Отже, підключіть контролер, відкрийте редактор і починайте експериментувати. Ви можете бути здивовані тим, чого дійсно може досягти ваш браузер і ваш CSS.