Oyun kumandanızla Chrome dinozor oyununu oynayın

Web oyunlarınızı bir üst seviyeye çıkarmak için Gamepad API'yi nasıl kullanacağınızı öğrenin.

Chrome'un çevrimdışı sayfası paskalya yumurtası, tarihin en kötü saklanan sırlarından biridir ([citation needed], ancak bu iddianın amacı dramatik etki yaratmaktır). Boşluk tuşuna basarsanız veya mobil cihazlarda dinozora dokunursanız çevrimdışı sayfa oynanabilir bir arcade oyununa dönüşür. Oyun oynamak istediğinizde internete bağlı olmanız gerekmediğini biliyor olabilirsiniz: Chrome'da about://dino'e gidebilir veya meraklıysanız about://network-error/-106'e göz atabilirsiniz. Ancak her ay 270 milyon Chrome dinozor oyunu oynandığını biliyor muydunuz?

Chrome'un Chrome dinozor oyununun bulunduğu çevrimdışı sayfası.
Oynamak için boşluk tuşuna basın.

Bilmenizin faydalı olabileceği bir diğer bilgi de, arcade modunda oyunu gamepad ile oynayabileceğinizdir. Gamepad desteği, bu makalenin yazıldığı tarih itibarıyla yaklaşık bir yıl önce Reilly Grant tarafından yapılan bir commit eklendi. Gördüğünüz gibi oyun, Chromium projesinin geri kalanı gibi tamamen açık kaynaktır. Bu gönderide, Gamepad API'nin nasıl kullanılacağını göstermek istiyorum.

Gamepad API'yi kullanma

Özellik algılama ve tarayıcı desteği

Gamepad API, hem masaüstü hem de mobil cihazlarda evrensel olarak mükemmel tarayıcı desteğine sahiptir. Aşağıdaki snippet'i kullanarak Gamepad API'nin desteklenip desteklenmediğini algılayabilirsiniz:

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

Tarayıcı, oyun kumandasını nasıl temsil eder?

Tarayıcı, gamepad'leri Gamepad nesnesi olarak temsil eder. Gamepad aşağıdaki özelliklere sahiptir:

  • id: Gamepad için bir tanımlayıcı dizesi. Bu dize, bağlı gamepad cihazının markasını veya tarzını tanımlar.
  • displayId: İlgili VRDisplay öğesinin VRDisplay.displayId değeri (varsa).
  • index: Gezgindeki oyun kumandasının dizini.
  • connected: Gamepad'in sisteme bağlı olup olmadığını belirtir.
  • hand: Kumandanın hangi elde tutulduğunu veya büyük olasılıkla tutulacağını tanımlayan bir enum.
  • timestamp: Bu gamepad'in verilerinin en son güncellendiği tarih.
  • mapping: Bu cihazda kullanılan düğme ve eksen eşlemesidir ("standard" veya "xr-standard").
  • pose: WebVR denetleyiciyle ilişkili duruş bilgilerini temsil eden bir GamepadPose nesnesi.
  • axes: Gamepad'in tüm eksenleri için -1.0-1.0 aralığına göre doğrusal olarak normalleştirilmiş bir değer dizisi.
  • buttons: Oyun kumandasının tüm düğmelerinin düğme durumları dizisi.

Düğmelerin dijital (basılı veya basılı değil) ya da analog (ör. %78 oranında basılı) olabileceğini unutmayın. Bu nedenle düğmeler, aşağıdaki özelliklere sahip GamepadButton nesneleri olarak raporlanır:

  • pressed: Düğmenin basılı durumu (düğmeye basılmışsa true, basılı değilse false).
  • touched: Düğmenin dokunulduğu durum. Düğme dokunmayı algılayabiliyorsa bu özellik, düğmeye dokunuluyorsa true, aksi takdirde false değerini alır.
  • value: Analog sensör içeren düğmeler için bu özellik, düğmeye basılan miktarı temsil eder ve 0.0-1.0 aralığında doğrusal olarak normalleştirilir.
  • hapticActuators: Her biri denetleyicide bulunan dokunma geri bildirimi donanımını temsil eden GamepadHapticActuator öğeleri içeren bir dizi.

Tarayıcınız ve gamepad'inize bağlı olarak karşılaşabileceğiniz bir diğer şey de vibrationActuator mülküdür. İki tür titreşim efekti sağlar:

  • Çift titreşim: Oyun kolunun her bir tutacağındaki iki eksantrik dönen kütle aktüatörü tarafından oluşturulan dokunsal geri bildirim efekti.
  • Tetikleyici titreşimi: İki bağımsız motor tarafından oluşturulan dokunma geri bildirimi efekti. Bu motorlardan biri gamepad'in tetikleyicilerinin her birinde bulunur.

Doğrudan spesifikasyondan alınan aşağıdaki şematik genel bakış, genel bir gamepad'deki düğmelerin ve eksenlerin haritalarını ve düzenini gösterir.

Genel bir gamepad'in düğme ve eksen eşlemelerine şematik genel bakış.
Standart bir oyun kumandası düzeninin görsel temsili (Kaynak).

Oyun kumandası bağlandığında bildirim

Bir gamepad'in ne zaman bağlandığını öğrenmek için window nesnesinde tetiklenen gamepadconnected etkinliğini dinleyin. Kullanıcı bir gamepad bağladığında (USB veya Bluetooth kullanılarak yapılabilir), gamepad'in ayrıntılarını uygun şekilde adlandırılmış bir gamepad mülkünde içeren bir GamepadEvent tetiklenir. Aşağıda, elimde bulunan bir Xbox 360 kumandasından alınmış bir örneği görebilirsiniz (Evet, retro oyunlara meraklıyım).

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

