Tone.js untuk Audio Game
Tone.js berjalan di atas Web Audio API dan membuat audio sintetis lebih praktis. Alih-alih memuat berkas audio, Anda menghasilkan suara secara terprogram. Ini berarti tanpa waktu pemuatan, variasi tak terbatas, dan suara yang dapat bereaksi terhadap gameplay secara real-time.
Cobalah (klik tombol untuk mendengarkan suara game sintetis):
Mengapa Memilih Tone.js daripada Berkas Audio
Berkas audio cocok untuk musik dan suara yang kompleks. Namun, untuk efek suara game, sintesis memiliki keunggulan nyata. Anda dapat memvariasikan nada dan tempo setiap kali suara diputar agar tidak terasa repetitif. Anda dapat menghasilkan suara secara langsung berdasarkan kondisi game. Selain itu, Anda sepenuhnya menghindari permintaan jaringan.
Kekurangannya, audio sintetis menggunakan lebih banyak CPU daripada memutar berkas. Untuk game sederhana, hal ini bukan masalah. Untuk game yang kompleks, Anda dapat menggabungkan kedua pendekatan tersebut.
Penyiapan
Muat Tone.js dari CDN atau npm:
<script src="https://unpkg.com/tone@15/build/Tone.js"></script>Atau dengan npm:
npm install toneimport * as Tone from 'tone'Masalah Autoplay
Browser memblokir audio hingga pengguna berinteraksi. Tone.js menangani hal ini, tetapi Anda perlu memanggil Tone.start() saat pengguna melakukan gestur:
document.addEventListener('click', async () => {
await Tone.start()
console.log('Audio siap')
}, { once: true })Tanpa ini, AudioContext akan tetap ditangguhkan dan tidak ada suara yang diputar.
Synth Pertama Anda
Synth menghasilkan suara. Yang paling sederhana adalah Tone.Synth:
const synth = new Tone.Synth().toDestination()
// Putar sebuah nada
synth.triggerAttackRelease('C4', '8n')triggerAttackRelease memainkan nada selama durasi tertentu. 'C4' adalah nada C tengah. '8n' adalah not seperdelapan pada tempo saat ini.
Synth memiliki osilator (sumber suara) dan envelope (cara suara muncul dan menghilang). Anda dapat menyesuaikan keduanya:
const synth = new Tone.Synth({
oscillator: { type: 'triangle' },
envelope: {
attack: 0.02,
decay: 0.2,
sustain: 0.3,
release: 0.4,
},
}).toDestination()Jenis osilator mencakup sine (murni, lembut), triangle (halus), sawtooth (berdengung), dan square (berongga, retro).
Membuat Sistem Efek Suara
Untuk game, Anda memerlukan beberapa synth untuk berbagai jenis suara. Berikut pola yang bekerja dengan baik:
let isInitialized = false
let softSynth = null
let subSynth = null
let noiseSynth = null
let sfxGain = null
function initSFX() {
if (isInitialized) return
// Kontrol volume utama
sfxGain = new Tone.Gain(0.7).toDestination()
// Synth lembut untuk suara melodis
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 untuk benturan yang dalam
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 untuk tekstur
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 memungkinkan Anda memainkan beberapa nada sekaligus. Synth biasa bersifat monofonik.
Contoh Suara Game
Berikut beberapa suara game umum dan cara membuatnya:
Mengambil koin - rangkaian nada yang naik terasa memuaskan:
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)
}Melompat - sapuan nada naik yang cepat:
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)
}Terkena serangan/kerusakan - nada menurun dengan noise:
function hit() {
const now = Tone.now()
subSynth.triggerAttackRelease('E2', '4n', now, 0.5)
noiseSynth.triggerAttackRelease('4n', now, 0.25)
}Kematian - nada-nada sedih yang menurun:
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)
}Menambahkan Efek
Efek membuat suara menjadi lebih kaya. Rangkai efek di antara synth dan destination:
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)
// Rangkaian: synth -> filter -> reverb -> gain -> destination
filter.connect(reverb)
reverb.connect(gain)
gain.toDestination()
const synth = new Tone.Synth().connect(filter)Filter lowpass memangkas frekuensi tinggi yang tajam. Reverb menambahkan kesan ruang. Gain mengontrol volume.
Pembatasan Frekuensi Pemutaran
Jika game Anda memicu banyak suara dengan cepat, suara-suara tersebut dapat tumpang tindih dan menjadi kacau. Tambahkan pembatasan frekuensi pemutaran:
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')
}Musik Latar Dinamis
Untuk musik yang bereaksi terhadap gameplay, gunakan sistem berbasis ketukan:
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 pada ketukan pertama
if (beatInMeasure === 0) {
bassSynth.triggerAttackRelease(chord.root, '4n', now, 0.6)
}
// Kick drum - hanya saat energi tinggi
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)
}
// Peristiwa dalam game memengaruhi energi
function onEnemyKill() {
energy = Math.min(1, energy + 0.1)
}Intensitas musik berubah berdasarkan apa yang terjadi di dalam game.
Drum dengan MembraneSynth dan NoiseSynth
Untuk perkusi, gunakan synth khusus:
// 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')Pembersihan
Buang synth saat Anda selesai menggunakannya untuk membebaskan sumber daya:
function cleanup() {
if (softSynth) softSynth.dispose()
if (subSynth) subSynth.dispose()
if (noiseSynth) noiseSynth.dispose()
if (sfxGain) sfxGain.dispose()
}Kesalahan Umum
Tidak menunggu Tone.start() - Audio tidak akan diputar hingga context dimulai melalui interaksi pengguna.
Membuat synth setiap kali suara diputar - Buat synth sekali saat inisialisasi, lalu gunakan kembali. Membuat synth baru setiap kali akan menyebabkan kebocoran memori.
Terlalu banyak suara yang tumpang tindih - Gunakan pembatasan frekuensi pemutaran dan deduplikasi untuk mencegah audio terdengar kacau.
Frekuensi yang tajam - Gunakan filter lowpass dan jenis osilator yang lebih lembut (sine, triangle) agar suara tidak mengganggu pemain.
Referensi Lainnya
Web Audio API untuk game membahas API yang mendasarinya serta cara memuat berkas audio.
Dokumentasi Tone.js menyediakan referensi API lengkap.
Tempat Menemukan Aset Game Gratis mencakup penyedia audio dan efek suara seperti Freesound.
Game web yang ramah perangkat seluler membahas pembatasan audio iOS dan pola sentuh-untuk-membuka-kunci.