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.speed7) 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
- Oyun girişi yönetimi
- Mobil uyumlu web oyunları
- Canvas 2D oyun döngüsü
- FPS oyunları için Pointer Lock — gamepad'i fareyle bakış kontrolüyle birleştirme
- Eşli Oyun Tasarımı — yerel eşli oyunda birden fazla kontrolcü girişine göre tasarım yapma
Harici Kaynaklar
- MDN: Gamepad API — eksiksiz API referansı
- MDN: Gamepad API'yi Kullanma — adım adım rehber
- Gamepad Test Aracı — kontrolcü eşlemelerinizi tarayıcıda test edin