Gamepad bağlantısı kesildiğinde bildirim

Gamepad bağlantılarının kesilmesi hakkında bildirim alma, bağlantıların algılanma şekline benzer şekilde gerçekleşir. Bu kez uygulama gamepaddisconnected etkinliğini dinler. Aşağıdaki örnekte, Xbox 360 kumandasının fişi çekildiğinde connected değerinin false olarak nasıl değiştiğine dikkat edin.

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

Oyun döngüsünüzdeki oyun kumandası

Gamepad'i kullanmaya başlamak için navigator.getGamepads() çağrısı yapılır. Bu çağrı, Gamepad öğe içeren bir dizi döndürür. Chrome'daki dizinin uzunluğu her zaman dört öğe olarak sabitlenmiştir. Sıfır veya dörtten az gamepad bağlıysa öğe yalnızca null olabilir. Her zaman dizinin tüm öğelerini kontrol edin. Gamepad'lerin, kullandıkları yuvanın yerini "hatırladığını" ve her zaman ilk kullanılabilir yuvaya yerleştirilmeyebileceğini unutmayın.

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

Bir veya daha fazla gamepad bağlıysa ancak navigator.getGamepads() hâlâ null öğe bildiriyorsa her gamepad'in düğmelerinden birine basarak gamepad'i "uyandırmanız" gerekebilir. Ardından, aşağıdaki kodda gösterildiği gibi oyun döngüsünüzde gamepad durumlarını anketleyebilirsiniz.

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

Titreşim aktüatörü

vibrationActuator mülkü, dokunma geri bildirimi amacıyla kuvvet uygulayabilen motorların veya diğer aktüatörlerin yapılandırmasına karşılık gelen bir GamepadHapticActuator nesnesi döndürür. Dokunma etkileri Gamepad.vibrationActuator.playEffect() çağrılarak oynatılabilir. Geçerli tek efekt türleri 'dual-rumble' ve 'trigger-rumble''dir.

Desteklenen titreşim efektleri

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

Çift titreşim

Çift titreşim, standart bir gamepad'in her koluna eksantrik dönen kütle titreşim motoru yerleştirilmiş bir dokunma yapılandırmasını ifade eder. Bu yapılandırmada her iki motor da gamepad'in tamamını titreştirebilir. İki kütle eşit değildir. Böylece her birinin etkileri birleştirilerek daha karmaşık dokunma etkileri oluşturulabilir. Çift titreşim efektleri dört parametreyle tanımlanır:

  • duration: Titreşim efektinin süresini milisaniye cinsinden ayarlar.
  • startDelay: Titreşimin başlatılmasına kadar geçecek süreyi ayarlar.
  • strongMagnitude ve weakMagnitude: Daha ağır ve daha hafif eksantrik dönen kütle motorları için titreşim yoğunluğu seviyelerini 0.0-1.0 aralığına göre normalleştirilmiş olarak ayarlayın.
// 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,
  });
};

Titreşimi tetikleme

Tetik titreşimi, gamepad'in tetikleyicilerinin her birinde bulunan bir motorla iki bağımsız motor tarafından oluşturulan dokunsal geri bildirim efektidir.

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

İzin Politikası ile entegrasyon

Gamepad API spesifikasyonu, "gamepad" dizesi ile tanımlanan bir politika kontrollü özelliği tanımlar. Varsayılan allowlist değeri "self"'dur. Bir dokümanın izin politikası, söz konusu dokümandaki içeriklerin navigator.getGamepads()'e erişip erişemeyeceğini belirler. Herhangi bir dokümanda devre dışı bırakılırsa dokümanda hiçbir içeriğin navigator.getGamepads() kullanmasına izin verilmez ve gamepadconnected ile gamepaddisconnected etkinlikleri tetiklenmez.

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

Demo

Aşağıdaki örnekte bir gamepad test cihazı demosu yerleştirilmiştir. Kaynak koduna Glitch'ten ulaşabilirsiniz. USB veya Bluetooth kullanarak bir gamepad bağlayın ve düğmelerinden birine basarak veya eksenlerinden birini hareket ettirerek denemeyi deneyin.

Bonus: web.dev'de Chrome Dinozor'u oynayın

Bu sitede gamepad'inizle Chrome dinozor oyununu oynayabilirsiniz. Kaynak kodunu GitHub'da bulabilirsiniz. trex-runner.js bölümündeki gamepad anketi uygulamasına göz atın ve tuş basmalarını nasıl taklit ettiğini öğrenin.

Chrome dino gamepad demosunun çalışması için Chrome dinozor oyununu ana Chromium projesinden çıkardım (Arnelle Ballane tarafından yapılan önceki bir çalışmayı güncelledim), bağımsız bir siteye yerleştirdim, eğme ve titreşim efektleri ekleyerek mevcut gamepad API uygulamasını genişlettim, tam ekran modu oluşturdum ve Mehul Satardekar karanlık mod uygulamasıyla katkıda bulundu. İyi oyunlar dileriz.

Teşekkür ederiz

Bu doküman François Beaufort ve Joe Medley tarafından incelenmiştir. Gamepad API spesifikasyonu, Steve Agoston, James Hollyer ve Matt Reynolds tarafından düzenlenmiştir. Eski spesifikasyon editörleri Brandon Jones, Scott Graham ve Ted Mielczarek'tir. Oyun kumandası uzantıları spesifikasyonu Brandon Jones tarafından düzenlenmiştir. Laura Torrent Puig tarafından oluşturulan hero resim.