Skip to content

Çevrimdışı web oyunları için PWA

Progresif Web Uygulamaları, oyuncuların oyununuzu yüklemesine ve çevrimdışı oynamasına olanak tanır. Bu eğitim, bir web oyununa PWA desteğinin nasıl ekleneceğini gösterir.

1) Web Uygulaması Manifesti

manifest.json dosyasını oluşturun:

json
{
  "name": "Harika Oyunum",
  "short_name": "HarikaOyun",
  "description": "Çevrimdışı oynayabileceğiniz harika bir oyun",
  "start_url": "/",
  "display": "fullscreen",
  "orientation": "landscape",
  "background_color": "#1a1a2e",
  "theme_color": "#4ade80",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-maskable.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ]
}

HTML dosyanızda buna bağlantı verin:

html
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#4ade80">
<link rel="apple-touch-icon" href="/icons/icon-192.png">

2) Temel service worker

sw.js dosyasını oluşturun:

js
const CACHE_NAME = 'game-v1'

const ASSETS = [
  '/',
  '/index.html',
  '/game.js',
  '/style.css',
  '/assets/sprites.png',
  '/assets/sounds/jump.mp3',
  '/assets/sounds/music.mp3',
]

// Install: cache assets
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(ASSETS)
    })
  )
})

// Activate: clean old caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) => {
      return Promise.all(
        keys.filter(key => key !== CACHE_NAME)
            .map(key => caches.delete(key))
      )
    })
  )
})

// Fetch: serve from cache, fall back to network
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request)
    })
  )
})

3) Service worker'ı kaydetme

js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js')
      console.log('SW registered:', registration.scope)
    } catch (err) {
      console.error('SW registration failed:', err)
    }
  })
}

4) Ağ üzerinden güncellemeli önbellek öncelikli strateji

Oyunlar için daha iyidir: arka planda güncellemeler yapılırken hızlı yükleme sağlar.

js
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.open(CACHE_NAME).then(async (cache) => {
      const cached = await cache.match(event.request)
      
      // Start network fetch in background
      const fetchPromise = fetch(event.request).then((response) => {
        if (response.ok) {
          cache.put(event.request, response.clone())
        }
        return response
      }).catch(() => null)
      
      // Return cached immediately, or wait for network
      return cached || fetchPromise
    })
  )
})

5) Güncellemeler için sürümlendirilmiş önbellek

js
const CACHE_VERSION = 'v2'
const STATIC_CACHE = `static-${CACHE_VERSION}`
const DYNAMIC_CACHE = `dynamic-${CACHE_VERSION}`

const STATIC_ASSETS = [
  '/',
  '/index.html',
  '/game.js',
  // ... core assets that rarely change
]

self.addEventListener('install', (event) => {
  self.skipWaiting() // Activate immediately
  
  event.waitUntil(
    caches.open(STATIC_CACHE).then(cache => cache.addAll(STATIC_ASSETS))
  )
})

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(keys => {
      return Promise.all(
        keys.filter(key => !key.includes(CACHE_VERSION))
            .map(key => caches.delete(key))
      )
    })
  )
  
  clients.claim() // Take control immediately
})

6) Oyun güncellemelerini yönetme

Bir güncelleme kullanıma sunulduğunda oyuncuları bilgilendirin:

js
// In main app
let refreshing = false

navigator.serviceWorker.addEventListener('controllerchange', () => {
  if (!refreshing) {
    refreshing = true
    showUpdateNotification()
  }
})

function showUpdateNotification() {
  const banner = document.createElement('div')
  banner.innerHTML = `
    <p>Oyun güncellendi! En son sürümü edinmek için yenileyin.</p>
    <button onclick="location.reload()">Yenile</button>
  `
  banner.className = 'update-banner'
  document.body.appendChild(banner)
}

7) Çevrimdışı durumu algılama

js
function updateOnlineStatus() {
  if (navigator.onLine) {
    hideOfflineBanner()
    syncGameData()
  } else {
    showOfflineBanner()
  }
}

