Skip to content

Oyunlar için Web Audio API

İyi sesler bir oyunu dönüştürür. Sessiz bir oyun cansız hissettirir. Web Audio API; düşük gecikmeli ses efektleri, müzik çalma ve hatta 3B uzamsal ses özelliklerini doğrudan tarayıcıda kullanmanızı sağlar.

Deneyin (sentezlenmiş sesleri duymak için bir düğmeye tıklayın):

Otomatik Oynatma Sorunu

Her şeyden önce otomatik oynatma kısıtlamalarını anlamanız gerekir. Tarayıcılar, kullanıcı sayfanızla etkileşime geçene kadar sesi engeller. Bu iyi bir şeydir (kimse sayfa yüklenir yüklenmez ses yayınına başlayan siteler istemez), ancak sesi bir tıklama, dokunma veya tuşa basma işlemine yanıt olarak başlatmanız gerektiği anlamına gelir.

js
let audioCtx = null

function initAudio() {
  if (!audioCtx) {
    audioCtx = new AudioContext()
  }
  if (audioCtx.state === 'suspended') {
    audioCtx.resume()
  }
  return audioCtx
}

// Initialize on first interaction
document.addEventListener('click', () => initAudio(), { once: true })
document.addEventListener('keydown', () => initAudio(), { once: true })

Bundan önce ses çalmayı denerseniz işlem hiçbir uyarı vermeden başarısız olur veya AudioContext askıda kalır. Bu durum birçok geliştiriciyi hazırlıksız yakalar.

Sesleri Yükleme

Ses dosyalarının oynatılmadan önce kodlarının çözülmesi gerekir. Dosyayı alın, kodunu çözün ve yeniden kullanmak üzere arabelleği saklayın:

js
async function loadSound(url) {
  const response = await fetch(url)
  const arrayBuffer = await response.arrayBuffer()
  const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer)
  return audioBuffer
}

Kod çözme zaman alır; bu nedenle tüm seslerinizi çalmanız gerektiği anda değil, yükleme ekranı sırasında yükleyin.

Sesleri Çalma

Bir sesi çalmak için bir arabellek kaynağı oluşturun, ses seviyesini kontrol etmek üzere bunu bir kazanç düğümüne bağlayın ve başlatın:

js
function playSound(buffer, volume = 1) {
  const source = audioCtx.createBufferSource()
  source.buffer = buffer
  
  const gainNode = audioCtx.createGain()
  gainNode.gain.value = volume
  
  source.connect(gainNode)
  gainNode.connect(audioCtx.destination)
  
  source.start(0)
  return source
}

Arabellek kaynakları tek kullanımlıktır. Bir kaynak oynatmayı bitirdikten sonra onu yeniden başlatamazsınız. Her oynatmada yeni bir tane oluşturun. Bu, kaynak israfı gibi görünse de API bu şekilde çalışır ve tarayıcılar buna göre optimize edilmiştir.

Ses Yöneticisi

Gerçek bir oyun için her şeyi bir yönetici sınıfında toplayın:

js
class SoundManager {
  constructor() {
    this.ctx = null
    this.sounds = new Map()
    this.masterGain = null
  }
  
  init() {
    this.ctx = new AudioContext()
    this.masterGain = this.ctx.createGain()
    this.masterGain.connect(this.ctx.destination)
  }
  
  async load(name, url) {
    const response = await fetch(url)
    const buffer = await this.ctx.decodeAudioData(await response.arrayBuffer())
    this.sounds.set(name, buffer)
  }
  
  play(name, volume = 1) {
    const buffer = this.sounds.get(name)
    if (!buffer) return
    
    const source = this.ctx.createBufferSource()
    source.buffer = buffer
    
    const gain = this.ctx.createGain()
    gain.gain.value = volume
    
    source.connect(gain)
    gain.connect(this.masterGain)
    source.start(0)
    
    return source
  }
  
  setMasterVolume(v) {
    this.masterGain.gain.value = v
  }
}

Ana kazanç düğümü, genel bir ses seviyesi kaydırıcısı uygulamanızı sağlar. Tüm sesler bu düğüm üzerinden yönlendirilir.

Arka Plan Müziği

Müzik için döngüye alma ve durdurma ya da kademeli olarak kısma özellikleri gerekir:

js
class MusicPlayer {
  constructor(ctx, masterGain) {
    this.ctx = ctx
    this.masterGain = masterGain
    this.currentTrack = null
    this.gain = ctx.createGain()
    this.gain.connect(masterGain)
  }
  
  play(buffer, volume = 0.5) {
    this.stop()
    
    this.currentTrack = this.ctx.createBufferSource()
    this.currentTrack.buffer = buffer
    this.currentTrack.loop = true
    this.currentTrack.connect(this.gain)
    this.gain.gain.value = volume
    this.currentTrack.start(0)
  }
  
  stop() {
    if (this.currentTrack) {
      this.currentTrack.stop()
      this.currentTrack = null
    }
  }
  
  fadeOut(duration = 1) {
    this.gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + duration)
  }
}

linearRampToValueAtTime yöntemi, ani kesintiler yerine yumuşak geçişler sağlar.

Uzamsal Ses

3B oyunlarda sesleri uzayda konumlandırabilirsiniz. Sesler dinleyiciden uzaklaştıkça daha kısık hâle gelir:

js
function playSpatialSound(buffer, x, y, z) {
  const source = audioCtx.createBufferSource()
  source.buffer = buffer
  
  const panner = audioCtx.createPanner()
  panner.panningModel = 'HRTF'
  panner.distanceModel = 'inverse'
  panner.refDistance = 1
  panner.maxDistance = 100
  panner.positionX.value = x
  panner.positionY.value = y
  panner.positionZ.value = z
  
  source.connect(panner)
  panner.connect(audioCtx.destination)
  source.start(0)
}

function updateListener(x, y, z, fx, fy, fz) {
  const listener = audioCtx.listener
  listener.positionX.value = x
  listener.positionY.value = y
  listener.positionZ.value = z
  listener.forwardX.value = fx
  listener.forwardY.value = fy
  listener.forwardZ.value = fz
  listener.upX.value = 0
  listener.upY.value = 1
  listener.upZ.value = 0
}

Her karede kamera veya oyuncu konumuyla updateListener işlevini çağırın. Kaydırıcı, mesafe ve yöne göre ses seviyesini ve stereo kaydırmayı otomatik olarak ayarlar.

Hızla Tekrarlanan Sesler

Saniyede birçok kez çalınan seslerde (silah sesleri, ayak sesleri) üst üste binen örneklerin birikmesini istemezsiniz. Basit bir havuz kalıbı yardımcı olur:

js
class SoundPool {
  constructor(ctx, buffer, size = 8) {
    this.sources = []
    this.index = 0
    this.ctx = ctx
    this.buffer = buffer
    this.size = size
  }
  
  play(volume = 1) {
    const source = this.ctx.createBufferSource()
    source.buffer = this.buffer
    
    const gain = this.ctx.createGain()
    gain.gain.value = volume
    
    source.connect(gain)
    gain.connect(this.ctx.destination)
    source.start(0)
    
    this.index = (this.index + 1) % this.size
  }
}

Havuz, aynı sesin aynı anda kaç örneğinin çalınabileceğini sınırlar. Sınıra ulaştığınızda en eski örnek değiştirilir.

Ses Biçimleri

Farklı biçimler, farklı durumlarda daha iyi sonuç verir:

MP3 her yerde çalışır ve müzik için uygundur. OGG Vorbis, aynı dosya boyutunda daha iyi kalite sunar. Safari uzun süre bu biçimi desteklemeyen tek büyük tarayıcıydı ancak Safari 18.4 ile yerel Ogg Vorbis oynatma desteği eklendi; dolayısıyla güncel tüm büyük tarayıcılar artık bu biçimi destekliyor. Eski Safari sürümlerini önemsiyorsanız yine de yedek olarak MP3 sunmalısınız. AAC/M4A, Apple cihazlarında iyi çalışır. WAV sıkıştırılmamıştır ve büyüktür ancak kodu anında çözülür; bu da dosya boyutunu karşılayabildiğiniz kısa ses efektleri için uygundur. WebM/Opus en iyi kalite-boyut oranını sunar ancak yalnızca modern tarayıcılarda çalışır.

En geniş uyumluluk için yedek biçimler sunun:

js
const audioUrl = canPlayOgg() ? 'sound.ogg' : 'sound.mp3'

Kullanışlı İpuçları

Perde değişimi, tekrarlanan seslerin daha az robotik duyulmasını sağlar:

js
function playWithPitchVariation(buffer, variance = 0.1) {
  const source = audioCtx.createBufferSource()
  source.buffer = buffer
  source.playbackRate.value = 1 + (Math.random() - 0.5) * variance
  source.connect(audioCtx.destination)
  source.start(0)
}

Böylece her silah veya ayak sesi biraz farklı duyulur.

Ses bastırma, diyaloglar veya önemli sesler sırasında müziğin seviyesini düşürür:

js
function duckMusic(musicGain, duration = 0.3) {
  musicGain.gain.linearRampToValueAtTime(0.2, audioCtx.currentTime + duration)
}

function unduckMusic(musicGain, duration = 0.3) {
  musicGain.gain.linearRampToValueAtTime(1, audioCtx.currentTime + duration)
}

iOS'taki Dikkat Edilmesi Gerekenler

iOS, diğer platformlara göre daha sıkı ses kısıtlamalarına sahiptir. AudioContext bir kullanıcı hareketi sırasında oluşturulmalı ve sürdürülmelidir. Bazı iOS sürümleri ayrıca yalnızca bağlamı oluşturmanızı değil, kullanıcı hareketi sırasında bir ses (sessiz bile olsa) çalmanızı gerektirir. Ses masaüstünde çalışıyor ancak iOS'ta çalışmıyorsa nedeni muhtemelen budur.

Diğer Kaynaklar

Hızlı yüklenen bir web oyunu yayınlayın, ses dâhil varlık sıkıştırmayı ele alır.

Mobil uyumlu web oyunları, iOS'a özgü sorunlar hakkında daha fazla bilgi içerir.

Çevrimdışı oyunlar için PWA, çevrimdışı oynatma için ses dosyalarının nasıl önbelleğe alınacağını gösterir.

Oyun sesleri için Tone.js, daha üst düzey bir kütüphane kullanarak prosedürel ses üretimini ve müzik sistemlerini ele alır.

Ücretsiz Oyun Varlıkları Nerede Bulunur?, Freesound ve Poly Haven gibi ücretsiz ses efekti ve müzik kaynaklarını listeler.

Harici Kaynaklar