Skip to content

Web oyunları için Gamepad API

Gamepad API, web oyunlarına konsol kalitesinde kontrolcü desteği getirir. Xbox, PlayStation, Switch Pro ve genel amaçlı kontrolcüleri destekleyin.

1) Gamepad'leri algılama

js
window.addEventListener('gamepadconnected', (e) => {
  console.log('Gamepad bağlandı:', e.gamepad.id)
  console.log('Dizin:', e.gamepad.index)
  console.log('Düğmeler:', e.gamepad.buttons.length)
  console.log('Eksenler:', e.gamepad.axes.length)
})

window.addEventListener('gamepaddisconnected', (e) => {
  console.log('Gamepad bağlantısı kesildi:', e.gamepad.id)
})

2) Gamepad durumunu yoklama

Gamepad durumu yoklanmalıdır (olay tabanlı değildir):

js
function pollGamepads() {
  const gamepads = navigator.getGamepads()
  
  for (const gamepad of gamepads) {
    if (gamepad) {
      processGamepad(gamepad)
    }
  }
}

// Oyun döngünüzde çağırın
function gameLoop() {
  pollGamepads()
  update()
  render()
  requestAnimationFrame(gameLoop)
}

3) Standart düğme eşleme

"Standart" eşleme (çoğu Xbox/PlayStation kontrolcüsü):

js
const BUTTONS = {
  A: 0,           // Çarpı (PS) / A (Xbox)
  B: 1,           // Daire (PS) / B (Xbox)
  X: 2,           // Kare (PS) / X (Xbox)
  Y: 3,           // Üçgen (PS) / Y (Xbox)
  LB: 4,          // L1 (PS) / LB (Xbox)
  RB: 5,          // R1 (PS) / RB (Xbox)
  LT: 6,          // L2 (PS) / LT (Xbox)
  RT: 7,          // R2 (PS) / RT (Xbox)
  SELECT: 8,      // Share (PS) / Back (Xbox)
  START: 9,       // Options (PS) / Start (Xbox)
  L3: 10,         // Sol çubuğa basma
  R3: 11,         // Sağ çubuğa basma
  DPAD_UP: 12,
  DPAD_DOWN: 13,
  DPAD_LEFT: 14,
  DPAD_RIGHT: 15,
  HOME: 16,       // PS düğmesi / Xbox düğmesi
}

const AXES = {
  LEFT_X: 0,
  LEFT_Y: 1,
  RIGHT_X: 2,
  RIGHT_Y: 3,
}

4) Düğmeleri ve çubukları okuma

js
function processGamepad(gamepad) {
  // Düğmeler (dijital veya analog)
  const jump = gamepad.buttons[BUTTONS.A].pressed
  const shoot = gamepad.buttons[BUTTONS.X].pressed
  
  // Tetikler (analog 0-1)
  const leftTrigger = gamepad.buttons[BUTTONS.LT].value
  const rightTrigger = gamepad.buttons[BUTTONS.RT].value
  
  // Çubuklar (analog -1 ile 1 arası)
  const leftX = gamepad.axes[AXES.LEFT_X]
  const leftY = gamepad.axes[AXES.LEFT_Y]
  const rightX = gamepad.axes[AXES.RIGHT_X]
  const rightY = gamepad.axes[AXES.RIGHT_Y]
  
  return { jump, shoot, leftTrigger, rightTrigger, leftX, leftY, rightX, rightY }
}

5) Ölü bölge yönetimi

Çubuklar boşta nadiren tam olarak 0 değerinde kalır:

js
function applyDeadzone(value, deadzone = 0.15) {
  if (Math.abs(value) < deadzone) return 0
  
  // Kalan aralığı 0-1 ölçeğine yeniden ayarla
  const sign = Math.sign(value)
  const adjusted = (Math.abs(value) - deadzone) / (1 - deadzone)
  return sign * adjusted
}

function getStickInput(gamepad) {
  return {
    leftX: applyDeadzone(gamepad.axes[AXES.LEFT_X]),
    leftY: applyDeadzone(gamepad.axes[AXES.LEFT_Y]),
    rightX: applyDeadzone(gamepad.axes[AXES.RIGHT_X]),
    rightY: applyDeadzone(gamepad.axes[AXES.RIGHT_Y]),
  }
}

6) Radyal ölü bölge (2D hareket için daha iyi)

js
function applyRadialDeadzone(x, y, deadzone = 0.15) {
  const magnitude = Math.sqrt(x * x + y * y)
  
  if (magnitude < deadzone) {
    return { x: 0, y: 0 }
  }
  
  const normalized = {
    x: x / magnitude,
    y: y / magnitude,
  }
  
  const adjusted = (magnitude - deadzone) / (1 - deadzone)
  
  return {
    x: normalized.x * adjusted,
    y: normalized.y * adjusted,
  }
}

// Kullanım
const stick = applyRadialDeadzone(
  gamepad.axes[AXES.LEFT_X],
  gamepad.axes[AXES.LEFT_Y]
)
player.vx = stick.x * player.speed
player.vy = stick.y * player.speed

7) Eksiksiz gamepad yöneticisi

