Skip to content

Mobil uyumlu web oyunları

Mobil cihazlar en büyük oyun platformudur. Birkaç düzenlemeyle web oyununuzun telefonlarda ve tabletlerde harika çalışmasını sağlayabilirsiniz.

1) Görüntü alanı ayarları

Yakınlaştırmayı önleyin ve doğru ölçeklendirmeyi sağlayın:

html
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

2) Tam ekran canvas

css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #000;
  touch-action: none; /* Prevent browser gestures */
}

#game {
  display: block;
  width: 100%;
  height: 100%;
}

Arayüzü height: 100% yerine görüntü alanı yüksekliğine göre boyutlandırıyorsanız vh yerine dinamik görüntü alanı birimleri olan dvh, svh ve lvh birimlerini tercih edin. Bu birimler, mobil tarayıcının daralıp genişleyen araç çubuğunu hesaba katar; 100vh ise bunu göz ardı eder (iOS'te 100vh, araç çubuğunun gizli olduğu yüksekliği temsil ettiğinden düzeninizin alt kısmı kırpılır). Bu birimler Baseline Widely Available durumuna ulaşmıştır ve Chrome/Edge 108+, Firefox 101+ ile Safari/iOS Safari 15.4+ tarafından desteklenmektedir; bu da kullanıcıların yaklaşık %92'sine karşılık gelir.

3) Dokunma olaylarını işleme

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

// Prevent default touch behaviors
canvas.addEventListener('touchstart', (e) => e.preventDefault(), { passive: false })
canvas.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false })

// Track touches
const touches = new Map()

canvas.addEventListener('touchstart', (e) => {
  for (const touch of e.changedTouches) {
    touches.set(touch.identifier, {
      x: touch.clientX,
      y: touch.clientY,
      startX: touch.clientX,
      startY: touch.clientY,
    })
  }
})

canvas.addEventListener('touchmove', (e) => {
  for (const touch of e.changedTouches) {
    const t = touches.get(touch.identifier)
    if (t) {
      t.x = touch.clientX
      t.y = touch.clientY
    }
  }
})

canvas.addEventListener('touchend', (e) => {
  for (const touch of e.changedTouches) {
    touches.delete(touch.identifier)
  }
})

Oyununuzun fare veya ekran kalemiyle de çalışması gerekiyorsa dokunma olayları yerine Pointer Events (pointerdown/pointermove/pointerup) kullanmayı değerlendirin. MDN artık bunları cihazdan bağımsız tek giriş modeli olarak öneriyor: tek bir kod yolu dokunma, fare ve kalem girişlerini işlerken çoklu dokunma da pointerId üzerinden çalışmaya devam eder (yukarıda kullanılan touch.identifier karşılığıdır). Yalnızca dokunmatik giriş kullanan oyunlar için dokunma olayları hâlâ tamamen uygundur.

4) Sanal joystick

js
class VirtualJoystick {
  constructor(x, y, radius) {
    this.baseX = x
    this.baseY = y
    this.radius = radius
    this.knobRadius = radius * 0.4
    this.active = false
    this.touchId = null
    this.dx = 0
    this.dy = 0
  }
  
  handleTouchStart(touch) {
    const dist = Math.hypot(touch.clientX - this.baseX, touch.clientY - this.baseY)
    if (dist < this.radius) {
      this.active = true
      this.touchId = touch.identifier
      this.updatePosition(touch.clientX, touch.clientY)
    }
  }
  
  handleTouchMove(touch) {
    if (touch.identifier === this.touchId) {
      this.updatePosition(touch.clientX, touch.clientY)
    }
  }
  
  handleTouchEnd(touch) {
    if (touch.identifier === this.touchId) {
      this.active = false
      this.touchId = null
      this.dx = 0
      this.dy = 0
    }
  }
  
  updatePosition(x, y) {
    let dx = x - this.baseX
    let dy = y - this.baseY
    const dist = Math.hypot(dx, dy)
    
    if (dist > this.radius) {
      dx = (dx / dist) * this.radius
      dy = (dy / dist) * this.radius
    }
    
    this.dx = dx / this.radius
    this.dy = dy / this.radius
  }
  
  draw(ctx) {
    // Base circle
    ctx.beginPath()
    ctx.arc(this.baseX, this.baseY, this.radius, 0, Math.PI * 2)
    ctx.fillStyle = 'rgba(255, 255, 255, 0.2)'
    ctx.fill()
    
    // Knob
    const knobX = this.baseX + this.dx * this.radius
    const knobY = this.baseY + this.dy * this.radius
    ctx.beginPath()
    ctx.arc(knobX, knobY, this.knobRadius, 0, Math.PI * 2)
    ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'
    ctx.fill()
  }
  
  getInput() {
    return { x: this.dx, y: this.dy }
  }
}

5) Dokunmatik düğmeler

js
class TouchButton {
  constructor(x, y, radius, label) {
    this.x = x
    this.y = y
    this.radius = radius
    this.label = label
    this.pressed = false
    this.touchId = null
  }
  
  contains(px, py) {
    return Math.hypot(px - this.x, py - this.y) < this.radius
  }
  
  handleTouchStart(touch) {
    if (this.contains(touch.clientX, touch.clientY)) {
      this.pressed = true
      this.touchId = touch.identifier
    }
  }
  
