Gamepad API를 사용하여 웹 게임을 한 단계 업그레이드하는 방법을 알아보세요.
Chrome의 오프라인 페이지 이스터 에그는 역사상 가장 잘 알려진 비밀 중 하나입니다 ([citation needed],
하지만 극적인 효과를 위해 주장함). 스페이스 키를 누르거나 휴대기기에서 공룡을 탭하면 오프라인 페이지가 플레이 가능한 아케이드 게임으로 바뀝니다. 게임을 하고 싶을 때 실제로 오프라인으로 전환할 필요는 없습니다. Chrome에서 about://dino로 이동하거나, 괴짜라면 about://network-error/-106로 이동하면 됩니다. 하지만 매달 2억 7천만 번의 Chrome 공룡 게임이 플레이된다는 사실을 알고 계셨나요?
알아두면 더 유용하지만 잘 알려지지 않은 또 다른 사실은 아케이드 모드에서 게임패드로 게임을 플레이할 수 있다는 것입니다. 게임패드 지원은 이 글을 작성하는 시점으로부터 약 1년 전에 Reilly Grant의 커밋으로 추가되었습니다. 보시다시피 이 게임은 Chromium 프로젝트의 다른 부분과 마찬가지로 완전히 오픈소스입니다. 이 게시물에서는 Gamepad API를 사용하는 방법을 보여드리겠습니다.
Gamepad API 사용
기능 감지 및 브라우저 지원
Gamepad API는 데스크톱과 모바일 모두에서 보편적으로 우수한 브라우저 지원을 제공합니다. 다음 스니펫을 사용하여 Gamepad API가 지원되는지 감지할 수 있습니다.
if ('getGamepads' in navigator) {
// The API is supported!
}
브라우저가 게임패드를 표현하는 방식
브라우저는 게임패드를 Gamepad 객체로 나타냅니다. Gamepad에는 다음과 같은 속성이 있습니다.
id: 게임패드의 식별 문자열입니다. 이 문자열은 연결된 게임패드 기기의 브랜드 또는 스타일을 식별합니다.displayId: 연결된VRDisplay의VRDisplay.displayId입니다(해당하는 경우).index: 탐색기의 게임패드 색인입니다.connected: 게임패드가 시스템에 계속 연결되어 있는지 나타냅니다.hand: 컨트롤러를 쥐고 있거나 쥐고 있을 가능성이 가장 높은 손을 정의하는 enum입니다.timestamp: 이 게임패드의 데이터가 마지막으로 업데이트된 시간입니다.mapping: 이 기기에 사용 중인 버튼 및 축 매핑("standard"또는"xr-standard")pose: WebVR 컨트롤러와 연결된 포즈 정보를 나타내는GamepadPose객체입니다.axes: 게임패드의 모든 축에 대한 값의 배열로,-1.0~1.0범위로 선형 정규화됩니다.buttons: 게임패드의 모든 버튼에 대한 버튼 상태 배열입니다.
버튼은 디지털 (눌림 또는 눌리지 않음) 또는 아날로그 (예: 78% 눌림)일 수 있습니다. 따라서 버튼은 다음 속성을 가진 GamepadButton 객체로 보고됩니다.
pressed: 버튼의 눌린 상태입니다(버튼이 눌린 경우true, 눌리지 않은 경우false).touched: 버튼의 터치 상태입니다. 버튼이 터치를 감지할 수 있는 경우 이 속성은 버튼을 터치하면true이고 그렇지 않으면false입니다.value: 아날로그 센서가 있는 버튼의 경우 이 속성은 버튼이 눌린 정도를 나타내며0.0~1.0범위로 선형 정규화됩니다.hapticActuators:GamepadHapticActuator객체를 포함하는 배열로, 각 객체는 컨트롤러에서 사용할 수 있는 햅틱 반응 하드웨어를 나타냅니다.
브라우저와 게임패드에 따라 발생할 수 있는 또 다른 문제는 vibrationActuator 속성입니다. 두 가지 종류의 진동 효과를 허용합니다.
- 이중 진동: 게임패드의 각 그립에 하나씩 있는 두 개의 편심 회전 질량 액추에이터에 의해 생성되는 햅틱 반응 효과입니다.
- 트리거 럼블: 두 개의 독립적인 모터에 의해 생성되는 햅틱 반응 효과로, 게임패드의 각 트리거에 모터가 하나씩 있습니다.
사양에서 바로 가져온 다음 개략적인 개요는 일반 게임패드의 버튼과 축의 매핑 및 정렬을 보여줍니다.
게임패드가 연결되면 알림
게임패드가 연결되는 시점을 알아보려면 window 객체에서 트리거되는 gamepadconnected 이벤트를 수신 대기합니다. 사용자가 USB 또는 블루투스를 사용하여 게임패드를 연결하면 GamepadEvent가 실행되며, 이 이벤트에는 적절한 이름의 gamepad 속성에 게임패드의 세부정보가 포함됩니다.
다음은 제가 가지고 있던 Xbox 360 컨트롤러의 예입니다 (예, 저는 레트로 게임을 좋아합니다).
window.addEventListener('gamepadconnected', (event) => {
console.log('✅ 🎮 A gamepad was connected:', event.gamepad);
/*
gamepad: Gamepad
axes: (4) [0, 0, 0, 0]
buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
connected: true
id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
index: 0
mapping: "standard"
timestamp: 6563054.284999998
vibrationActuator: GamepadHapticActuator {type: "dual-rumble"}
*/
});
게임패드 연결이 끊어지면 알림
게임패드 연결 해제 알림은 연결이 감지되는 방식과 유사하게 발생합니다.
이번에는 앱이 gamepaddisconnected 이벤트를 수신 대기합니다. 다음 예에서 Xbox 360 컨트롤러를 분리하면 connected가 false가 됩니다.
window.addEventListener('gamepaddisconnected', (event) => {
console.log('❌ 🎮 A gamepad was disconnected:', event.gamepad);
/*
gamepad: Gamepad
axes: (4) [0, 0, 0, 0]
buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]
connected: false
id: "Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)"
index: 0
mapping: "standard"
timestamp: 6563054.284999998
vibrationActuator: null
*/
});
게임 루프의 게임패드
게임패드를 가져오려면 navigator.getGamepads()를 호출하여 Gamepad 항목이 있는 배열을 반환합니다. Chrome의 배열은 항상 고정된 길이의 항목 4개로 구성됩니다. 게임패드가 0개 또는 4개 미만으로 연결된 경우 항목은 null일 수 있습니다. 항상 배열의 모든 항목을 확인하고 게임패드가 슬롯을 '기억'하며 사용 가능한 첫 번째 슬롯에 항상 존재하지는 않을 수 있다는 점에 유의하세요.
// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]
하나 이상의 게임패드가 연결되어 있지만 navigator.getGamepads()에서 여전히 null 항목을 보고하는 경우 각 게임패드의 버튼을 눌러 게임패드를 '절전 모드 해제'해야 할 수 있습니다. 그런 다음, 다음 코드와 같이 게임 루프에서 게임패드 상태를 폴링할 수 있습니다.
const pollGamepads = () => {
// Always call `navigator.getGamepads()` inside of
// the game loop, not outside.
const gamepads = navigator.getGamepads();
for (const gamepad of gamepads) {
// Disregard empty slots.
if (!gamepad) {
continue;
}
// Process the gamepad state.
console.log(gamepad);
}
// Call yourself upon the next animation frame.
// (Typically this happens every 60 times per second.)
window.requestAnimationFrame(pollGamepads);
};
// Kick off the initial game loop iteration.
pollGamepads();
진동 액추에이터
vibrationActuator 속성은 GamepadHapticActuator 객체를 반환합니다. 이 객체는 햅틱 피드백을 위해 힘을 가할 수 있는 모터 또는 기타 액추에이터의 구성에 상응합니다. Gamepad.vibrationActuator.playEffect()를 호출하여 햅틱 효과를 재생할 수 있습니다. 유효한 효과 유형은 'dual-rumble' 및 'trigger-rumble'뿐입니다.
지원되는 진동 효과
if (gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
// Trigger rumble supported.
} else if (gamepad.vibrationActuator.effects.includes('dual-rumble')) {
// Dual rumble supported.
} else {
// Rumble effects aren't supported.
}
듀얼 진동
이중 진동은 표준 게임패드의 각 핸들에 편심 회전 질량 진동 모터가 있는 햅틱 구성을 설명합니다. 이 구성에서는 두 모터 중 하나가 전체 게임패드를 진동시킬 수 있습니다. 두 질량은 서로 다르므로 각 질량의 효과를 결합하여 더 복잡한 햅틱 효과를 만들 수 있습니다. 이중 진동 효과는 다음 네 가지 매개변수로 정의됩니다.
duration: 진동 효과의 지속 시간을 밀리초 단위로 설정합니다.startDelay: 진동이 시작될 때까지의 지연 시간을 설정합니다.strongMagnitude및weakMagnitude: 무겁고 가벼운 편심 회전 질량 모터의 진동 강도 수준을 설정합니다.0.0~1.0범위로 정규화됩니다.
// This assumes a `Gamepad` as the value of the `gamepad` variable.
const dualRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
if (!('vibrationActuator' in gamepad)) {
return;
}
gamepad.vibrationActuator.playEffect('dual-rumble', {
// Start delay in ms.
startDelay: delay,
// Duration in ms.
duration: duration,
// The magnitude of the weak actuator (between 0 and 1).
weakMagnitude: weak,
// The magnitude of the strong actuator (between 0 and 1).
strongMagnitude: strong,
});
};
트리거 진동
트리거 진동은 두 개의 독립적인 모터에 의해 생성되는 햅틱 반응 효과로, 각 모터는 게임패드의 트리거에 위치합니다.
// This assumes a `Gamepad` as the value of the `gamepad` variable.
const triggerRumble = (gamepad, delay = 0, duration = 100, weak = 1.0, strong = 1.0) => {
if (!('vibrationActuator' in gamepad)) {
return;
}
// Feature detection.
if (!('effects' in gamepad.vibrationActuator) || !gamepad.vibrationActuator.effects.includes('trigger-rumble')) {
return;
}
gamepad.vibrationActuator.playEffect('trigger-rumble', {
// Duration in ms.
duration: duration,
// The left trigger (between 0 and 1).
leftTrigger: leftTrigger,
// The right trigger (between 0 and 1).
rightTrigger: rightTrigger,
});
};
권한 정책과 통합
Gamepad API 사양은 문자열 "gamepad"로 식별되는 정책 제어 기능을 정의합니다. 기본 allowlist는 "self"입니다. 문서의 권한 정책은 해당 문서의 콘텐츠가 navigator.getGamepads()에 액세스할 수 있는지 여부를 결정합니다. 문서에서 사용 중지된 경우 문서의 어떤 콘텐츠도 navigator.getGamepads()를 사용할 수 없으며 gamepadconnected 및 gamepaddisconnected 이벤트가 실행되지 않습니다.
<iframe src="index.html" allow="gamepad"></iframe>
데모
게임패드 테스터 데모는 다음 예에 삽입되어 있습니다. 소스 코드는 GitHub에서 제공됩니다. USB 또는 블루투스를 사용하여 게임패드를 연결하고 버튼을 누르거나 축을 움직여 데모를 사용해 보세요.
보너스: web.dev에서 Chrome 공룡 게임 플레이하기
이 사이트에서 게임패드로 Chrome 공룡 게임을 플레이할 수 있습니다. 소스 코드는 GitHub에서 제공됩니다.
trex-runner.js에서 게임패드 폴링 구현을 확인하고 키 누르기를 에뮬레이션하는 방법을 확인합니다.
Chrome 공룡 게임패드 데모가 작동하도록 핵심 Chromium 프로젝트에서 Chrome 공룡 게임을 추출하고(Arnelle Ballane의 이전 작업 업데이트), 독립형 사이트에 배치하고, 숙이기 및 진동 효과를 추가하여 기존 게임패드 API 구현을 확장하고, 전체 화면 모드를 만들었으며, Mehul Satardekar가 어두운 모드 구현에 기여했습니다. 즐거운 게임 시간 되세요.
유용한 링크
감사의 말씀
이 문서는 프랑수아 보포르와 조 메들리가 검토했습니다. Gamepad API 사양은 Steve Agoston, James Hollyer, Matt Reynolds가 수정합니다. 이전 사양 편집자는 브랜든 존스, 스콧 그레이엄, 테드 미엘차렉입니다. 게임패드 확장 프로그램 사양은 Brandon Jones가 수정합니다.