Oyun Sesleri için Tone.js
Tone.js, Web Audio API üzerine kuruludur ve sentezlenmiş sesleri pratik hâle getirir. Ses dosyaları yüklemek yerine sesleri programlı olarak üretirsiniz. Bu da sıfır yükleme süresi, sonsuz çeşitlilik ve oynanışa gerçek zamanlı tepki verebilen sesler anlamına gelir.
Deneyin (sentezlenmiş oyun seslerini duymak için düğmelere tıklayın):
Neden Ses Dosyaları Yerine Tone.js?
Ses dosyaları müzik ve karmaşık sesler için gayet iyi çalışır. Ancak sentezleme, oyun ses efektleri için gerçek avantajlar sunar. Seslerin tekrara düşmemesi için her çalınışta perdeyi ve zamanlamayı değiştirebilirsiniz. Oyun durumuna göre anında sesler üretebilirsiniz. Üstelik ağ isteklerini tamamen ortadan kaldırırsınız.
Dezavantajı, sentezlenmiş seslerin bir dosyayı çalmaktan daha fazla işlemci gücü kullanmasıdır. Basit oyunlarda bu sorun değildir. Karmaşık oyunlarda ise iki yaklaşımı birlikte kullanabilirsiniz.
Kurulum
Tone.js'i bir CDN veya npm üzerinden yükleyin:
<script src="https://unpkg.com/tone@15/build/Tone.js"></script>Ya da npm ile:
npm install toneimport * as Tone from 'tone'Otomatik Oynatma Sorunu
Tarayıcılar, kullanıcı etkileşimi gerçekleşene kadar sesi engeller. Tone.js bunu yönetir, ancak bir kullanıcı hareketi sırasında Tone.start() çağrısı yapmanız gerekir:
document.addEventListener('click', async () => {
await Tone.start()
console.log('Audio ready')
}, { once: true })Bunu yapmazsanız AudioContext askıda kalır ve hiçbir ses çalmaz.
İlk Sentezleyiciniz
Sentezleyici ses üretir. En basit seçenek Tone.Synth'tir:
const synth = new Tone.Synth().toDestination()
// Play a note
synth.triggerAttackRelease('C4', '8n')triggerAttackRelease, bir notayı belirli bir süre boyunca çalar. 'C4' orta Do notasıdır. '8n', mevcut tempoda bir sekizlik notadır.
Sentezleyicide bir osilatör (ses kaynağı) ve bir zarf (sesin nasıl girip çıktığı) bulunur. Her ikisini de özelleştirebilirsiniz:
const synth = new Tone.Synth({
oscillator: { type: 'triangle' },
envelope: {
attack: 0.02,
decay: 0.2,
sustain: 0.3,
release: 0.4,
},
}).toDestination()Osilatör türleri arasında sine (saf, yumuşak), triangle (yumuşak), sawtooth (vızıltılı) ve square (boş tınılı, retro) bulunur.
Ses Efekti Sistemi Oluşturma
Bir oyunda, farklı ses türleri için birden fazla sentezleyici kullanmak istersiniz. Aşağıdaki yapı bu iş için oldukça uygundur:
let isInitialized = false
let softSynth = null
let subSynth = null
let noiseSynth = null
let sfxGain = null
function initSFX() {
if (isInitialized) return
// Master volume control
sfxGain = new Tone.Gain(0.7).toDestination()
// Soft synth for melodic sounds
softSynth = new Tone.PolySynth(Tone.Synth, {
maxPolyphony: 6,
oscillator: { type: 'sine' },
envelope: {
attack: 0.02,
decay: 0.2,
sustain: 0.3,
release: 0.4,
},
volume: -8,
}).connect(sfxGain)
// Sub bass for deep impacts
subSynth = new Tone.Synth({
oscillator: { type: 'sine' },
envelope: {
attack: 0.02,
decay: 0.3,
sustain: 0.1,
release: 0.3,
},
volume: -8,
}).connect(sfxGain)
// Noise for texture
noiseSynth = new Tone.NoiseSynth({
noise: { type: 'pink' },
envelope: {
attack: 0.02,
decay: 0.15,
sustain: 0,
release: 0.15,
},
volume: -20,
}).connect(sfxGain)
isInitialized = true
}PolySynth, aynı anda birden fazla nota çalmanızı sağlar. Normal Synth ise monofoniktir.
Oyun Sesi Örnekleri
Yaygın oyun seslerinden bazılarını şu şekilde oluşturabilirsiniz:
Jeton toplama - yükselen notalar ödüllendirici hissettirir:
function coinCollect() {
const now = Tone.now()
softSynth.triggerAttackRelease('C4', '8n', now, 0.3)
softSynth.triggerAttackRelease('E4', '8n', now + 0.1, 0.35)
softSynth.triggerAttackRelease('G4', '4n', now + 0.2, 0.4)
}Zıplama - hızlı bir yukarı perde kayması:
function jump() {
const synth = new Tone.Synth({
oscillator: { type: 'square' },
envelope: { attack: 0.01, decay: 0.1, sustain: 0, release: 0.1 },
}).toDestination()
const now = Tone.now()
synth.frequency.setValueAtTime(150, now)
synth.frequency.exponentialRampToValueAtTime(400, now + 0.1)
synth.triggerAttackRelease('C4', '8n', now, 0.2)
}Darbe/hasar - gürültü eşliğinde alçalan ses:
function hit() {
const now = Tone.now()
subSynth.triggerAttackRelease('E2', '4n', now, 0.5)
noiseSynth.triggerAttackRelease('4n', now, 0.25)
}Ölüm - hüzünlü, alçalan notalar:
function death() {
const now = Tone.now()
softSynth.triggerAttackRelease('E4', '4n', now, 0.4)
softSynth.triggerAttackRelease('C4', '4n', now + 0.2, 0.35)
softSynth.triggerAttackRelease('A3', '4n', now + 0.4, 0.3)
}Efekt Ekleme
Efektler sesleri daha zengin hâle getirir. Bunları sentezleyici ile hedef arasına zincirleyin:
const filter = new Tone.Filter({
frequency: 3000,
type: 'lowpass',
rolloff: -12,
})
const reverb = new Tone.Reverb({
decay: 1.2,
wet: 0.25,
})
const gain = new Tone.Gain(0.7)
// Chain: synth -> filter -> reverb -> gain -> destination
filter.connect(reverb)
reverb.connect(gain)
gain.toDestination()
const synth = new Tone.Synth().connect(filter)Alçak geçiren filtre, rahatsız edici yüksek frekansları keser. Yankı, sese mekân hissi katar. Kazanç ise ses seviyesini kontrol eder.
Hız Sınırlama
Oyununuz kısa sürede çok sayıda sesi tetiklerse bu sesler üst üste binerek karmaşa yaratabilir. Hız sınırlaması ekleyin:
const MIN_INTERVAL_MS = 35
let lastNoteTime = 0
function canPlayNote() {
const now = performance.now()
if (now - lastNoteTime < MIN_INTERVAL_MS) {
return false
}
lastNoteTime = now
return true
}
function playSound() {
if (!canPlayNote()) return
synth.triggerAttackRelease('C4', '8n')
}Dinamik Arka Plan Müziği
Oynanışa tepki veren müzik için vuruş tabanlı bir sistem kullanın:
const BPM = 140
const BEAT_MS = (60 / BPM) * 1000
let bassSynth = null
let kickDrum = null
let beatInterval = null
let beatCount = 0
let energy = 0.3
const PROGRESSION = [
{ root: 'E2', chord: ['E3', 'G3', 'B3'] },
{ root: 'C2', chord: ['C3', 'E3', 'G3'] },
{ root: 'D2', chord: ['D3', 'F#3', 'A3'] },
{ root: 'A1', chord: ['A2', 'C3', 'E3'] },
]
function playBeat() {
const beatInMeasure = beatCount % 8
const chordIndex = Math.floor(beatCount / 8) % PROGRESSION.length
const chord = PROGRESSION[chordIndex]
const now = Tone.now()
// Bass on beat 1
if (beatInMeasure === 0) {
bassSynth.triggerAttackRelease(chord.root, '4n', now, 0.6)
}
// Kick drum - only when energy is high
if (beatInMeasure === 0 && energy > 0.3) {
kickDrum.triggerAttackRelease('C1', '8n', now, 0.4)
}
beatCount++
}
function startMusic() {
beatInterval = setInterval(playBeat, BEAT_MS)
}
function stopMusic() {
clearInterval(beatInterval)
}
// Game events affect energy
function onEnemyKill() {
energy = Math.min(1, energy + 0.1)
}Müziğin yoğunluğu, oyunda olup bitenlere göre değişir.
MembraneSynth ve NoiseSynth ile Davullar
Vurmalı çalgılar için özel sentezleyiciler kullanın:
// Kick drum
const kick = new Tone.MembraneSynth({
pitchDecay: 0.05,
octaves: 4,
envelope: {
attack: 0.001,
decay: 0.2,
sustain: 0,
release: 0.1,
},
volume: -14,
}).toDestination()
kick.triggerAttackRelease('C1', '8n')
// Snare
const snare = new Tone.NoiseSynth({
noise: { type: 'white' },
envelope: {
attack: 0.002,
decay: 0.15,
sustain: 0,
release: 0.1,
},
volume: -12,
}).toDestination()
snare.triggerAttackRelease('8n')
// Hi-hat
const hihat = new Tone.NoiseSynth({
noise: { type: 'white' },
envelope: {
attack: 0.001,
decay: 0.04,
sustain: 0,
release: 0.03,
},
volume: -22,
}).toDestination()
hihat.triggerAttackRelease('32n')Temizleme
Kaynakları serbest bırakmak için işiniz bittiğinde sentezleyicileri temizleyin:
function cleanup() {
if (softSynth) softSynth.dispose()
if (subSynth) subSynth.dispose()
if (noiseSynth) noiseSynth.dispose()
if (sfxGain) sfxGain.dispose()
}Yaygın Hatalar
Tone.start() çağrısını beklememek - Kullanıcı etkileşimiyle bağlam başlatılana kadar ses çalmaz.
Her ses için yeni sentezleyici oluşturmak - Sentezleyicileri başlangıçta bir kez oluşturup yeniden kullanın. Her seferinde yenilerini oluşturmak bellek sızıntısına yol açar.
Çok fazla sesin üst üste binmesi - Ses karmaşasını önlemek için hız sınırlaması ve yinelenenleri kaldırma yöntemlerini kullanın.
Sert frekanslar - Oyuncuları rahatsız etmeyecek sesler için alçak geçiren filtreler ve daha yumuşak osilatör türleri (sinüs, üçgen) kullanın.
Daha Fazla Kaynak
Oyunlar için Web Audio API, temel API'yi ve ses dosyalarının nasıl yükleneceğini ele alır.
Tone.js belgeleri eksiksiz API referansını içerir.
Ücretsiz Oyun Varlıkları Nerede Bulunur?, Freesound gibi ses ve ses efekti sağlayıcılarını içerir.
Mobil uyumlu web oyunları, iOS ses kısıtlamalarını ve dokunarak ses kilidini açma yöntemlerini ele alır.