ดูวิธีใช้ Gamepad API เพื่อยกระดับเกมบนเว็บไปอีกขั้น
Easter Egg ในหน้าออฟไลน์ของ Chrome เป็นหนึ่งในความลับที่เก็บไว้ได้แย่ที่สุดในประวัติศาสตร์ ([citation needed]
แต่เราพูดแค่ให้ดูมีสีสัน) หากคุณกดแป้น Space หรือแตะไดโนเสาร์ในอุปกรณ์เคลื่อนที่ หน้าออฟไลน์จะกลายเป็นเกมอาร์เคดที่เล่นได้ คุณอาจทราบแล้วว่าไม่จำเป็นต้องออฟไลน์เมื่ออยากเล่น ใน Chrome คุณเพียงแค่ไปที่ about://dino
หรือหากเป็นมือโปรก็ไปที่ about://network-error/-106
แต่คุณทราบไหมว่ามีเกมไดโนเสาร์ Chrome ที่มีผู้เล่นถึง 270 ล้านเกมต่อเดือน
ข้อเท็จจริงอีกข้อที่มีประโยชน์กว่าและคุณอาจไม่ทราบคือคุณสามารถเล่นเกมด้วยเกมแพดในโหมดอาร์เคด มีการเพิ่มการรองรับเกมแพดเมื่อประมาณ 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
ที่แสดงข้อมูลท่าทางที่เชื่อมโยงกับตัวควบคุม WebVRaxes
: อาร์เรย์ของค่าสำหรับแกนเกมแพดทุกแกน โดยมีการปรับให้เป็นมาตรฐานเชิงเส้นกับช่วง-1.0
–1.0
buttons
: อาร์เรย์ของสถานะปุ่มสำหรับปุ่มทั้งหมดของเกมแพด
โปรดทราบว่าปุ่มอาจเป็นแบบดิจิทัล (กดหรือไม่กด) หรือแบบแอนะล็อก (เช่น กด 78%) ด้วยเหตุนี้ ปุ่มจึงได้รับการรายงานเป็นออบเจ็กต์ GamepadButton
ซึ่งมีแอตทริบิวต์ต่อไปนี้
pressed
: สถานะการกดของปุ่ม (true
หากกดปุ่ม และfalse
หากไม่ได้กดปุ่มtouched
: สถานะการแตะของปุ่ม หากปุ่มตรวจจับการสัมผัสได้ พร็อพเพอร์ตี้นี้จะมีค่าเป็นtrue
หากมีการสัมผัสปุ่ม และมีค่าเป็นfalse
ในกรณีอื่นvalue
: สําหรับปุ่มที่มีเซ็นเซอร์อนาล็อก พร็อพเพอร์ตี้นี้จะแสดงจํานวนการกดปุ่ม โดยแปลงเป็นค่ามาตรฐานเชิงเส้นในช่วง0.0
–1.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.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 ตัว โดยมอเตอร์แต่ละตัวจะอยู่ในทริกเกอร์ของเกมแพดแต่ละตัว
// 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