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

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

Easter Egg ในหน้าออฟไลน์ของ 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.0–1.0
  • buttons: อาร์เรย์ของสถานะปุ่มสำหรับปุ่มทั้งหมดของเกมแพด

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

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

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

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

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

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

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

หากต้องการทราบเมื่อมีการเชื่อมต่อเกมแพด ให้ฟังเหตุการณ์ 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' และ '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: ตั้งค่าระยะเวลาหน่วงจนกว่าการสั่นจะเริ่มขึ้น
  • 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,
  });
};

ทริกเกอร์การสั่น

การสั่นของทริกเกอร์คือเอฟเฟกต์การตอบสนองแบบรู้สึกได้ที่สร้างขึ้นโดยมอเตอร์อิสระ 2 ตัว โดยมีมอเตอร์ 1 ตัวอยู่ในทริกเกอร์แต่ละตัวของเกมแพด

// 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 หรือบลูทูธ แล้วกดปุ่มใดก็ได้หรือเลื่อนแกนใดก็ได้

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

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

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

การรับทราบ

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