เล่นเกมไดโนเสาร์ Chrome ด้วยเกมแพด

เรียนรู้วิธีใช้ Gamepad API เพื่อยกระดับเกมบนเว็บของคุณไปอีกขั้น

ไข่อีสเตอร์ในหน้าออฟไลน์ของ Chrome เป็นหนึ่งในความลับที่เลวร้ายที่สุดในประวัติศาสตร์ ([citation needed] แต่การกล่าวอ้างว่ามีผลอย่างมาก) หากคุณกดแป้น Space หรือบนอุปกรณ์เคลื่อนที่ ให้แตะไดโนเสาร์ หน้าออฟไลน์จะกลายเป็นเกมอาร์เคดที่เล่นได้ คุณอาจรู้ว่าจริงๆ แล้วคุณไม่ต้องออฟไลน์เมื่ออยากเล่นเกม เพราะใน Chrome คุณสามารถไปที่ about://dino หรือไปที่ about://network-error/-106 เพื่อแนะนำคุณก็ได้ แต่คุณทราบหรือไม่ว่า มีเกมไดโนเสาร์ Chrome ที่มีการเล่นถึง 270 ล้านเกมทุกเดือน

หน้าออฟไลน์ของ Chrome ที่มีเกมไดโนเสาร์ 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.displayId ของ VRDisplay ที่เชื่อมโยง (หากเกี่ยวข้อง)
  • index: ดัชนีของเกมแพดในตัวนำทาง
  • connected: ระบุว่าเกมแพดยังเชื่อมต่อกับระบบอยู่หรือไม่
  • hand: enum เป็นตัวกำหนดว่าอุปกรณ์ใช้มือใดอยู่ หรือน่าจะถือไว้ในมือ
  • timestamp: ครั้งล่าสุดที่มีการอัปเดตข้อมูลสำหรับเกมแพดนี้
  • mapping: ปุ่มและการแมปแกนที่ใช้กับอุปกรณ์นี้ ซึ่งอาจเป็น "standard" หรือ "xr-standard"
  • pose: ออบเจ็กต์ GamepadPose ที่แสดงข้อมูลท่าทางที่เชื่อมโยงกับตัวควบคุม WebVR
  • axes: อาร์เรย์ของค่าสำหรับแกนทั้งหมดของเกมแพด โดยปรับให้เป็นเส้นตรงในช่วง -1.01.0
  • buttons: อาร์เรย์ของสถานะปุ่มสำหรับปุ่มทั้งหมดของเกมแพด

