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

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

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

หน้าออฟไลน์ของ Chrome ที่มีเกมไดโนเสาร์ Chrome
กดแป้นเว้นวรรคเพื่อเล่น

ข้อเท็จจริงอีกข้อที่มีประโยชน์กว่าและคุณอาจไม่ทราบคือคุณสามารถเล่นเกมด้วยเกมแพดในโหมดอาร์เคด มีการเพิ่มการรองรับเกมแพดเมื่อประมาณ 1 ปีที่แล้วในขณะที่มีการเขียนเรื่องนี้โดยcommitโดย 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: อาร์เรย์แบบจำกัดที่กำหนดว่าตัวควบคุมถือด้วยมือข้างใด หรือมีแนวโน้มที่จะถือด้วยมือข้างใดมากที่สุด
  • 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 แบบ ได้แก่

  • การสั่นแบบคู่: ผลป้อนกลับแบบสัมผัสที่เกิดจากตัวกระตุ้นมวลหมุนแบบเอนซิงค์ 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
  */
});

เกมแพดในลูปเกม

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

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

หากเกมแพดอย่างน้อย 1 เครื่องเชื่อมต่ออยู่ แต่ 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.
}

การสั่นแบบคู่

การสั่นแบบคู่อธิบายถึงการกำหนดค่าการสัมผัสด้วยมอเตอร์สั่นแบบมวลหมุนเยื้องกันในด้ามจับแต่ละด้ามของเกมแพดมาตรฐาน ในการกำหนดค่าแบบนี้ มอเตอร์ตัวใดก็ได้จะสั่นทั้งเกมแพดได้ มวลทั้ง 2 ก้อนไม่เท่ากันเพื่อให้สามารถรวมผลของมวลแต่ละก้อนเข้าด้วยกันเพื่อสร้างเอฟเฟกต์การสัมผัสที่ซับซ้อนยิ่งขึ้น เอฟเฟกต์การสั่นแบบคู่จะกำหนดโดยพารามิเตอร์ 4 รายการต่อไปนี้

  • duration: ตั้งค่าระยะเวลาของเอฟเฟกต์การสั่นเป็นมิลลิวินาที
  • startDelay: ตั้งค่าระยะเวลาหน่วงเวลาจนกว่าการสั่นจะเริ่มขึ้น
  • strongMagnitude และ weakMagnitude: ตั้งค่าระดับความเข้มการสั่นสะเทือนสำหรับมอเตอร์มวลการหมุนที่มีน้ำหนักและเบากว่าและปรับเป็นช่วงมาตรฐานที่ช่วง 0.01.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 ตัว โดยมอเตอร์แต่ละตัวจะอยู่ในทริกเกอร์ของเกมแพดแต่ละตัว

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

สาธิต

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

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

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

เราได้แยกเกมไดโนเสาร์ 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