Skip to content

Oyun Girdilerini İşleme

İyi bir girdi sistemi, bir oyunu başarıya da başarısızlığa da götürebilir. Gecikmeli kontroller, algılanmayan girdiler veya kafa karıştırıcı düğme eşlemeleri, oyuncular daha oyununuzun içeriğini görmeden onları bezdirir. Bu eğitimde klavye, fare, dokunma ve oyun kumandası girdilerini birleşik bir şekilde nasıl işleyeceğinizi öğreneceksiniz.

Önce deneyin:

Girdi Durumu Kalıbı

Basit yaklaşım, olaylara doğrudan tepki vermektir: kullanıcı bir tuşa bastığında bir şey yap. Bu yaklaşım sorunlara yol açar. Olaylar öngörülemeyen zamanlarda tetiklenirken oyun döngünüz sabit bir hızda çalışır. Oyuncuyu keydown işleyicisinde hareket ettirirseniz hareket hızı, işletim sistemine göre değişen tuş tekrarlama hızına bağlı olur.

Bunun yerine, oyun döngünüzün okuyacağı bir girdi durumu nesnesi tutun:

js
const input = {
  left: false,
  right: false,
  up: false,
  down: false,
  action: false,
  pointer: { x: 0, y: 0, down: false },
}

Olay işleyicileri bu bayrakları ayarlar. Oyun döngüsü de bunları okur. Bu ayrıştırma her şeyi kolaylaştırır: güncelleme mantığınız, girdinin klavyeden, dokunmadan veya oyun kumandasından gelip gelmediğini önemsemez.

Klavye

Fiziksel tuş konumunu belirttiği için e.key yerine e.code kullanın. Böylece WASD, QWERTY dışındaki klavye düzenlerinde de doğru çalışır.

js
const keyMap = {
  ArrowLeft: 'left',
  ArrowRight: 'right',
  ArrowUp: 'up',
  ArrowDown: 'down',
  KeyA: 'left',
  KeyD: 'right',
  KeyW: 'up',
  KeyS: 'down',
  Space: 'action',
}

window.addEventListener('keydown', (e) => {
  const action = keyMap[e.code]
  if (action) {
    input[action] = true
    e.preventDefault()
  }
})

window.addEventListener('keyup', (e) => {
  const action = keyMap[e.code]
  if (action) input[action] = false
})

e.preventDefault(), ok tuşlarının sayfayı kaydırmasını engeller. Bunu yalnızca işlediğiniz tuşlar için çağırın.

Fare

Fare koordinatları ekran uzayında gelir. Bunları tuval uzayına dönüştürmeniz gerekir:

js
const canvas = document.getElementById('game')

canvas.addEventListener('mousemove', (e) => {
  const rect = canvas.getBoundingClientRect()
  input.pointer.x = e.clientX - rect.left
  input.pointer.y = e.clientY - rect.top
})

canvas.addEventListener('mousedown', () => {
  input.pointer.down = true
  input.action = true
})

canvas.addEventListener('mouseup', () => {
  input.pointer.down = false
  input.action = false
})

Tuvaliniz ölçeklendirilmişse (duyarlı tuval eğitimine bakın), oyun koordinatlarını elde etmek için ölçek faktörüne bölmeniz gerekir.

Dokunma

Dokunma, fareye benzer şekilde çalışır ancak tarayıcının kaydırma veya yakınlaştırma yapmasını engellemek için e.preventDefault() kullanmanız gerekir:

js
canvas.addEventListener('touchstart', (e) => {
  e.preventDefault()
  const touch = e.touches[0]
  const rect = canvas.getBoundingClientRect()
  input.pointer.x = touch.clientX - rect.left
  input.pointer.y = touch.clientY - rect.top
  input.pointer.down = true
  input.action = true
}, { passive: false })

canvas.addEventListener('touchmove', (e) => {
  e.preventDefault()
  const touch = e.touches[0]
  const rect = canvas.getBoundingClientRect()
  input.pointer.x = touch.clientX - rect.left
  input.pointer.y = touch.clientY - rect.top
}, { passive: false })

canvas.addEventListener('touchend', () => {
  input.pointer.down = false
  input.action = false
})

Modern tarayıcılar performans amacıyla varsayılan olarak pasif dokunma dinleyicileri kullandığından { passive: false } gereklidir. Bu ayar olmadan preventDefault çalışmaz.

Mobil Cihazlar İçin Sanal Yön Tuşları

Mobil cihazlarda yön kontrollerine ihtiyaç duyan oyunlar için ekrana düğmeler ekleyin:

js
function createVirtualDpad(container) {
  const dpad = document.createElement('div')
  dpad.className = 'virtual-dpad'
  dpad.innerHTML = `
    <button data-dir="up">↑</button>
    <button data-dir="left">←</button>
    <button data-dir="right">→</button>
    <button data-dir="down">↓</button>
  `
  
  dpad.querySelectorAll('button').forEach(btn => {
    const dir = btn.dataset.dir
    btn.addEventListener('touchstart', (e) => {
      e.preventDefault()
      input[dir] = true
    })
    btn.addEventListener('touchend', () => input[dir] = false)
  })
  
  container.appendChild(dpad)
}