โปรดทราบว่าปุ่มอาจเป็นรูปแบบดิจิทัล (กดหรือไม่กด) หรือแอนะล็อก (เช่น กดไป 78%) นี่คือเหตุผลที่ปุ่มถูกรายงานเป็นออบเจ็กต์ GamepadButton โดยมีแอตทริบิวต์ต่อไปนี้

  • pressed: สถานะที่มีการกด (true หากกดปุ่ม และ false หากไม่ได้กด
  • touched: สถานะการแตะปุ่ม หากปุ่มตรวจจับการแตะได้ พร็อพเพอร์ตี้นี้จะ true หากถูกแตะปุ่ม และ false ในกรณีอื่นๆ
  • value: สำหรับปุ่มที่มีเซ็นเซอร์แอนะล็อก คุณสมบัตินี้หมายถึงจำนวนที่มีการกดปุ่มดังกล่าว ซึ่งปรับให้เป็นเส้นตรงในช่วง 0.01.0
  • hapticActuators: อาร์เรย์ที่มีออบเจ็กต์ GamepadHapticActuator ซึ่งแต่ละรายการแสดงถึงฮาร์ดแวร์การตอบสนองแบบรู้สึกได้ที่มีอยู่ในตัวควบคุม

อีกสิ่งหนึ่งที่คุณอาจพบก็คือพร็อพเพอร์ตี้ vibrationActuator ทั้งนี้ขึ้นอยู่กับเบราว์เซอร์และเกมแพดที่คุณมี โดยจะสร้างเอฟเฟกต์แตกได้ 2 แบบ ดังนี้

  • Dual-Rumble: เอฟเฟกต์การตอบสนองแบบรู้สึกได้ที่เกิดจากเครื่องเปิดใช้งานมวลชนแบบหมุนที่ผิดปกติ 2 ตัว ซึ่งใช้ในแต่ละการจับของเกมแพด
  • ทริกเกอร์-รุมเบิล: การตอบสนองแบบรู้สึกได้ที่เกิดจากมอเตอร์อิสระ 2 ตัว โดยมีมอเตอร์ 1 ตัวอยู่ในทริกเกอร์ของแต่ละเกมแพด

ภาพรวมแบบแผนต่อไปนี้ซึ่งมาจากข้อกำหนดโดยตรงจะแสดงการแมปและการจัดเรียงปุ่มและแกนบนเกมแพดทั่วไป

ภาพรวมแบบสคีมาของการแมปปุ่มและแกนของเกมแพดทั่วไป
การแสดงภาพเลย์เอาต์เกมแพดมาตรฐาน (แหล่งที่มา)

การแจ้งเตือนเมื่อมีการเชื่อมต่อเกมแพด

หากต้องการทราบเวลาที่เกมแพดเชื่อมต่ออยู่ ให้คอยฟังเหตุการณ์ gamepadconnected ที่ทริกเกอร์บนออบเจ็กต์ window เมื่อผู้ใช้เชื่อมต่อเกมแพด ซึ่งอาจเกิดขึ้นเมื่อใช้ 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 โปรดสังเกตวิธีในตัวอย่างต่อไปนี้ connected ตอนนี้คือfalseเมื่อฉันถอดปลั๊กตัวควบคุม Xbox 360

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

เกมแพดใน Game Loop ของคุณ

การรับเกมแพดจะเริ่มต้นด้วยการเรียกไปยัง navigator.getGamepads() ซึ่งจะแสดงผลอาร์เรย์ที่มี Gamepad รายการ อาร์เรย์ใน Chrome ทุกครั้งมีความยาวคงที่อยู่ที่ 4 รายการ หากเกมแพดเชื่อมต่ออยู่ 0 หรือน้อยกว่า 4 รายการ ไอเทมอาจมีแค่ null อย่าลืมตรวจสอบรายการทั้งหมดในอาร์เรย์ และโปรดทราบว่าเกมแพดจะ "จำ" ช่องของมันไว้และอาจไม่ได้แสดงในช่องแรกที่พร้อมใช้งานเสมอไป

// When no gamepads are connected:
navigator.getGamepads();
// (4) [null, null, null, null]

หากเกมแพดอย่างน้อย 1 เกมเชื่อมต่ออยู่ แต่ navigator.getGamepads() ยังคงรายงาน null รายการ คุณอาจต้อง "ปลุกระบบ" เกมแพดแต่ละเกมด้วยการกดปุ่มใดก็ได้ จากนั้นคุณจะสำรวจสถานะของเกมแพด ใน Game Loop ได้ดังที่แสดงในโค้ดต่อไปนี้

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' Dual-rumble อธิบายการกำหนดค่าแบบรู้สึกได้ที่มีมอเตอร์สั่นสะเทือนขนาดใหญ่และหมุนได้แบบประหลาดในแฮนเดิลแต่ละอันของเกมแพดมาตรฐาน ในการกำหนดค่านี้ มอเตอร์สามารถสั่นทั้งเกมแพดได้ มวลทั้ง 2 ชนิดไม่เท่ากัน จึงนำผลของมวลแต่ละชนิดมารวมกันเพื่อสร้างเอฟเฟกต์แบบรู้สึกได้ที่ซับซ้อนมากขึ้น เอฟเฟกต์แบบ Dual-rumble กำหนดโดยพารามิเตอร์ 4 ตัวดังนี้

  • duration: ตั้งค่าระยะเวลาของเอฟเฟกต์การสั่นเป็นมิลลิวินาที
  • startDelay: ตั้งระยะหน่วงเวลาจนกว่าการสั่นจะเริ่ม
  • strongMagnitude และ weakMagnitude: ตั้งระดับความเข้มของการสั่นสำหรับมอเตอร์มวลแบบหมุนนอกศูนย์กลางที่หนักกว่าและเบากว่า ซึ่งปรับให้เป็นช่วง 0.01.0 แล้ว

เอฟเฟกต์ 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.
}

ตะลุมบอน

// 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>

ข้อมูลประชากร

มีการสาธิตสำหรับผู้ทดสอบ Gamepad ในตัวอย่างต่อไปนี้ ซอร์สโค้ดมีให้ใช้งานใน Glitch ลองดูการสาธิตโดยเชื่อมต่อเกมแพดโดยใช้ USB หรือบลูทูธ แล้วกดปุ่มใดก็ได้หรือขยับแกนเกม

โบนัส: เล่นไดโนเสาร์ Chrome ใน web.dev

คุณเล่นไดโนเสาร์ Chrome ด้วยเกมแพดในเว็บไซต์นี้ได้ ซอร์สโค้ดมีอยู่ใน GitHub ดูการใช้งานแบบสำรวจเกมแพดใน trex-runner.js และดูว่าฟีเจอร์นี้จำลองการกดแป้นได้อย่างไร

เพื่อให้การสาธิตเกมแพดไดโนเสาร์ Chrome ใช้งานได้ ผมได้นำเกมไดโนเสาร์ Chrome ออกจากโปรเจ็กต์ Chromium หลัก (อัปเดตความพยายามก่อนหน้านี้โดย Arnelle Ballane) วางบนเว็บไซต์แบบสแตนด์อโลน ขยายการติดตั้งใช้งาน API เกมแพดที่มีอยู่ด้วยการเพิ่มเอฟเฟกต์การลดเสียงและการสั่น สร้างโหมดเต็มหน้าจอ และ Mehul Satardekar ขอให้สนุกกับการเล่นเกม

ข้อความแสดงการยอมรับ

เอกสารนี้ได้รับการตรวจสอบโดย François Beaufort และ Joe Medley ข้อกำหนดของ Gamepad API ได้รับการแก้ไขโดย Steve Agoston, James Hollyer และ Matt Reynolds อดีตบรรณาธิการข้อกำหนดคือ Brandon Jones, Scott Graham และ Ted Mielczarek ข้อกำหนดของส่วนขยายเกมแพดได้รับการแก้ไขโดย Brandon Jones รูปภาพหลักโดย Laura Torrent Puig