Files
cocos_zhuzhan/assets/scripts/audio/GameAudio.ts

152 lines
5.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { AudioClip, AudioSource, director, Node, resources } from 'cc';
/** Unity Assets/Art/Audio → resources/audio/ */
const CLIPS = {
background: 'audio/Backgroud',
move: 'audio/Move',
jump: 'audio/Jump',
fail: 'audio/Fail',
success: 'audio/Success',
coins: 'audio/GetCoins',
} as const;
type SfxKey = Exclude<keyof typeof CLIPS, 'background'>;
/**
* 对齐 Unity关卡加载时 curLevel 上循环播放 Backgroud.mp3
* 音效路径与 Player.prefab 一致。
*/
export class GameAudio {
private static readonly cache = new Map<string, AudioClip>();
private static readonly loading = new Map<string, Promise<AudioClip | null>>();
private static sfxHost: Node | null = null;
private static muted = false;
static isMuted(): boolean {
return GameAudio.muted;
}
/** HUD / JS callMute静音后背景与全部音效都不再出声 */
static setMuted(mute: boolean) {
GameAudio.muted = mute;
GameAudio.applyVolumeToScene();
}
static async preload(): Promise<void> {
await Promise.all(Object.values(CLIPS).map((p) => GameAudio.loadClip(p)));
}
static loadClip(path: string): Promise<AudioClip | null> {
const hit = GameAudio.cache.get(path);
if (hit) return Promise.resolve(hit);
const pending = GameAudio.loading.get(path);
if (pending) return pending;
const task = new Promise<AudioClip | null>((resolve) => {
resources.load(path, AudioClip, (err, clip) => {
GameAudio.loading.delete(path);
if (err || !clip) {
console.warn(`[GameAudio] 加载失败: ${path}`, err);
resolve(null);
return;
}
GameAudio.cache.set(path, clip);
resolve(clip);
});
});
GameAudio.loading.set(path, task);
return task;
}
/** 在关卡根节点播放循环背景音乐(对齐 Unity createNewLevel */
static async playBackground(levelRoot: Node): Promise<void> {
if (!levelRoot?.isValid) return;
const clip = await GameAudio.loadClip(CLIPS.background);
if (!clip || !levelRoot.isValid) return;
let host = levelRoot.getChildByName('_BGM');
if (!host) {
host = new Node('_BGM');
host.parent = levelRoot;
}
const src = host.getComponent(AudioSource) ?? host.addComponent(AudioSource);
src.clip = clip;
src.loop = true;
src.playOnAwake = false;
src.volume = GameAudio.currentVolume();
if (!src.playing) {
src.play();
}
}
static playSfx(key: SfxKey, host?: Node) {
if (GameAudio.muted) return;
void GameAudio.playSfxAsync(key, host);
}
/** 对齐 Unity同一 AudioSource 播放中则不重复触发移动/跳跃音效 */
static async playSfxOnSource(src: AudioSource, key: SfxKey): Promise<boolean> {
if (GameAudio.muted || !src?.node?.isValid || src.playing) return false;
const clip = await GameAudio.loadClip(CLIPS[key]);
if (!clip || GameAudio.muted || !src.node?.isValid) return false;
src.clip = clip;
src.loop = false;
src.volume = GameAudio.currentVolume();
src.play();
return true;
}
private static async playSfxAsync(key: SfxKey, host?: Node) {
if (GameAudio.muted) return;
const clip = await GameAudio.loadClip(CLIPS[key]);
if (!clip || GameAudio.muted) return;
const root = host?.isValid ? host : GameAudio.ensureSfxHost();
if (!root?.isValid) return;
const src = root.getComponent(AudioSource) ?? root.addComponent(AudioSource);
src.volume = GameAudio.currentVolume();
src.playOneShot(clip, GameAudio.currentVolume());
}
private static ensureSfxHost(): Node | null {
if (GameAudio.sfxHost?.isValid) return GameAudio.sfxHost;
const scene = director.getScene();
if (!scene) return null;
let host = scene.getChildByName('_GameSFX');
if (!host) {
host = new Node('_GameSFX');
host.parent = scene;
}
GameAudio.sfxHost = host;
return host;
}
/** 浏览器需用户交互后才能播放音频,首次点击 HUD 时恢复 */
static resumeAll() {
const scene = director.getScene();
if (!scene) return;
const vol = GameAudio.currentVolume();
for (const src of scene.getComponentsInChildren(AudioSource)) {
src.volume = vol;
if (src.clip && !src.playing) src.play();
}
}
private static currentVolume(): number {
return GameAudio.muted ? 0 : 1;
}
private static applyVolumeToScene() {
const scene = director.getScene();
if (!scene) return;
const vol = GameAudio.currentVolume();
for (const src of scene.getComponentsInChildren(AudioSource)) {
src.volume = vol;
}
}
}