Overworld
包参考

@overworld-engine/audio

BGM/音效管理器:场景-曲目映射、切曲淡入淡出与自动播放策略处理

BGM / 音效管理器:单例 BGM 音源、切曲淡入淡出、浏览器自动播放策略处理 (首次用户交互后自动重试)、可选的音量与静音设置持久化(经 persist 显式开启), 并可订阅事件总线上的 scene:changed 事件自动切换场景 BGM。播放经可注入的 AudioBackend(缺省 HTMLAudio,行为不变;微信小游戏注入 weapp 后端)。

包内零游戏内容:曲目表、场景映射全部由配置注入。所有浏览器 API 均有守卫, 在 Node / SSR / 测试环境中导入和创建不会崩溃(仅记录状态、不实际播放)。

快速开始

import { createAudioManager } from '@overworld-engine/audio'

const audio = createAudioManager({
  tracks: {
    town: '/bgm/town.mp3',
    dungeon: '/bgm/dungeon.mp3',
    pickup: '/sfx/pickup.mp3',
  },
  sceneTracks: { plaza: 'town', crypt: 'dungeon' }, // 场景 id → 曲目 id
})

// 之后每次 gameEvents.emit('scene:changed', ...) 都会自动切换 BGM
audio.playSfx('pickup') // 一次性音效

配置(AudioManagerConfig)

选项默认说明
tracks必填曲目 id → 音频 URL(BGM 与音效共用)
sceneTracks场景 id → 曲目 id;未映射的场景会停止当前 BGM
autoSubscribeSceneChangestrue订阅总线 scene:changed 自动换曲
events全局 gameEvents自定义事件总线(测试时传入新实例);bus 是保留的旧别名,两者同传时 events 优先
volume / sfxVolume0.7初始 BGM / 音效音量(0–1)
fadeDuration1000切曲淡入淡出时长(ms),0 表示立即切换
looptrueBGM 是否循环
backendhtmlAudioBackend播放后端注入(见下文"后端注入");缺省为 HTMLAudio,行为不变
pauseOnHidefalse订阅总线 app:paused / app:resumed(由 @overworld-engine/platform 桥发出):切后台自动暂停 BGM、回前台恢复;dispose() 退订
persist省略(关闭)框架统一约定:省略或 false 不持久化;true 用默认配置持久化音量/静音(键 overworld:audio);可传对象自定义。v0.9 起省略即关闭(此前默认开启),依赖持久化请显式传 persist: true

Manager API

  • store — 底层 zustand vanilla store(StoreApi<AudioState>),状态为 { volume, sfxVolume, muted, currentTrackId, unlocked };getState() 取快照, React 里用 zustand 的 useStore:

    import { useStore } from 'zustand'
    
    const muted = useStore(audio.store, (s) => s.muted)
  • playTrack(trackId) / stopTrack() — 播放 / 停止 BGM(带淡入淡出)

  • playSceneTrack(sceneId) — 按场景映射播放;resolveSceneTrack(sceneId) 仅做解析

  • playSfx(trackId) — 一次性音效

  • setVolume(v) / setSfxVolume(v) — 音量(自动钳制到 0–1)

  • setMuted(muted) / toggleMute() — 静音;静音期间仍记录目标曲目,取消静音后恢复播放

  • dispose() — 退订总线、移除解锁监听并停止播放

后端注入(AudioBackend)

管理器的全部播放操作都经过可注入的后端接口,跨端只需换后端、逻辑零改动:

interface AudioBackend {
  create(url: string): AudioHandle // 每个音源一个 handle
  isAvailable?(): boolean          // false = 仅记录状态(Node/SSR 守卫)
}
interface AudioHandle {
  play(): Promise<void> | void
  pause(): void
  setVolume(v: number): void
  getVolume(): number              // 淡入淡出读取当前音量
  setLoop(loop: boolean): void
  isPaused(): boolean
  onEnded(cb: () => void): () => void // 一次性音效播完自动 destroy
  destroy(): void
}
  • 缺省 htmlAudioBackend(已导出):new Audio(url),与历史行为一致;
  • 微信小游戏:backend: createWeappAudioBackend() (来自 @overworld-engine/adapters-weapp,映射 wx.createInnerAudioContext)。
  • silentBackend 是无 DOM、SSR 与测试环境的静默实现,满足相同契约但不播放声音。

环境音区的纯函数也公开导出:zoneWeight(zone, listener) 根据 inner/outer radius 返回 0..1 线性权重;mixBuses(...) 把 zone、ambience 与 master 音量 合成为最终 gain。两者无 Web Audio 依赖,可单独测试。

自动播放策略

浏览器通常禁止无交互时自动播放。当 audio.play() 被拒绝时,管理器会在 window 上注册一次性的 pointerdown / keydown 监听,首次用户交互后自动 重试当前曲目;成功后 unlocked 置为 true

依赖

依赖 @overworld-engine/core(事件总线与持久化辅助);peerDependency 为 zustand

本页目录