  handleTouchEnd(touch) {
    if (touch.identifier === this.touchId) {
      this.pressed = false
      this.touchId = null
    }
  }
  
  draw(ctx) {
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2)
    ctx.fillStyle = this.pressed ? 'rgba(255, 255, 255, 0.5)' : 'rgba(255, 255, 255, 0.2)'
    ctx.fill()
    
    ctx.font = 'bold 24px sans-serif'
    ctx.fillStyle = '#fff'
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'
    ctx.fillText(this.label, this.x, this.y)
  }
}

6) Ekran yönünü işleme

js
function checkOrientation() {
  const isLandscape = window.innerWidth > window.innerHeight
  
  if (!isLandscape) {
    showRotatePrompt()
  } else {
    hideRotatePrompt()
    resizeCanvas()
  }
}

window.addEventListener('resize', checkOrientation)
window.addEventListener('orientationchange', checkOrientation)

function showRotatePrompt() {
  document.getElementById('rotate-prompt').style.display = 'flex'
}

function hideRotatePrompt() {
  document.getElementById('rotate-prompt').style.display = 'none'
}

7) Performans optimizasyonu

js
// Reduce resolution on low-end devices
function getDeviceScale() {
  const dpr = window.devicePixelRatio || 1
  const isLowEnd = navigator.hardwareConcurrency <= 2
  return isLowEnd ? Math.min(dpr, 1.5) : Math.min(dpr, 2)
}

// Throttle expensive operations
let lastHeavyUpdate = 0
function maybeDoHeavyWork(timestamp) {
  if (timestamp - lastHeavyUpdate > 100) { // 10 fps for heavy stuff
    doExpensiveCalculations()
    lastHeavyUpdate = timestamp
  }
}

// Use lower quality assets
function getAssetQuality() {
  const memory = navigator.deviceMemory || 4
  if (memory <= 2) return 'low'
  if (memory <= 4) return 'medium'
  return 'high'
}

8) Pil kullanımına ilişkin hususlar

js
// Check battery and reduce effects
async function checkBattery() {
  if ('getBattery' in navigator) {
    const battery = await navigator.getBattery()
    
    if (battery.level < 0.2 && !battery.charging) {
      enableBatterySaver()
    }
    
    battery.addEventListener('levelchange', () => {
      if (battery.level < 0.2 && !battery.charging) {
        enableBatterySaver()
      }
    })
  }
}

function enableBatterySaver() {
  game.particleCount = Math.floor(game.particleCount * 0.5)
  game.targetFps = 30
  console.log('Battery saver enabled')
}

9) Fullscreen API

js
async function requestFullscreen() {
  const elem = document.documentElement
  
  try {
    if (elem.requestFullscreen) {
      await elem.requestFullscreen()
    } else if (elem.webkitRequestFullscreen) {
      await elem.webkitRequestFullscreen()
    }
    
    // Lock orientation
    if (screen.orientation?.lock) {
      await screen.orientation.lock('landscape')
    }
  } catch (err) {
    console.warn('Fullscreen failed:', err)
  }
}

// Call on user interaction
document.getElementById('play-btn').addEventListener('click', () => {
  requestFullscreen()
  startGame()
})

Önemli bir sınırlama vardır: Fullscreen API, iPhone'daki genel öğeler için çalışmaz. iOS Safari tam ekran özelliğini yalnızca yerel video öğeleri için standart dışı webkitEnterFullscreen aracılığıyla sunar; bu nedenle documentElement.requestFullscreen() iPhone'da reddedilir. Yukarıdaki try/catch bunu sorunsuz biçimde işler. Bu nedenle oyununuzu iPhone'da gerçek tam ekran olmadan oynanabilecek şekilde tasarlayın ve tam ekranı, desteklendiği iPad, Android ve masaüstü cihazlarda ek bir iyileştirme olarak değerlendirin. screen.orientation.lock() çağrısı da iPhone'da aynı sınırlamaya sahiptir, dolayısıyla buna bağımlı olmayın.

10) Test kontrol listesi

Yayınlamadan önce:

  • Dokunma tepkiselliği — Yalnızca emülatörlerde değil, gerçek cihazlarda test edin
  • Çeşitli ekran boyutları — Telefon, tablet, iPad Pro
  • Her iki ekran yönü — Ya da tek bir yöne kilitleyin
  • Düşük donanımlı cihazlar — 2-3 yıllık telefonlarda test edin
  • Yavaş ağlar — 3G simülasyonunda test edin
  • iOS Safari — Ses ve tam ekranla ilgili kendine özgü davranışları vardır
  • Android Chrome — En yaygın tarayıcıdır
  • Kesintiler — Telefon aramaları, bildirimler
  • Bellek baskısı — Arka plan sekmesinden döndükten sonra toparlanıyor mu?
js
// Debug touch visualization
function drawTouchDebug(ctx) {
  for (const [id, touch] of touches) {
    ctx.beginPath()
    ctx.arc(touch.x, touch.y, 30, 0, Math.PI * 2)
    ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'
    ctx.fill()
    ctx.fillStyle = '#fff'
    ctx.fillText(id.toString(), touch.x, touch.y)
  }
}

İlgili içerikler

Harici kaynaklar