Когда вы подключаете контроллер, вы нажимаете кнопки, перемещаете джойстики, нажимаете на триггеры… и как разработчик вы ничего из этого не видите. Браузер, конечно, его улавливает, но если вы не вводите числа в консоль, он невидим. Это головная боль с 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 базовый, активный, отладочный;

@layer base { /* ... */ }

@layer активен { /* ... */ }

@layer отладка { /* ... */ }

Поскольку каждый слой складывается предсказуемо, вы всегда знаете, какие правила выигрывают. Эта предсказуемость делает отладку не только проще, но и управляемой. Мы рассмотрели проблему (невидимый, беспорядочный ввод) и подход (визуальный отладчик, созданный с помощью каскадных слоев). Теперь мы пошагово рассмотрим процесс сборки отладчика. Концепция отладчика Самый простой способ сделать скрытый ввод видимым — просто нарисовать его на экране. Именно это и делает этот отладчик. Кнопки, триггеры и джойстики теперь визуализируются.

Нажмите A: загорится кружок. Подтолкните палку: круг скользит по кругу. Нажмите спусковой крючок наполовину: полоска заполнится наполовину.

Теперь вы не смотрите на 0 и 1, а наблюдаете за реакцией контроллера в реальном времени. Конечно, как только вы начнете накапливать такие состояния, как «по умолчанию», «нажатие», «отладочная информация» и, возможно, даже режим записи, CSS начинает становиться больше и сложнее. Вот тут-то и пригодятся каскадные слои. Вот урезанный пример: @layer base { .кнопка { фон: #222; радиус границы: 50%; ширина: 40 пикселей; высота: 40 пикселей; } }

@layer активен { .button.pressed { фон: #0f0; /* ярко-зеленый */ } }

@layer отладка { .button::after { содержимое: attr(значение данных); размер шрифта: 12 пикселей; цвет: #fff; } }

Порядок слоев имеет значение: базовый → активный → отладочный.

база рисует контроллер. активные дескрипторы нажатых состояний. отладочные выдачи на оверлеях.

Такое разделение означает, что вы не ведете странные войны за специфичность. Каждый слой имеет свое место, и вы всегда знаете, что победит. Строим это Давайте сначала что-нибудь выведем на экран. Ему не обязательно хорошо выглядеть — он просто должен существовать, чтобы нам было с чем работать.

Каскадный отладчик геймпада

A
B
X

<дел>

Отладчик неактивен

Это буквально просто коробки. Пока это не интересно, но это дает нам возможность использовать CSS и JavaScript позже. Хорошо, я использую здесь каскадные слои, потому что они сохраняют порядок, когда вы добавляете больше состояний. Вот грубый проход:

/* ================================== НАСТРОЙКА КАСКАДНЫХ СЛОЕВ Порядок имеет значение: базовый → активный → отладочный. ================================== */

/* Определить порядок слоев заранее */ @layer базовый, активный, отладочный;

/* Уровень 1: базовые стили — внешний вид по умолчанию */ @layer base { .кнопка { фон: #333; радиус границы: 50%; ширина: 70 пикселей; высота: 70 пикселей; дисплей: гибкий; оправдание-содержание: центр; выровнять-элементы: по центру; }

.пауза { ширина: 20 пикселей; высота: 70 пикселей; фон: #333; отображение: встроенный блок; } }

/* Уровень 2: Активные состояния — обрабатывают нажатые кнопки */ @layer активен { .button.active { фон: #0f0; /* Ярко-зеленый при нажатии */ преобразование: масштаб (1.1); /* Немного увеличивает кнопку */ }

.pause.active { фон: #0f0; преобразование: масштабY(1.1); /* Растягивается вертикально при нажатии */ } }

/* Уровень 3: наложения отладки — информация разработчика */ @layer отладка { .button::after { содержимое: attr(значение данных); /* Показывает числовое значение */ размер шрифта: 12 пикселей; цвет: #fff; } }

Прелесть этого подхода в том, что каждый уровень имеет четкую цель. Базовый уровень никогда не может переопределить активный, а активный никогда не может переопределить отладку, независимо от специфики. Это устраняет войны за специфику CSS, которые обычно досаждают инструментам отладки. Теперь это выглядит так, будто какие-то скопления располагаются на темном фоне. Честно говоря, не так уж и плохо.

Добавление JavaScript Время JavaScript. Здесь контроллер действительно что-то делает. Мы будем строить это шаг за шагом. Шаг 1. Настройте управление состоянием Во-первых, нам нужны переменные для отслеживания состояния отладчика: // ================================== // ГОСУДАРСТВЕННОЕ УПРАВЛЕНИЕ // ==================================

пусть работает = ложь; // Отслеживает, активен ли отладчик пусть рафид; // Сохраняет идентификатор requestAnimationFrame для отмены

Эти переменные управляют циклом анимации, который непрерывно считывает вводимые с геймпада данные. Шаг 2. Получите ссылки на DOM Далее мы получаем ссылки на все элементы HTML, которые будем обновлять: // ================================== // ССЫЛКИ НА ЭЛЕМЕНТЫ DOM // ==================================

const btnA = document.getElementById("btn-a"); const btnB = document.getElementById("btn-b"); const btnX = document.getElementById("btn-x"); constpause1 = document.getElementById("pause1"); constpause2 = document.getElementById("pause2"); const status = document.getElementById("статус");

Предварительное сохранение этих ссылок более эффективно, чем повторный запрос DOM. Шаг 3. Добавьте резервную клавиатуру Для тестирования без физического контроллера мы назначим клавиши клавиатуры кнопкам: // ================================== // ОТМЕНА КЛАВИАТУРЫ (для тестирования без контроллера) // ==================================

const keyMap = { «а»: бтнА, «б»: бтнБ, «х»: btnX, "p": [pause1, Pause2] // клавиша "p" управляет обеими полосами паузы };

Это позволяет нам тестировать пользовательский интерфейс, нажимая клавиши на клавиатуре. Шаг 4. Создайте основной цикл обновления Вот где происходит волшебство. Эта функция работает постоянно и считывает состояние геймпада: // ================================== // ГЛАВНЫЙ ЦИКЛ ОБНОВЛЕНИЯ ГЕЙМПАДА // ==================================

функция updateGamepad() { // Получаем все подключенные геймпады const геймпады = navigator.getGamepads(); если (!геймпады) возвращаются;

// Используем первый подключенный геймпад const gp = геймпады [0];

если (гп) { // Обновляем состояния кнопок, переключая «активный» класс btnA.classList.toggle("активный", gp.buttons[0].pressed); btnB.classList.toggle("активный", gp.buttons[1].pressed); btnX.classList.toggle("активный", gp.buttons[2].pressed);

// Обработка кнопки паузы (индекс кнопки 9 на большинстве контроллеров) const паузаPressed = gp.buttons[9].pressed; пауза1.classList.toggle("активный", паузаPressed); пауза2.classList.toggle("активный", паузаPressed);

// Создаем список нажатых в данный момент кнопок для отображения статуса пусть нажата = []; gp.buttons.forEach((btn, i) => { если (кнопка нажата)нажатый.push("Кнопка " + я); });

// Обновляем текст статуса, если нажата какая-либо кнопка если (длина нажатия > 0) { status.textContent = "Нажато: " + нажато.join(", "); } }

// Продолжаем цикл, если отладчик запущен если (работает) { RafId = requestAnimationFrame (updateGamepad); } }

Метод classList.toggle() добавляет или удаляет активный класс в зависимости от того, нажата ли кнопка, что запускает стили слоев CSS. Шаг 5. Обработка событий клавиатуры Эти прослушиватели событий заставляют работать резервную клавиатуру: // ================================== // ОБРАБОТЧИКИ СОБЫТИЙ КЛАВИАТУРЫ // ==================================

document.addEventListener("keydown", (e) => { если (keyMap[e.key]) { // Обработка одного или нескольких элементов если (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.add("активный")); } еще { keyMap[e.key].classList.add("активный"); } status.textContent = "Клавиша нажата: " + e.key.toUpperCase(); } });

document.addEventListener("keyup", (e) => { если (keyMap[e.key]) { // Удалить активное состояние при отпускании клавиши если (Array.isArray(keyMap[e.key])) { keyMap[e.key].forEach(el => el.classList.remove("активный")); } еще { keyMap[e.key].classList.remove("активный"); } status.textContent = "Ключ освобожден: " + e.key.toUpperCase(); } });

Шаг 6: Добавьте контроль запуска/остановки Наконец, нам нужен способ включения и выключения отладчика: // ================================== // ВКЛЮЧЕНИЕ/ВЫКЛЮЧЕНИЕ ОТЛАДЧИКА // ==================================

document.getElementById("toggle").addEventListener("click", () => { работает = ! работает; // Переворачиваем рабочее состояние

если (работает) { status.textContent = "Отладчик работает..."; обновить геймпад(); // Запускаем цикл обновления } еще { status.textContent = "Отладчик неактивен"; cancelAnimationFrame (rafId); // Остановить цикл } });

Да, нажмите кнопку, и она загорится. Нажмите на палку, и она начнет двигаться. Вот и все. И еще: необработанные значения. Иногда вам просто хочется видеть цифры, а не огни.

На этом этапе вы должны увидеть:

Простой экранный контроллер, Кнопки, которые реагируют на ваше взаимодействие с ними, и Дополнительное отладочное считывание, показывающее индексы нажатых кнопок.

Чтобы сделать это менее абстрактным, вот краткая демонстрация того, как экранный контроллер реагирует в реальном времени:

Теперь нажатие «Начать запись» записывает все, пока вы не нажмете «Остановить запись». 2. Экспорт данных в CSV/JSON. Как только у нас появится журнал, мы захотим его сохранить.

Шаг 1. Создайте помощник по загрузке Во-первых, нам нужна вспомогательная функция, которая обрабатывает загрузку файлов в браузере: // ================================== // ПОМОЩНИК ЗАГРУЗКИ ФАЙЛА // ==================================

функция downloadFile(filename, content, type = "text/plain") { // Создаем объект из содержимого const blob = новый Blob ([содержание], {тип}); const URL = URL.createObjectURL(BLOB);

// Создаем временную ссылку для скачивания и кликаем по ней const a = document.createElement("a"); а.href = URL-адрес; a.download = имя файла; а.клик();

// Очищаем URL объекта после загрузки setTimeout(() => URL.revokeObjectURL(url), 100); }

Эта функция работает путем создания Blob (большого двоичного объекта) из ваших данных, создания для него временного URL-адреса и программного щелчка по ссылке для скачивания. Очистка гарантирует отсутствие утечек памяти. Шаг 2. Обработка экспорта JSON JSON идеально подходит для сохранения полной структуры данных:

// ================================== // ЭКСПОРТ КАК JSON // ==================================

document.getElementById("export-json").addEventListener("click", () => { // Проверяем, есть ли что экспортировать если (!frames.length) { console.warn("Нет записи, доступной для экспорта."); возврат; }

// Создаем полезную нагрузку с метаданными и кадрами константная полезная нагрузка = { создано: новая дата().toISOString(), рамки };

// Загрузка в формате JSON скачатьФайл( "геймпад-log.json", JSON.stringify(полезная нагрузка, ноль, 2), "приложение/json" ); });

Формат JSON сохраняет все структурированным и легко анализируемым, что делает его идеальным для загрузки обратно в инструменты разработки или обмена с коллегами по команде. Шаг 3. Обработка экспорта CSV Для экспорта CSV нам нужно разбить иерархические данные на строки и столбцы:

//================================== // ЭКСПОРТ В формате CSV // ==================================

document.getElementById("export-csv").addEventListener("click", () => { // Проверяем, есть ли что экспортировать если (!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", заголовок + строки, "text/csv"); });

CSV отлично подходит для анализа данных, поскольку он открывается непосредственно в Excel или Google Sheets, что позволяет вам создавать диаграммы, фильтровать данные или визуально выявлять закономерности. Теперь, когда кнопки экспорта включены, вы увидите на панели две новые опции: «Экспорт JSON» и «Экспорт CSV». JSON удобен, если вы хотите вернуть необработанный журнал в свои инструменты разработки или изучить структуру. CSV, с другой стороны, открывается прямо в Excel или Google Sheets, поэтому вы можете составлять диаграммы, фильтровать или сравнивать входные данные. На следующем рисунке показано, как выглядит панель с этими дополнительными элементами управления.

3. Система моментальных снимков Иногда вам не нужна полная запись, достаточно быстрого «скриншота» состояний ввода. Вот тут-то и поможет кнопка «Сделать снимок».

И JavaScript:

// ================================== // СДЕЛАТЬ СНИМОК // ==================================

document.getElementById("снимок").addEventListener("click", () => { // Получаем все подключенные геймпады constpads = navigator.getGamepads(); const activePads = [];

// Проходим и фиксируем состояние каждого подключенного геймпада for (const gp площадок) { если (!gp) продолжить; // Пропускаем пустые слоты

activePads.push({ id: gp.id, // Имя/модель контроллера временная метка: Performance.now(), кнопки: gp.buttons.map(b => ({ нажата: б. нажата, значение: b.значение })), оси: [...gp.axes] }); }

// Проверяем, найдены ли геймпады если (!activePads.length) { console.warn("Для снимка не подключены геймпады."); alert("Контроллер не обнаружен!"); возврат; }

// Регистрируем и уведомляем пользователя console.log("Снимок:", activePads); alert(Снимок сделан! Захвачены контроллеры ${activePads.length}).); });

Снимки фиксируют точное состояние вашего контроллера в определенный момент времени. 4. Воспроизведение призрачного ввода Теперь самое интересное: повтор призрачного ввода. При этом берется журнал и воспроизводится его визуально, как если бы контроллер использовал фантомный игрок.

JavaScript для повтора: // ================================== // ПОВТОР ПРИЗРАКА // ==================================

document.getElementById("replay").addEventListener("click", () => { // Убедитесь, что у нас есть запись для воспроизведения если (!frames.length) { alert("Нет записи для повтора!"); возврат; }

console.log("Запуск призрачного повтора...");

// Отслеживание времени для синхронизированного воспроизведения пусть startTime = Performance.now(); пусть FrameIndex = 0;

// Повтор цикла анимации функция шаг() { const now = Performance.now(); const истекло = сейчас - startTime;

// Обрабатываем все кадры, которые должны были произойти к настоящему моменту while (frameIndex

// Обновляем пользовательский интерфейс с записанными состояниями кнопок btnA.classList.toggle("активный",frame.buttons[0].pressed); btnB.classList.toggle("активный",frame.buttons[1].pressed); btnX.classList.toggle("активный",frame.buttons[2].pressed);

// Отображение статуса обновления пусть нажата = []; frame.buttons.forEach((btn, i) => { if (btn.pressed) нажата.push("Кнопка " + i); }); если (длина нажатия > 0) { status.textContent = "Призрак: " + pressed.join(", "); }

фреймИндекс++; }

// Продолжаем цикл, если кадров больше если (frameIndex

// Запускаем повтор шаг(); });

Чтобы сделать отладку более практической, я добавил призрачный повтор. После того, как вы записали сеанс, вы можете нажать «Повтор» и посмотреть, как пользовательский интерфейс разыгрывает его, почти так, как будто фантомный игрок управляет пэдом. Для этого на панели появится новая кнопка «Replay Ghost».

Нажмите «Запись», немного повозитесь с контроллером, остановитесь, затем воспроизведите заново. Пользовательский интерфейс просто повторяет все, что вы делаете, словно призрак, следящий за вашими вводами. Зачем беспокоиться об этих дополнениях?

Запись/экспорт позволяет тестировщикам легко показать, что именно произошло. Снимки останавливают момент времени, что очень полезно, когда вы ищете странные ошибки. Повтор Ghost отлично подходит для обучения, проверки доступности или простого сравнения настроек управления.

На данный момент это уже не просто изящная демонстрация, а то, что вы действительно можете применить на практике. Реальные примеры использования Теперь у нас есть отладчик, который может многое. Он отображает ввод в реальном времени, записывает журналы, экспортирует их и даже воспроизводит данные. Но главный вопрос: кого это вообще волнует? Для кого это полезно? Разработчики игр Контроллеры — часть работы, но их отладка? Обычно боль. Представьте, что вы тестируете комбинацию из файтинга, например ↓ → + удар. Вместо того, чтобы молиться, вы дважды нажимаете одинаково, записываете один раз и воспроизводите. Готово. Или вы обмениваетесь журналами JSON с товарищем по команде, чтобы проверить, одинаково ли реагирует ваш многопользовательский код на его компьютере. Это огромно. Специалисты по обеспечению доступности Это близко моему сердцу. Не все играют со «стандартным» контроллером. Адаптивные контроллеры иногда выдают странные сигналы. С помощью этого инструмента вы можете точно увидеть, что происходит. Учителя, исследователи, кто угодно. Они могут получать журналы, сравнивать их или воспроизводить входные данные параллельно. Внезапно невидимое становится очевидным. Тестирование обеспечения качества Тестировщики обычно пишут заметки типа «Я тут кнопки помял, и оно сломалось». Не очень полезно. Сейчас? Они могут захватить точные печатные машины, экспортировать журнал и отправить его. Никаких догадок. Педагоги Если вы снимаете обучающие материалы или видеоролики на YouTube, призрачный повтор — это золото. Вы можете буквально сказать: «Вот что я сделал с контроллером», в то время как пользовательский интерфейс показывает, что это происходит. Делает объяснения более понятными. Помимо игр И да, речь идет не только об играх. Люди использовали контроллеры для роботов, художественных проектов и интерфейсов доступности. Каждый раз одна и та же проблема: что на самом деле видит браузер? При этом вам не придется гадать. Заключение Отладка входа контроллера всегда была похожа на полет вслепую. В отличие от DOM или CSS, для геймпадов нет встроенного инспектора; это просто необработанные цифры в консоли, легко теряющиеся в шуме. Используя несколько сотен строк HTML, CSS и JavaScript, мы создали нечто новое:

Визуальный отладчик, который делает невидимые входные данные видимыми. Многоуровневая система CSS, которая обеспечивает чистоту и возможность отладки пользовательского интерфейса. Набор улучшений (запись, экспорт, снимки, призрачное воспроизведение), которые превращают его из демо-версии в инструмент разработчика.

Этот проект показывает, как далеко вы можете зайти, объединив мощь веб-платформы с небольшим творческим подходом к каскадным слоям CSS. Инструмент, который я только что объяснил, имеет открытый исходный код. Вы можете клонировать репозиторий GitHub и попробовать его самостоятельно. Но что еще более важно, вы можете сделать это самостоятельно. Добавляйте свои собственные слои. Создайте свою собственную логику повторов. Интегрируйте его с прототипом вашей игры. Или даже использовать его так, как я даже не мог себе представить. Для обучения, доступности или анализа данных. В конце концов, речь идет не только об отладке геймпадов. Речь идет о том, чтобы пролить свет на скрытые входы и дать разработчикам уверенность в работе с оборудованием, которое Интернет до сих пор не полностью поддерживает. Итак, подключите контроллер, откройте редактор и начните экспериментировать. Вы можете быть удивлены тем, на что действительно способен ваш браузер и ваш CSS.

You May Also Like

Enjoyed This Article?

Get weekly tips on growing your audience and monetizing your content — straight to your inbox.

No spam. Join 138,000+ creators. Unsubscribe anytime.

Create Your Free Bio Page

Join 138,000+ creators on Seemless.

Get Started Free