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

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

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

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

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

หากเชื่อมต่อเกมแพด 1 หรือ 2 ตัว แต่ 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