js
class GamepadManager {
  constructor() {
    this.gamepads = new Map()
    this.prevState = new Map()
    
    window.addEventListener('gamepadconnected', (e) => {
      this.gamepads.set(e.gamepad.index, e.gamepad)
      console.log('Bağlandı:', e.gamepad.id)
    })
    
    window.addEventListener('gamepaddisconnected', (e) => {
      this.gamepads.delete(e.gamepad.index)
      this.prevState.delete(e.gamepad.index)
    })
  }
  
  poll() {
    const gamepads = navigator.getGamepads()
    for (const gp of gamepads) {
      if (gp) this.gamepads.set(gp.index, gp)
    }
  }
  
  getState(index = 0) {
    const gp = this.gamepads.get(index)
    if (!gp) return null
    
    const stick = applyRadialDeadzone(gp.axes[0], gp.axes[1])
    const rightStick = applyRadialDeadzone(gp.axes[2], gp.axes[3])
    
    return {
      connected: true,
      leftStick: stick,
      rightStick: rightStick,
      
      // Ön yüz düğmeleri
      a: gp.buttons[0].pressed,
      b: gp.buttons[1].pressed,
      x: gp.buttons[2].pressed,
      y: gp.buttons[3].pressed,
      
      // Omuz düğmeleri
      lb: gp.buttons[4].pressed,
      rb: gp.buttons[5].pressed,
      
      // Tetikler (analog)
      lt: gp.buttons[6].value,
      rt: gp.buttons[7].value,
      
      // Yön düğmeleri
      dpadUp: gp.buttons[12].pressed,
      dpadDown: gp.buttons[13].pressed,
      dpadLeft: gp.buttons[14].pressed,
      dpadRight: gp.buttons[15].pressed,
      
      // Meta
      select: gp.buttons[8].pressed,
      start: gp.buttons[9].pressed,
    }
  }
  
  isPressed(index, button) {
    const gp = this.gamepads.get(index)
    return gp?.buttons[button]?.pressed || false
  }
  
  wasJustPressed(index, button) {
    const current = this.isPressed(index, button)
    const prev = this.prevState.get(index)?.[button] || false
    return current && !prev
  }
  
  endFrame() {
    for (const [index, gp] of this.gamepads) {
      const state = {}
      for (let i = 0; i < gp.buttons.length; i++) {
        state[i] = gp.buttons[i].pressed
      }
      this.prevState.set(index, state)
    }
  }
}

// Kullanım
const gamepadManager = new GamepadManager()

function update() {
  gamepadManager.poll()
  
  const state = gamepadManager.getState(0)
  if (state) {
    player.vx = state.leftStick.x * player.speed
    player.vy = state.leftStick.y * player.speed
    
    if (gamepadManager.wasJustPressed(0, BUTTONS.A)) {
      player.jump()
    }
  }
  
  gamepadManager.endFrame()
}

8) Titreşim / dokunsal geri bildirim

js
function vibrate(gamepad, duration = 200, weakMagnitude = 0.5, strongMagnitude = 0.5) {
  if (gamepad.vibrationActuator) {
    gamepad.vibrationActuator.playEffect('dual-rumble', {
      startDelay: 0,
      duration,
      weakMagnitude,   // Yüksek frekanslı motor
      strongMagnitude, // Düşük frekanslı motor
    })
  }
}

// Kullanım
function onPlayerHit() {
  const gp = navigator.getGamepads()[0]
  if (gp) vibrate(gp, 150, 0.3, 0.6)
}

function onExplosion() {
  const gp = navigator.getGamepads()[0]
  if (gp) vibrate(gp, 300, 0.8, 1.0)
}

9) Birden fazla oyuncu

js
class LocalMultiplayer {
  constructor(maxPlayers = 4) {
    this.maxPlayers = maxPlayers
    this.players = []
  }
  
  getActivePlayers() {
    const gamepads = navigator.getGamepads()
    const active = []
    
    for (let i = 0; i < this.maxPlayers; i++) {
      if (gamepads[i]) {
        active.push({
          index: i,
          gamepad: gamepads[i],
          state: this.getState(gamepads[i])
        })
      }
    }
    
    return active
  }
  
  getState(gp) {
    // ... öncekiyle aynı
  }
}

// Oyun döngüsünde
const multiplayer = new LocalMultiplayer(4)

function update() {
  const players = multiplayer.getActivePlayers()
  
  for (const { index, state } of players) {
    updatePlayer(index, state)
  }
}

10) Yedek ve birleşik giriş

Hem klavyeyi hem de gamepad'i destekleyin:

js
class InputManager {
  constructor() {
    this.keyboard = new KeyboardInput()
    this.gamepad = new GamepadManager()
  }
  
  poll() {
    this.gamepad.poll()
  }
  
  getInput(playerIndex = 0) {
    const gpState = this.gamepad.getState(playerIndex)
    const kbState = this.keyboard.getState()
    
    // Girişleri birleştir (analog girişlerde gamepad önceliklidir)
    return {
      moveX: gpState?.leftStick.x || (kbState.left ? -1 : kbState.right ? 1 : 0),
      moveY: gpState?.leftStick.y || (kbState.up ? -1 : kbState.down ? 1 : 0),
      jump: gpState?.a || kbState.space,
      shoot: gpState?.x || kbState.z,
      pause: gpState?.start || kbState.escape,
    }
  }
  
  endFrame() {
    this.gamepad.endFrame()
    this.keyboard.endFrame()
  }
}

İlgili içerikler

Harici Kaynaklar