window.addEventListener('online', updateOnlineStatus)
window.addEventListener('offline', updateOnlineStatus)

function showOfflineBanner() {
  document.getElementById('offline-banner').style.display = 'block'
}

function hideOfflineBanner() {
  document.getElementById('offline-banner').style.display = 'none'
}

8) Yükleme istemi

js
let deferredPrompt = null

window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault()
  deferredPrompt = e
  showInstallButton()
})

function showInstallButton() {
  const btn = document.getElementById('install-btn')
  btn.style.display = 'block'
  btn.addEventListener('click', installApp)
}

async function installApp() {
  if (!deferredPrompt) return
  
  deferredPrompt.prompt()
  const { outcome } = await deferredPrompt.userChoice
  
  console.log('Install prompt outcome:', outcome)
  deferredPrompt = null
  document.getElementById('install-btn').style.display = 'none'
}

window.addEventListener('appinstalled', () => {
  console.log('App installed!')
  deferredPrompt = null
})

iOS ve iPadOS'te beforeinstallprompt olayı hiçbir zaman tetiklenmez; bu nedenle yukarıdaki yükleme düğmesi yalnızca Chromium tarayıcılarında görünür. iPhone ve iPad kullanıcıları, Paylaş'a ve ardından "Ana Ekrana Ekle" seçeneğine dokunarak yükleme yapar. 2026'da değişen ve bilinmesi gereken bir nokta var: Safari 26'dan (iOS 26 / iPadOS 26) itibaren Ana Ekran'a eklenen her site, varsayılan olarak etkin olan "Web Uygulaması Olarak Aç" seçeneğiyle birlikte varsayılan olarak bir web uygulaması şeklinde açılır. iOS'te artık yüklenebilirlik şartı yoktur; ancak manifest ve service worker sunmak yine de çok daha iyi bir çevrimdışı deneyim sağlar.

9) Liderlik tabloları için arka plan senkronizasyonu

Destek konusunda önemli bir not: Background Sync API yalnızca Chromium tarayıcılarında (Chrome, Edge, Opera, Samsung Internet) uygulanmıştır. Firefox ve Safari (iOS dâhil) bunu desteklemez; bu nedenle her zaman özellik algılama kullanın ve skorları uygulamanın bir sonraki normal yüklenişinde gönderen bir yedek yöntem bulundurun.

js
// In service worker
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-scores') {
    event.waitUntil(syncScores())
  }
})

async function syncScores() {
  const db = await openDB('game', 1)
  const pendingScores = await db.getAll('pending-scores')
  
  for (const score of pendingScores) {
    try {
      await fetch('/api/scores', {
        method: 'POST',
        body: JSON.stringify(score),
        headers: { 'Content-Type': 'application/json' }
      })
      await db.delete('pending-scores', score.id)
    } catch (err) {
      // Will retry on next sync
      break
    }
  }
}

// In main app
async function submitScore(score) {
  try {
    await fetch('/api/scores', { method: 'POST', body: JSON.stringify(score) })
  } catch {
    // Save for later sync
    const db = await openDB('game', 1)
    await db.add('pending-scores', { ...score, id: Date.now() })
    
    if ('serviceWorker' in navigator && 'sync' in window.registration) {
      await navigator.serviceWorker.ready
      await registration.sync.register('sync-scores')
    }
  }
}

10) PWA'yı test etme

Chrome DevTools:

  • Application > Service Workers
  • Application > Manifest
  • Application > Cache Storage
  • Network > Offline onay kutusu

Lighthouse denetimi:

  • PWA denetimini çalıştırın
  • Yüklenebilirliği kontrol edin
  • Çevrimdışı çalışma özelliğini kontrol edin

Gerçek cihazda test:

  • Telefonun ana ekranına yükleyin
  • Uçak modunu etkinleştirin
  • Tüm özellikleri çevrimdışı test edin

İlgili içerikler

Harici Kaynaklar