Mainkan game Chrome dino dengan gamepad

Pelajari cara menggunakan Gamepad API untuk meningkatkan kualitas game web Anda.

Fitur tersembunyi halaman offline Chrome adalah salah satu rahasia yang paling dirahasiakan dalam sejarah ([citation needed], tetapi klaim dibuat untuk efek dramatis). Jika Anda menekan tombol spasi atau, di perangkat seluler, mengetuk dinosaurus, halaman offline akan menjadi game arcade yang dapat dimainkan. Anda mungkin menyadari bahwa Anda sebenarnya tidak harus offline saat ingin bermain: di Chrome, Anda cukup menavigasi ke about://dino, atau, untuk geek dalam diri Anda, jelajahi about://network-error/-106. Tapi tahukah Anda bahwa ada 270 juta game dino Chrome yang dimainkan setiap bulan?

Halaman offline Chrome dengan game dino Chrome.
Tekan tombol spasi untuk bermain.

Fakta lain yang mungkin lebih berguna untuk diketahui dan mungkin tidak Anda sadari adalah bahwa dalam mode arcade, Anda dapat memainkan game dengan gamepad. Dukungan gamepad ditambahkan sekitar setahun lalu pada saat penulisan ini dalam commit oleh Reilly Grant. Seperti yang Anda lihat, game ini, seperti halnya project Chromium lainnya, sepenuhnya merupakan open source. Dalam postingan ini, saya ingin menunjukkan cara menggunakan Gamepad API.

Menggunakan Gamepad API

Deteksi fitur dan dukungan browser

Gamepad API memiliki dukungan browser yang sangat baik secara universal di desktop dan seluler. Anda dapat mendeteksi apakah Gamepad API didukung menggunakan cuplikan berikut:

if ('getGamepads' in navigator) {
  // The API is supported!
}

Cara browser merepresentasikan gamepad

Browser menampilkan gamepad sebagai objek Gamepad. Gamepad memiliki properti berikut:

  • id: String identifikasi untuk gamepad. String ini mengidentifikasi merek atau gaya perangkat gamepad yang terhubung.
  • displayId: VRDisplay.displayId dari VRDisplay yang terkait (jika relevan).
  • index: Indeks gamepad di navigator.
  • connected: Menunjukkan apakah gamepad masih terhubung ke sistem.
  • hand: Enum yang menentukan tangan mana yang dipegang pengontrol, atau yang paling mungkin dipegang.
  • timestamp: Terakhir kali data untuk gamepad ini diperbarui.
  • mapping: Pemetaan tombol dan sumbu yang digunakan untuk perangkat ini, "standard" atau "xr-standard".
  • pose: Objek GamepadPose yang mewakili informasi pose yang terkait dengan pengontrol WebVR.
  • axes: Array nilai untuk semua sumbu gamepad, yang dinormalisasi secara linear ke rentang -1.01.0.
  • buttons: Array status tombol untuk semua tombol gamepad.

Perhatikan bahwa tombol bisa digital (ditekan atau tidak ditekan) atau analog (misalnya, 78% ditekan). Inilah alasan tombol dilaporkan sebagai objek GamepadButton, dengan atribut berikut:

  • pressed: Status tombol yang ditekan (true jika tombol ditekan, dan false jika tidak ditekan.
  • touched: Status tombol yang disentuh. Jika tombol dapat mendeteksi sentuhan, properti ini adalah true jika tombol disentuh, dan false jika tidak.
  • value: Untuk tombol yang memiliki sensor analog, properti ini mewakili jumlah yang digunakan tombol, dinormalisasi secara linear ke rentang 0.01.0.
  • hapticActuators: Array yang berisi objek GamepadHapticActuator, yang masing-masing mewakili hardware respons haptic yang tersedia di pengontrol.

Bergantung pada browser dan gamepad yang Anda miliki, satu hal tambahan yang mungkin Anda temui adalah properti vibrationActuator. Fungsi ini memungkinkan dua jenis efek rumble:

  • Dual-Rumble: Efek respons haptik yang dihasilkan oleh dua aktuator massa berputar eksentrik, satu di setiap genggaman gamepad.
  • Trigger-Rumble: Efek respons haptik yang dihasilkan oleh dua motor independen, dengan satu motor yang terletak di setiap pemicu gamepad.

Ringkasan skema berikut, yang diambil langsung dari spesifikasi, menampilkan pemetaan serta pengaturan tombol dan sumbu pada gamepad generik.

Ringkasan skema pemetaan tombol dan sumbu gamepad umum.
Representasi visual tata letak gamepad standar (Sumber).

Notifikasi saat gamepad terhubung

Untuk mengetahui kapan gamepad terhubung, proses peristiwa gamepadconnected yang dipicu pada objek window. Saat pengguna menghubungkan gamepad, yang dapat terjadi menggunakan USB atau Bluetooth, GamepadEvent akan diaktifkan yang memiliki detail gamepad dalam properti gamepad yang diberi nama dengan tepat. Di bawah ini, Anda dapat melihat contoh dari pengontrol Xbox 360 yang telah saya letakkan (ya, saya menyukai game retro).

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"}
  */
});

