ดูวิธีใช้ 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 แบบ ได้แก่
- 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.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