Engineering

用 Web Audio API 做遊戲音效:不用任何音檔

2026 年 5 月 4 日 約 9 分鐘閱讀 作者:Hao0321 Studio

DODGE MASTER 整款遊戲只有 921 行 HTML,包含撞擊、金幣、連擊、雷射、引擎、UI click 等十幾種音效。神奇的是它沒有引用任何 .mp3 或 .wav 檔案。所有聲音都是用 Web Audio API 即時用程式碼合成出來的。這篇拆解整套合成式音效系統 — 為什麼這樣做、怎麼寫每一個音效、瀏覽器相容性怎麼處理。

為什麼不用音檔

看起來「不用音檔」是反直覺的選擇。實際的決策理由:

缺點:合成音效聽起來「電子感」、「8-bit」風格,做不出寫實樂器音色。但對賽博龐克風的閃避遊戲,這個風格反而加分。

Web Audio API 基礎

Web Audio API 的核心概念是「節點圖(node graph)」。每個聲音由:

  1. OscillatorNode(振盪器)— 產生原始波形
  2. GainNode(音量)— 控制音量與包絡(envelope)
  3. AudioContext.destination — 喇叭輸出

連起來就是:oscillator.connect(gain).connect(ctx.destination)

第一個音效:金幣聲

最簡單的音效是「pickup coin」— 一個快速上升的方波音調。

const ctx = new (window.AudioContext || window.webkitAudioContext)();

function coinSfx() {
  const osc = ctx.createOscillator();
  const gain = ctx.createGain();

  osc.type = 'square';
  osc.frequency.setValueAtTime(800, ctx.currentTime);
  osc.frequency.exponentialRampToValueAtTime(1600, ctx.currentTime + 0.05);

  gain.gain.setValueAtTime(0.15, ctx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.1);

  osc.connect(gain).connect(ctx.destination);
  osc.start();
  osc.stop(ctx.currentTime + 0.1);
}

這 12 行程式碼產生的聲音聽起來像 SNES 時代的金幣音效。重點:

第二個音效:撞擊聲

撞擊音(hit)需要「噪音 + 急速衰減」的特性。用噪音 buffer 替代 oscillator:

function hitSfx() {
  // 1. 產生 0.1 秒的白噪音
  const bufferSize = ctx.sampleRate * 0.1;
  const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
  const data = buffer.getChannelData(0);
  for (let i = 0; i < bufferSize; i++) {
    data[i] = Math.random() * 2 - 1;
  }

  // 2. 連接 source → filter → gain → output
  const source = ctx.createBufferSource();
  source.buffer = buffer;

  const filter = ctx.createBiquadFilter();
  filter.type = 'lowpass';
  filter.frequency.setValueAtTime(1500, ctx.currentTime);
  filter.frequency.exponentialRampToValueAtTime(100, ctx.currentTime + 0.08);

  const gain = ctx.createGain();
  gain.gain.setValueAtTime(0.4, ctx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.1);

  source.connect(filter).connect(gain).connect(ctx.destination);
  source.start();
}

差異:

第三個音效:連擊上升音

每連擊一次都播一個「越來越高」的音 — 給玩家持續的正向回饋。

let comboCount = 0;

function comboSfx() {
  comboCount++;
  const baseFreq = 400;
  const pitch = baseFreq * Math.pow(1.05, Math.min(comboCount, 20));

  const osc = ctx.createOscillator();
  const gain = ctx.createGain();

  osc.type = 'triangle';
  osc.frequency.setValueAtTime(pitch, ctx.currentTime);

  gain.gain.setValueAtTime(0.12, ctx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15);

  osc.connect(gain).connect(ctx.destination);
  osc.start();
  osc.stop(ctx.currentTime + 0.15);
}

關鍵:每次 combo +1,頻率上升 5%。連擊到 20 之後封頂,不然會超出可聽範圍。

音效調色盤(10 種音)

整套 DODGE MASTER 用的音效設計:

音效用途波形頻率特徵
coin撿金幣square800→1600 Hz 上升
hit玩家撞擊noise1500→100 Hz lowpass
combo連擊triangle動態頻率(依連擊數)
powerup道具獲得square + sine 疊加和弦
laser射擊sawtooth2000→200 Hz 急降
death死亡noise + sine下降爆炸
levelup等級提升triangle 三連音琶音上行
clickUI 按鍵square1200 Hz 短點
warning警告sawtooth低音持續
spin轉盤square + LFO顫音效果

瀏覽器相容性踩坑

1. iOS Safari 必須使用者互動才能播

iPhone Safari 在使用者第一次 click/tap 之前不准任何 AudioContext 播放。解法:

document.addEventListener('click', () => {
  if (ctx.state === 'suspended') {
    ctx.resume();
  }
}, { once: true });

第一次 click 喚醒 AudioContext,之後就可以正常播放。

2. 舊版 Safari 沒有 AudioContext

舊版 iOS 用 webkitAudioContext。前面建構式裡的 fallback 處理:

const ctx = new (window.AudioContext || window.webkitAudioContext)();

3. Chrome 自動播放政策

背景音樂(不是音效)必須等 user gesture 才能 start。對於遊戲音效,因為都是 click 觸發的,沒問題。但如果想做 BGM,要等到使用者「按開始」之後再 ctx.resume()。

4. Android 上的 AudioWorklet 延遲

低階 Android 在播放 oscillator 時會有 30–80ms 延遲。這個無解,只能接受。但 Web Audio API 的延遲已經比 HTML5 audio 元素低很多(後者在 Android 上可達 200ms)。

音量管控

遊戲中同一秒可能觸發 5–10 個音效。如果每個都 0.4 音量,會爆音(clipping)。我用一個 master gain 統一管控:

const masterGain = ctx.createGain();
masterGain.gain.value = 0.6;
masterGain.connect(ctx.destination);

// 所有音效都連到 masterGain 而不是 ctx.destination
osc.connect(individualGain).connect(masterGain);

玩家還可以從設定改 masterGain.gain.value(0–1)做整體音量控制。

實際省下多少

項目音檔方案合成方案
10 種音效大小~ 250 KB~ 4 KB(程式碼)
初次載入延遲~ 300 ms0 ms
授權成本 / 風險需追蹤
動態變化能力困難容易
音色多樣性低(限 8-bit 風格)

結語

合成式音效不是萬靈丹 — 寫實風遊戲還是要用真實音源。但對瀏覽器小遊戲、街機風、復古風、低延遲需求的場景,Web Audio API 是被低估的工具。整套寫熟了之後,新遊戲的音效從「找音源、買授權、調 mix」變成「20 行 code 搞定」。

完整的 sfx 模組(約 200 行)我有計畫整理成 npm package。如果你做相關專案有興趣,歡迎寄信 lo246179268@gmail.com