컨트롤러를 연결하면 버튼을 으깨고, 스틱을 움직이고, 방아쇠를 당기지만 개발자는 아무것도 볼 수 없습니다. 물론 브라우저가 이를 인식하지만 콘솔에 숫자를 기록하지 않는 이상 보이지 않습니다. 이것이 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 기본, 활성, 디버그;
@레이어 베이스 { /* ... */ }
@레이어 활성 { /* ... */ }
@layer 디버그 { /* ... */ }
각 레이어는 예측 가능하게 쌓이기 때문에 어떤 규칙이 승리하는지 항상 알 수 있습니다. 이러한 예측 가능성으로 인해 디버깅이 더 쉬워질 뿐만 아니라 실제로 관리도 가능해집니다. 우리는 문제(보이지 않고 지저분한 입력)와 접근 방식(캐스케이드 레이어로 구축된 시각적 디버거)을 다루었습니다. 이제 디버거를 빌드하는 단계별 프로세스를 살펴보겠습니다. 디버거 개념 숨겨진 입력을 표시하는 가장 쉬운 방법은 화면에 그리는 것입니다. 이것이 바로 이 디버거가 하는 일입니다. 버튼, 트리거 및 조이스틱이 모두 시각적으로 표시됩니다.
A 누르기: 원이 켜집니다. 막대기를 살짝 밀기: 원이 주위로 미끄러집니다. 방아쇠를 반쯤 당기면 막대가 반쯤 채워집니다.
이제 0과 1을 쳐다보는 것이 아니라 실제로 컨트롤러가 실시간으로 반응하는 것을 지켜보는 것입니다. 물론 기본, 누름, 디버그 정보, 심지어 녹음 모드와 같은 상태를 쌓기 시작하면 CSS가 점점 더 커지고 복잡해지기 시작합니다. 이것이 캐스케이드 레이어가 유용한 곳입니다. 간단한 예는 다음과 같습니다. @레이어 베이스 { .버튼 { 배경: #222; 테두리 반경: 50%; 너비: 40px; 높이: 40px; } }
@레이어 활성 { .버튼을 눌렀습니다. { 배경: #0f0; /* 밝은 녹색 */ } }
@layer 디버그 { .button::이후 { 내용: attr(데이터-값); 글꼴 크기: 12px; 색상: #fff; } }
레이어 순서가 중요합니다: 기본 → 활성 → 디버그.
base는 컨트롤러를 그립니다. 활성 핸들은 누른 상태입니다. 오버레이에서 디버그가 발생합니다.
이렇게 나누면 이상한 특이성 전쟁에 맞서 싸우지 않는다는 의미입니다. 각 레이어에는 해당 위치가 있으며 무엇이 승리하는지 항상 알 수 있습니다. 구축하기 먼저 화면에 뭔가를 표시해 보겠습니다. 보기 좋게 보일 필요는 없습니다. 작업할 수 있도록 존재하기만 하면 됩니다.
게임패드 캐스케이드 디버거
말 그대로 그냥 상자일 뿐입니다. 아직 흥미롭지는 않지만 나중에 CSS와 JavaScript로 처리할 수 있는 핸들을 제공합니다. 좋습니다. 여기서는 캐스케이드 레이어를 사용하고 있습니다. 상태를 더 추가하면 내용이 정리되어 유지되기 때문입니다. 대략적인 패스는 다음과 같습니다.
/* =================================== 캐스케이드 레이어 설정 순서 중요: 기본 → 활성 → 디버그 ====================================*/
/* 레이어 순서를 미리 정의합니다 */ @layer 기본, 활성, 디버그;
/* 레이어 1: 기본 스타일 - 기본 모양 */ @레이어 베이스 { .버튼 { 배경: #333; 테두리 반경: 50%; 너비: 70px; 높이: 70px; 디스플레이: 플렉스; 내용 정당화: 센터; 항목 정렬: 중앙; }
.일시 중지 { 너비: 20px; 높이: 70px; 배경: #333; 디스플레이: 인라인 블록; } }
/* 레이어 2: 활성 상태 - 누른 버튼을 처리합니다 */ @레이어 활성 { .button.활성 { 배경: #0f0; /* 누르면 밝은 녹색 */ 변환: scale(1.1); /* 버튼을 약간 확대 */ }
.pause.active { 배경: #0f0; 변환: scaleY(1.1); /* 누르면 수직으로 늘어납니다 */ } }
/* 레이어 3: 디버그 오버레이 - 개발자 정보 */ @layer 디버그 { .button::이후 { 내용: attr(데이터-값); /* 숫자 값을 표시합니다 */ 글꼴 크기: 12px; 색상: #fff; } }
이 접근 방식의 장점은 각 레이어에 명확한 목적이 있다는 것입니다. 기본 계층은 활성을 재정의할 수 없으며 활성은 특이성에 관계없이 디버그를 재정의할 수 없습니다. 이는 일반적으로 디버깅 도구를 괴롭히는 CSS 특정성 전쟁을 제거합니다. 이제 일부 클러스터가 어두운 배경에 앉아 있는 것처럼 보입니다. 솔직히 나쁘지는 않습니다.
자바스크립트 추가 자바스크립트 시간. 여기가 컨트롤러가 실제로 뭔가를 하는 곳입니다. 우리는 이것을 단계별로 구축할 것입니다. 1단계: 상태 관리 설정 먼저 디버거의 상태를 추적하기 위한 변수가 필요합니다. // =================================== // 상태 관리 // ===================================
실행 = false로 두십시오. // 디버거가 활성화되어 있는지 추적합니다. rafId를 보자; // 취소를 위한 requestAnimationFrame ID를 저장합니다.
이러한 변수는 게임패드 입력을 지속적으로 읽는 애니메이션 루프를 제어합니다. 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단계: 키보드 대체 추가 물리적 컨트롤러 없이 테스트하기 위해 키보드 키를 버튼에 매핑합니다. // =================================== // KEYBOARD FALLBACK(컨트롤러 없이 테스트하는 경우) // ===================================
const 키맵 = { "a": btnA, "b": btnB, "x": btnX, "p": [pause1, Pause2] // 'p' 키는 두 일시정지 막대를 모두 제어합니다. };
이를 통해 키보드의 키를 눌러 UI를 테스트할 수 있습니다. 4단계: 기본 업데이트 루프 생성 여기서 마법이 일어납니다. 이 함수는 지속적으로 실행되며 게임패드 상태를 읽습니다. // =================================== // 메인 게임패드 업데이트 루프 // ===================================
함수 업데이트게임패드() { // 연결된 모든 게임패드를 가져옵니다. const 게임패드 = navigator.getGamepads(); if (!gamepads) 반환;
// 처음 연결된 게임패드 사용 const gp = 게임패드[0];
만약 (gp) { // "활성" 클래스를 전환하여 버튼 상태를 업데이트합니다. btnA.classList.toggle("active", gp.buttons[0].pressed); btnB.classList.toggle("active", gp.buttons[1].pressed); btnX.classList.toggle("active", gp.buttons[2].pressed);
// 일시 정지 버튼 처리(대부분의 컨트롤러에서 버튼 인덱스 9) const PausePressed = gp.buttons[9].pressed; Pause1.classList.toggle("active", PausePressed); Pause2.classList.toggle("active", PausePressed);
// 상태 표시를 위해 현재 누른 버튼 목록을 만듭니다. 누름 = []; gp.buttons.forEach((btn, i) => { if(btn.눌림)Pressed.push("버튼 " + i); });
// 버튼을 누르면 상태 텍스트 업데이트 if (pressed.length > 0) { status.textContent = "눌림: " +pressed.join(", "); } }
// 디버거가 실행 중이면 루프를 계속합니다. if (실행 중) { 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("active"); } 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("active"); } status.textContent = "키 해제됨: " + e.key.toUpperCase(); } });
6단계: 시작/중지 제어 추가 마지막으로 디버거를 켜고 끄는 방법이 필요합니다. // =================================== // 디버거 켜기/끄기 전환 // ===================================
document.getElementById("toggle").addEventListener("click", () => { 실행 중 =!실행 중; // 실행 상태 반전
if (실행 중) { status.textContent = "디버거 실행 중..."; 업데이트게임패드(); // 업데이트 루프를 시작합니다. } 그렇지 않으면 { status.textContent = "디버거 비활성"; cancelAnimationFrame(rafId); // 루프를 중지합니다. } });
예, 버튼을 누르면 빛납니다. 스틱을 누르면 움직입니다. 그게 다야. 한 가지 더: 원시 값입니다. 때로는 조명이 아닌 숫자만 보고 싶을 때도 있습니다.
이 단계에서는 다음을 확인해야 합니다.
간단한 화면 컨트롤러, 상호작용할 때 반응하는 버튼 누른 버튼 인덱스를 표시하는 선택적 디버그 판독값입니다.
이를 덜 추상적으로 만들기 위해 실시간으로 반응하는 화면 컨트롤러에 대한 간단한 데모가 있습니다.
이제 녹음 시작을 누르면 녹음 중지를 누를 때까지 모든 내용이 기록됩니다. 2. 데이터를 CSV/JSON으로 내보내기 로그가 있으면 저장하고 싶을 것입니다.
1단계: 다운로드 도우미 만들기 먼저, 브라우저에서 파일 다운로드를 처리하는 도우미 함수가 필요합니다. // =================================== // 파일 다운로드 도우미 // ===================================
function downloadFile(filename, content, type = "text/plain") { // 콘텐츠에서 블롭을 생성합니다. const blob = new Blob([콘텐츠], { 유형 }); const url = URL.createObjectURL(blob);
// 임시 다운로드 링크를 생성하고 클릭합니다. const a = document.createElement("a"); a.href = URL; a.다운로드 = 파일명; a.클릭();
// 다운로드 후 객체 URL을 정리합니다. setTimeout(() => URL.revokeObjectURL(url), 100); }
이 기능은 데이터에서 Blob(바이너리 대형 개체)을 생성하고 이에 대한 임시 URL을 생성한 다음 프로그래밍 방식으로 다운로드 링크를 클릭하여 작동합니다. 정리를 통해 메모리 누수를 방지할 수 있습니다. 2단계: JSON 내보내기 처리 JSON은 전체 데이터 구조를 보존하는 데 적합합니다.
// =================================== // JSON으로 내보내기 // ===================================
document.getElementById("export-json").addEventListener("click", () => { // 내보낼 항목이 있는지 확인 if (!frames.length) { console.warn("내보낼 수 있는 녹음이 없습니다."); 반환; }
// 메타데이터와 프레임이 포함된 페이로드 생성 const 페이로드 = { 생성된 위치: new Date().toISOString(), 프레임 };
// JSON 형식으로 다운로드 다운로드파일( "게임패드-log.json", JSON.stringify(페이로드, null, 2), "응용프로그램/json" ); });
JSON 형식은 모든 것을 구조화하고 쉽게 구문 분석할 수 있도록 유지하므로 개발 도구로 다시 로드하거나 팀원과 공유하는 데 이상적입니다. 3단계: CSV 내보내기 처리 CSV 내보내기의 경우 계층적 데이터를 행과 열로 평면화해야 합니다.
//=================================== // CSV로 내보내기 // ===================================
document.getElementById("export-csv").addEventListener("클릭", () => { // 내보낼 항목이 있는지 확인 if (!frames.length) { console.warn("내보낼 수 있는 녹음이 없습니다."); 반환; }
// CSV 헤더 행 생성(타임스탬프, 모든 버튼, 모든 축에 대한 열) const headerButtons = 프레임[0].buttons.map((_, i) => btn${i}); const headerAxes = 프레임[0].axes.map((_, i) => 축${i}); const 헤더 = ["t", ...headerButtons, ...headerAxes].join(",") + "\n";
// CSV 데이터 행 작성 const 행 = 프레임.맵(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. 스냅샷 시스템 전체 기록이 필요하지 않고 입력 상태에 대한 빠른 "스크린샷"만 필요한 경우도 있습니다. 이때 스냅샷 찍기 버튼이 도움이 됩니다.
그리고 자바스크립트는:
// =================================== // 스냅샷 찍기 // ===================================
document.getElementById("스냅샷").addEventListener("클릭", () => { // 연결된 모든 게임패드를 가져옵니다. const 패드 = navigator.getGamepads(); const activePads = [];
// 연결된 각 게임패드의 상태를 반복하고 캡처합니다. for (패드의 const gp) { if (!gp) 계속; // 빈 슬롯 건너뛰기
activePads.push({ id: gp.id, // 컨트롤러 이름/모델 타임스탬프:performance.now(), 버튼: gp.buttons.map(b => ({ 눌림: b.눌림, 값: b.값 })), 축: [...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("고스트 재생 시작 중...");
// 동기화된 재생의 타이밍을 추적합니다. startTime =performance.now(); 프레임 인덱스 = 0으로 놔두세요;
// 애니메이션 루프 재생 함수 단계() { const now =performance.now(); const 경과 = 현재 - startTime;
// 지금까지 발생했어야 하는 모든 프레임을 처리합니다. while (frameIndex < 프레임.길이 && 프레임[frameIndex].t <= 경과됨) { const 프레임 = 프레임[frameIndex];
// 기록된 버튼 상태로 UI 업데이트 btnA.classList.toggle("active", 프레임.버튼[0].pressed); btnB.classList.toggle("active", 프레임.버튼[1].pressed); btnX.classList.toggle("active", 프레임.버튼[2].pressed);
// 상태 표시 업데이트 누름 = []; 프레임.버튼.forEach((btn, i) => { if (btn.pressed)pressed.push("버튼 " + i); }); if (pressed.length > 0) { status.textContent = "유령: " +pressed.join(", "); }
프레임인덱스++; }
// 프레임이 더 있으면 반복을 계속합니다. if (frameIndex < 프레임 길이) { requestAnimationFrame(단계); } 그렇지 않으면 { console.log("재생끝났습니다."); status.textContent = "재생 완료"; } }
// 재생 시작 단계(); });
디버깅을 좀 더 직접적으로 수행하기 위해 고스트 재생을 추가했습니다. 세션을 녹화한 후에는 재생을 누르고 마치 유령 플레이어가 패드를 실행하는 것처럼 UI가 작동하는 것을 볼 수 있습니다. 이를 위해 새로운 Replay Ghost 버튼이 패널에 표시됩니다.
녹음을 누르고 컨트롤러를 약간 조작한 후 중지하고 다시 재생하세요. UI는 입력을 따라가는 유령처럼 사용자가 한 모든 것을 반영합니다. 왜 이런 추가 기능을 신경쓰나요?
기록/내보내기를 통해 테스터는 무슨 일이 일어났는지 정확하게 보여줄 수 있습니다. 스냅샷은 순간적으로 정지되므로 이상한 버그를 추적할 때 매우 유용합니다. Ghost replay는 튜토리얼, 접근성 확인 또는 제어 설정을 나란히 비교하는 데 적합합니다.
이 시점에서는 더 이상 깔끔한 데모가 아니라 실제로 작업에 투입할 수 있는 것입니다. 실제 사용 사례 이제 우리는 많은 일을 할 수 있는 디버거를 갖게 되었습니다. 실시간 입력을 표시하고, 로그를 기록하고, 내보내고, 심지어 내용을 재생하기도 합니다. 하지만 진짜 질문은 누가 실제로 관심을 갖는가입니다. 이것은 누구에게 유용합니까? 게임 개발자 컨트롤러는 작업의 일부이지만 디버깅을 하시겠습니까? 보통 통증이 있습니다. ↓ → + 펀치와 같은 격투 게임 콤보를 테스트한다고 상상해 보세요. 기도하는 대신 같은 방법으로 두 번 누르고, 한 번 녹음하고, 다시 재생하면 됩니다. 완료. 또는 팀원과 JSON 로그를 교환하여 멀티플레이어 코드가 팀원의 컴퓨터에서 동일하게 반응하는지 확인합니다. 정말 대단해요. 접근성 실무자 이것은 내 마음에 가깝습니다. 모든 사람이 "표준" 컨트롤러를 사용하는 것은 아닙니다. 적응형 컨트롤러는 때때로 이상한 신호를 보냅니다. 이 도구를 사용하면 무슨 일이 일어나고 있는지 정확하게 확인할 수 있습니다. 교사, 연구원, 누구든지. 로그를 가져오고 비교하거나 입력을 나란히 재생할 수 있습니다. 갑자기 보이지 않는 것들이 명백해집니다. 품질 보증 테스트 테스터는 일반적으로 "여기 버튼을 으깨서 깨졌습니다."와 같은 메모를 작성합니다. 별로 도움이 되지 않습니다. 지금? 정확한 프레스를 캡처하고 로그를 내보내어 보낼 수 있습니다. 추측하지 마세요. 교육자 튜토리얼이나 YouTube 동영상을 제작하는 경우 고스트 재생이 금상첨화입니다. UI에 해당 작업이 표시되는 동안 문자 그대로 "컨트롤러로 수행한 작업은 다음과 같습니다."라고 말할 수 있습니다. 설명이 훨씬 더 명확해집니다. 비욘드 게임 그리고 네, 이것은 단지 게임에 관한 것이 아닙니다. 사람들은 로봇, 예술 프로젝트, 접근성 인터페이스에 컨트롤러를 사용해 왔습니다. 매번 같은 문제: 브라우저는 실제로 무엇을 보고 있습니까? 이것으로 추측할 필요가 없습니다. 결론 컨트롤러 입력 디버깅은 항상 맹목적인 느낌이었습니다. DOM이나 CSS와 달리 게임패드에는 내장된 검사기가 없습니다. 그것은 콘솔의 원시 숫자일 뿐이며 소음에 쉽게 빠져들게 됩니다. 수백 줄의 HTML, CSS 및 JavaScript를 사용하여 우리는 뭔가 다른 것을 만들었습니다.
보이지 않는 입력을 표시하는 시각적 디버거입니다. UI를 깔끔하고 디버깅 가능하게 유지하는 계층화된 CSS 시스템입니다. 데모에서 개발자 도구로 승격시키는 일련의 향상된 기능(녹화, 내보내기, 스냅샷, 고스트 재생)입니다.
이 프로젝트는 웹 플랫폼의 강력한 기능과 CSS 캐스케이드 레이어의 약간의 창의성을 결합하여 얼마나 멀리 갈 수 있는지 보여줍니다. 방금 전체적으로 설명한 도구는 오픈 소스입니다. GitHub 저장소를 복제하여 직접 사용해 볼 수 있습니다. 하지만 더 중요한 것은 그것을 자신만의 것으로 만들 수 있다는 것입니다. 나만의 레이어를 추가하세요. 자신만의 재생 로직을 구축하세요. 이를 게임 프로토타입과 통합하세요. 아니면 내가 상상하지 못한 방식으로 사용하기도 합니다. 교육, 접근성 또는 데이터 분석용입니다. 결국 이것은 단지 게임패드 디버깅에 관한 것이 아닙니다. 이는 숨겨진 입력에 빛을 비추고 개발자에게 웹이 아직 완전히 수용하지 못하는 하드웨어로 작업할 수 있다는 자신감을 주는 것입니다. 컨트롤러를 연결하고 편집기를 열고 실험을 시작하세요. 브라우저와 CSS가 실제로 무엇을 성취할 수 있는지에 놀라실 수도 있습니다.