Web Audio API untuk Game
Audio yang bagus dapat mengubah sebuah game. Game tanpa suara terasa tidak hidup. Web Audio API menyediakan efek suara berlatensi rendah, pemutaran musik, dan bahkan audio spasial 3D, semuanya langsung di browser.
Cobalah (klik tombol untuk mendengar suara yang disintesis):
Masalah Autoplay
Sebelum melakukan hal lain, Anda perlu memahami pembatasan autoplay. Browser memblokir audio hingga pengguna berinteraksi dengan halaman Anda. Ini merupakan hal yang baik (tidak ada yang ingin situs web tiba-tiba memutar suara saat dimuat), tetapi artinya Anda harus menginisialisasi audio sebagai respons terhadap klik, ketukan, atau penekanan tombol.
let audioCtx = null
function initAudio() {
if (!audioCtx) {
audioCtx = new AudioContext()
}
if (audioCtx.state === 'suspended') {
audioCtx.resume()
}
return audioCtx
}
// Inisialisasi pada interaksi pertama
document.addEventListener('click', () => initAudio(), { once: true })
document.addEventListener('keydown', () => initAudio(), { once: true })Jika Anda mencoba memutar audio sebelum ini, prosesnya akan gagal tanpa pesan atau AudioContext tetap ditangguhkan. Hal ini sering membuat banyak developer lengah.
Memuat Suara
File audio perlu didekode sebelum diputar. Ambil file, dekode, lalu simpan buffer agar dapat digunakan kembali:
async function loadSound(url) {
const response = await fetch(url)
const arrayBuffer = await response.arrayBuffer()
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer)
return audioBuffer
}Proses decoding membutuhkan waktu, jadi muat semua suara Anda selama layar pemuatan, bukan ketika suara tersebut perlu diputar.
Memutar Suara
Untuk memutar suara, buat sumber buffer, hubungkan melalui gain node untuk mengontrol volume, lalu mulai pemutarannya:
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
}Sumber buffer hanya dapat digunakan sekali. Setelah sumber selesai diputar, Anda tidak dapat memulainya kembali. Buat sumber baru setiap kali Anda memutar suara. Ini mungkin terasa boros, tetapi begitulah cara kerja API ini, dan browser telah mengoptimalkannya.
Sound Manager
Untuk game sungguhan, bungkus semuanya dalam sebuah kelas pengelola:
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
}
}Master gain node memungkinkan Anda menerapkan slider volume global. Semua suara dirutekan melaluinya.
Musik Latar
Musik memerlukan perulangan serta kemampuan untuk dihentikan atau dipudarkan:
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)
}
}Metode linearRampToValueAtTime menghasilkan transisi volume yang mulus alih-alih terputus secara mendadak.
Audio Spasial
Untuk game 3D, Anda dapat memosisikan suara di dalam ruang. Suara menjadi lebih pelan saat menjauh dari pendengar:
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
}Panggil updateListener pada setiap frame menggunakan posisi kamera atau pemain. Panner secara otomatis menyesuaikan volume dan panning stereo berdasarkan jarak dan arah.
Suara Tembakan Cepat
Untuk suara yang diputar berkali-kali per detik (tembakan, langkah kaki), Anda tentu tidak ingin instance yang saling tumpang tindih terus menumpuk. Pola pool sederhana dapat membantu:
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
}
}Pool membatasi jumlah instance dari suara yang sama yang dapat diputar secara bersamaan. Ketika batas tercapai, instance tertua akan digantikan.
Format Audio
Format yang berbeda lebih cocok untuk situasi yang berbeda:
MP3 dapat digunakan di mana saja dan cocok untuk musik. OGG Vorbis menawarkan kualitas lebih baik dengan ukuran file yang sama. Safari sebelumnya menjadi browser utama terakhir yang belum mendukungnya, tetapi dukungan pemutaran native Ogg Vorbis telah ditambahkan di Safari 18.4, sehingga kini semua browser utama versi terbaru mendukungnya. Anda tetap sebaiknya menyediakan MP3 sebagai fallback jika ingin mendukung Safari versi lama. AAC/M4A berfungsi dengan baik di perangkat Apple. WAV tidak terkompresi dan berukuran besar, tetapi dapat didekode seketika, sehingga cocok untuk efek suara pendek jika ukuran file bukan masalah. WebM/Opus menawarkan rasio kualitas terhadap ukuran terbaik, tetapi hanya berfungsi di browser modern.
Untuk kompatibilitas maksimal, sediakan fallback:
const audioUrl = canPlayOgg() ? 'sound.ogg' : 'sound.mp3'Trik Berguna
Variasi pitch membuat suara yang berulang terasa tidak terlalu robotis:
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)
}Setiap suara tembakan atau langkah kaki akan terdengar sedikit berbeda.
Ducking menurunkan volume musik selama dialog atau saat suara penting diputar:
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)
}Hal yang Perlu Diwaspadai di iOS
iOS memiliki pembatasan audio yang lebih ketat dibandingkan platform lain. AudioContext harus dibuat dan dilanjutkan selama gestur pengguna. Beberapa versi iOS juga mengharuskan Anda memutar suara (bahkan suara hening) selama gestur tersebut, bukan sekadar membuat context. Jika audio berfungsi di desktop tetapi tidak di iOS, kemungkinan besar inilah penyebabnya.
Referensi Lainnya
Rilis game web yang dimuat dengan cepat membahas kompresi aset, termasuk audio.
Game web yang ramah perangkat seluler membahas lebih lanjut masalah khusus iOS.
PWA untuk game offline menunjukkan cara menyimpan file audio dalam cache agar dapat dimainkan secara offline.
Tone.js untuk audio game membahas pembuatan suara prosedural dan sistem musik menggunakan library tingkat tinggi.
Tempat Menemukan Aset Game Gratis mencantumkan sumber efek suara dan musik gratis seperti Freesound dan Poly Haven.
Referensi Eksternal
- MDN: Web Audio API — referensi API lengkap
- MDN: AudioContext — membuat dan mengelola audio context
- Spesifikasi Web Audio API — spesifikasi W3C