게임패드로 Chrome 공룡 게임을 플레이하세요.

Gamepad API를 사용하여 웹 게임을 한 단계 업그레이드하는 방법을 알아보세요.

Chrome의 오프라인 페이지 이스터 에그는 역사상 가장 잘 지켜지지 않은 비밀 중 하나입니다 ([citation needed], 극적인 효과를 위해 주장함). 스페이스 키를 누르거나 휴대기기에서 공룡을 탭하면 오프라인 페이지가 플레이 가능한 아케이드 게임으로 바뀝니다. 게임을 즐기고 싶을 때 실제로 오프라인으로 전환할 필요는 없습니다. Chrome에서 about://dino로 이동하거나 긱이라면 about://network-error/-106로 이동하면 됩니다. 하지만 매달 2억 7천만 건의 Chrome 공룡 게임이 플레이된다는 사실을 알고 계셨나요?

Chrome 공룡 게임이 있는 Chrome의 오프라인 페이지
스페이스바를 눌러 재생하세요.

아케이드 모드에서는 게임 패드로 게임을 플레이할 수 있다는 사실도 알아두면 유용합니다. 게임패드 지원은 이 글을 작성하는 시점으로부터 약 1년 전에 Reilly Grantcommit에서 추가되었습니다. 보시다시피 이 게임은 나머지 Chromium 프로젝트와 마찬가지로 완전히 오픈소스입니다. 이 게시물에서는 Gamepad API를 사용하는 방법을 보여드리겠습니다.

Gamepad API 사용

기능 감지 및 브라우저 지원

Gamepad API는 데스크톱과 모바일 모두에서 전반적으로 우수한 브라우저 지원을 제공합니다. 다음 스니펫을 사용하여 Gamepad API가 지원되는지 감지할 수 있습니다.

if ('getGamepads' in navigator) {
  // The API is supported!
}

브라우저가 게임패드를 나타내는 방법

브라우저는 게임패드를 Gamepad 객체로 나타냅니다. Gamepad에는 다음과 같은 속성이 있습니다.

  • id: 게임패드의 식별 문자열입니다. 이 문자열은 연결된 게임패드 기기의 브랜드 또는 스타일을 식별합니다.
  • displayId: 연결된 VRDisplayVRDisplay.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 속성도 발생할 수 있습니다. 다음과 같은 두 가지 종류의 럼블 효과를 허용합니다.

  • Dual-Rumble: 게임패드의 그립마다 하나씩 있는 두 개의 편심 회전 질량 액추에이터에 의해 생성되는 햅틱 효과입니다.
  • 트리거 럼블: 두 개의 독립형 모터에서 생성되는 햅틱 효과로, 각 게임패드의 트리거에 모터 하나가 있습니다.

다음의 개략적인 개요는 사양에서 직접 가져온 것으로, 일반 게임패드의 버튼과 축의 매핑 및 배열을 보여줍니다.

일반적인 게임패드의 버튼 및 축 매핑에 관한 개요입니다.
표준 게임패드 레이아웃의 시각적 표현(소스).

게임패드가 연결되면 알림

게임패드가 연결된 시점을 알아보려면 window 객체에서 트리거되는 gamepadconnected 이벤트를 수신 대기합니다. 사용자가 USB 또는 블루투스를 사용하여 게임패드를 연결하면 적절한 이름의 gamepad 속성에 게임패드 세부정보가 포함된 GamepadEvent가 실행됩니다. 다음은 제가 가지고 있는 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 컨트롤러의 전원을 뽑으면 connectedfalse이 됩니다.

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
  */
});

게임 루프의 게임패드

게임패드를 가져오는 작업은 Gamepad 항목이 포함된 배열을 반환하는 navigator.getGamepads() 호출로 시작됩니다. Chrome의 배열은 항상 4개의 항목으로 고정된 길이를 갖습니다. 연결된 게임패드가 0개 이하인 경우 항목이 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.
}

듀얼 럼블

Dual-rumble은 표준 게임패드의 각 핸들에 편심 회전 질량 진동 모터가 있는 햅틱 구성을 나타냅니다. 이 구성에서는 어느 모터나 전체 게임패드를 진동시킬 수 있습니다. 두 질량은 서로 다르므로 각 질량의 효과를 결합하여 더 복잡한 햅틱 효과를 만들 수 있습니다. 듀얼 럼블 효과는 다음 4가지 매개변수로 정의됩니다.

  • duration: 진동 효과의 지속 시간을 밀리초 단위로 설정합니다.
  • startDelay: 진동이 시작될 때까지의 지연 시간을 설정합니다.
  • strongMagnitudeweakMagnitude: 더 무겁고 가벼운 편심 회전 질량 모터의 진동 강도 수준을 설정합니다(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()를 사용할 수 없으며 gamepadconnectedgamepaddisconnected 이벤트가 실행되지 않습니다.

<iframe src="index.html" allow="gamepad"></iframe>

데모

다음 예에는 게임패드 테스터 데모가 삽입되어 있습니다. 소스 코드는 Glitch에서 확인할 수 있습니다. USB 또는 블루투스를 사용하여 게임패드를 연결하고 버튼을 누르거나 축을 움직여 데모를 사용해 보세요.

보너스: web.dev에서 Chrome 공룡 플레이하기

이 사이트에서 게임패드로 Chrome 공룡을 플레이할 수 있습니다. 소스 코드는 GitHub에서 확인할 수 있습니다. trex-runner.js에서 게임패드 폴링 구현을 확인하고 키 누르기를 에뮬레이션하는 방법을 확인합니다.

Chrome 공룡 게임패드 데모가 작동하도록 핵심 Chromium 프로젝트에서 Chrome 공룡 게임을 리핑하고 (아르넬 발라네이전 작업 업데이트), 독립형 사이트에 배치하고, 덕킹 및 진동 효과를 추가하여 기존 게임패드 API 구현을 확장하고, 전체 화면 모드를 만들었습니다. Mehul Satardekar님이 어두운 모드 구현에 참여했습니다. 즐거운 게임 되시기 바랍니다.

감사의 말씀

이 문서는 프랑소와 보포르조 미들리가 검토했습니다. Gamepad API 사양은 스티브 아고스톤, 제임스 홀리어, 맷 레이놀즈가 수정합니다. 이전 사양 편집자는 브랜든 존스, 스콧 그레이엄, 테드 미엘차렉입니다. Gamepad Extensions 사양은 브랜든 존스가 수정합니다. 로라 토렌트 푸이그가 제작한 히어로 이미지