Notifikasi saat gamepad terputus

Notifikasi pemutusan gamepad terjadi secara analog dengan cara koneksi terdeteksi. Kali ini aplikasi akan memproses peristiwa gamepaddisconnected. Perhatikan bagaimana dalam contoh berikut connected sekarang menjadi false saat saya mencabut pengontrol 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
  */
});

Gamepad dalam game loop Anda

Mendapatkan gamepad dimulai dengan panggilan ke navigator.getGamepads(), yang menampilkan array dengan item Gamepad. Array di Chrome selalu memiliki panjang tetap empat item. Jika nol atau kurang dari empat gamepad terhubung, item mungkin hanya null. Selalu pastikan untuk memeriksa semua item array dan perlu diketahui bahwa gamepad "mengingat" slotnya dan mungkin tidak selalu ada di slot pertama yang tersedia.

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

Jika satu atau beberapa gamepad terhubung, tetapi navigator.getGamepads() masih melaporkan item null, Anda mungkin perlu "mengaktifkan" setiap gamepad dengan menekan salah satu tombolnya. Anda kemudian dapat memeriksa status gamepad di game loop seperti yang ditunjukkan pada kode berikut.

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();

Aktuator getaran

Properti vibrationActuator menampilkan objek GamepadHapticActuator, yang sesuai dengan konfigurasi motor atau aktuator lain yang dapat menerapkan gaya untuk tujuan respons haptik. Efek sentuhan dapat diputar dengan memanggil Gamepad.vibrationActuator.playEffect(). Satu-satunya jenis efek yang valid adalah 'dual-rumble'. Dual-rumble menjelaskan konfigurasi haptic dengan motor getaran massa berputar eksentrik di setiap tuas gamepad standar. Dalam konfigurasi ini, salah satu motor mampu menggetarkan seluruh gamepad. Kedua massa tersebut tidak sama sehingga efek dari masing-masing massa tersebut dapat digabungkan untuk menciptakan efek haptik yang lebih kompleks. Efek dual-rumble ditentukan oleh empat parameter:

  • duration: Menyetel durasi efek getaran dalam milidetik.
  • startDelay: Menyetel durasi penundaan hingga getaran dimulai.
  • strongMagnitude dan weakMagnitude: Menyetel tingkat intensitas getaran untuk motor massa berputar eksentrik yang lebih berat dan lebih ringan, yang dinormalisasi ke rentang 0.01.0.

Efek rumble yang didukung

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.
}

Suara gemuruh ganda

// 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,
  });
};

Picu gemuruh

// 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,
  });
};

Integrasi dengan Kebijakan Izin

Spesifikasi Gamepad API menentukan fitur yang dikontrol kebijakan yang diidentifikasi oleh string "gamepad". allowlist default-nya adalah "self". Kebijakan izin dokumen menentukan apakah konten dalam dokumen tersebut diizinkan untuk mengakses navigator.getGamepads(). Jika dinonaktifkan di dokumen apa pun, konten dalam dokumen tidak akan diizinkan untuk menggunakan navigator.getGamepads(), begitu juga peristiwa gamepadconnected dan gamepaddisconnected.

<iframe src="index.html" allow="gamepad"></iframe>

Demo

Demo penguji gamepad disematkan dalam contoh berikut. Kode sumbernya tersedia di Glitch. Coba demo dengan menghubungkan gamepad menggunakan USB atau Bluetooth, lalu menekan salah satu tombol atau menggerakkan salah satu sumbunya.

Bonus: mainkan Chrome dino di web.dev

Anda dapat bermain dino Chrome dengan gamepad di situs ini. Kode sumber tersedia di GitHub. Lihat implementasi polling gamepad di trex-runner.js dan perhatikan bagaimana implementasi ini mengemulasi penekanan tombol.

Agar demo gamepad dino Chrome berfungsi, saya telah menghapus game dino Chrome dari project Chromium inti (mengupdate upaya sebelumnya oleh Arnelle Ballane), menempatkannya di situs mandiri, memperluas implementasi API gamepad yang ada dengan menambahkan efek pengecilan dan getaran, membuat mode layar penuh, dan Mehul Satardekar berkontribusi dalam implementasi mode gelap. Selamat bermain game!

Ucapan terima kasih

Dokumen ini ditinjau oleh François Beaufort dan Joe Medley. Spesifikasi Gamepad API diedit oleh Steve Agoston, James Hollyer, dan Matt Reynolds. Mantan editor spesifikasi adalah Brandon Jones, Scott Graham, dan Ted Mielczarek. Spesifikasi Ekstensi Gamepad diedit oleh Brandon Jones. Banner besar oleh Laura Torrent Puig.