Bu düğmeleri başparmakla kullanılabilecek kadar büyük (en az 44x44 piksel) olacak şekilde biçimlendirin ve alt köşelere yerleştirin.

Oyun Kumandası

Klavye ve farenin aksine oyun kumandası durumu olay tabanlı değildir. Her karede sorgulamanız gerekir:

js
function pollGamepads() {
  const gamepads = navigator.getGamepads()
  const gp = gamepads[0]
  
  if (gp) {
    // D-pad or left stick
    input.left = gp.buttons[14]?.pressed || gp.axes[0] < -0.5
    input.right = gp.buttons[15]?.pressed || gp.axes[0] > 0.5
    input.up = gp.buttons[12]?.pressed || gp.axes[1] < -0.5
    input.down = gp.buttons[13]?.pressed || gp.axes[1] > 0.5
    
    // A button (standard mapping)
    input.action = gp.buttons[0]?.pressed
  }
}

Güncelleme fonksiyonunuzun başında pollGamepads() çağrısı yapın. Analog çubuklardaki 0,5 eşiği, küçük çubuk hareketlerinin girdi olarak algılanmaması için bir ölü bölge oluşturur.

Oyun kumandalarının ne zaman bağlandığını ve bağlantısının kesildiğini algılayabilirsiniz:

js
window.addEventListener('gamepadconnected', (e) => {
  console.log('Gamepad connected:', e.gamepad.id)
})

window.addEventListener('gamepaddisconnected', (e) => {
  console.log('Gamepad disconnected:', e.gamepad.id)
})

Oyun Döngünüzde Girdi Kullanımı

Artık güncelleme fonksiyonunuz yalnızca girdi durumunu okur:

js
function update(dt) {
  pollGamepads()
  
  if (input.left) player.x -= player.speed * dt
  if (input.right) player.x += player.speed * dt
  if (input.up) player.y -= player.speed * dt
  if (input.down) player.y += player.speed * dt
  
  if (input.action && player.canShoot) {
    player.shoot()
  }
}

Bu, girdinin klavyeden, dokunmadan veya oyun kumandasından gelmesinden bağımsız olarak çalışır. Oyun mantığının bunu bilmesi gerekmez.

Yeni Basıldı ve Basılı Tutuluyor Ayrımı

Bazen bir düğmenin o anda basılı tutulup tutulmadığını değil, yalnızca bu karede yeni basılıp basılmadığını algılamak istersiniz. Zıplama buna iyi bir örnektir: düğme basılı tutulduğu sürece sürekli zıplamak yerine, her basışta bir kez zıplanmasını istersiniz.

js
const inputPrev = { ...input }

function wasJustPressed(action) {
  return input[action] && !inputPrev[action]
}

function endFrame() {
  Object.assign(inputPrev, input)
}

Güncelleme döngünüzün sonunda endFrame() çağrısı yapın. Ardından tek seferlik eylemler için input.action yerine wasJustPressed('action') kullanın.

Odak Kaybı

Tarayıcı penceresi odağı kaybettiğinde klavye olayları tetiklenmeyi bırakır. Oyuncu bir tuşu basılı tutuyorsa tuş basılı kalmış gibi davranır:

js
window.addEventListener('blur', () => {
  input.left = input.right = input.up = input.down = input.action = false
})

Bu olmadan oyuncu, kullanıcı Alt+Tab ile başka bir pencereye geçtiğinde hareket etmeye devam eder.

En İyi Uygulamalar

Oyuncuların farklı tercihleri olduğundan hem WASD hem de ok tuşlarını destekleyin. Tuşların yeniden atanmasına izin verin ve yapılandırmayı localStorage içinde saklayın. Mobil cihazlarda dokunmatik kontroller sağlayın. Xbox, PlayStation ve genel amaçlı oyun kumandalarının her biri biraz farklı davrandığından gerçek kumandalarla test yapın. Ayrıca odak kaybını her zaman sorunsuz biçimde yönetin.

Diğer Kaynaklar

Gamepad API'ye ayrıntılı bakış, titreşim ve birden fazla oyuncu gibi gelişmiş oyun kumandası özelliklerini ele alır.

Mobil uyumlu web oyunları, dokunmatik girdiyi daha ayrıntılı biçimde ele alır.

FPS oyunları için Pointer Lock, birinci şahıs kontrolleri için fareyi nasıl yakalayacağınızı gösterir.

Canvas 2D oyun döngüsü, eksiksiz bir oyun döngüsüne entegre edilmiş girdi durumu kalıbını gösterir.

WebSocket ile çok oyunculu oyunların temelleri, ağ üzerinden bağlı oyuncular arasında girdilerin eşitlenmesini ele alır.

Harici Kaynaklar