import { _decorator, Component, AudioClip, AudioSource, resources, Node, find, director } from 'cc'; import { BundleLoader } from '../Core/BundleLoader'; const { ccclass, property } = _decorator; @ccclass('AudioManager') export class AudioManager extends Component { @property({ type: AudioSource, tooltip: '背景音乐播放器' }) public musicAudioSource: AudioSource = null; @property({ type: AudioSource, tooltip: 'UI音效播放器' }) public uiSoundAudioSource: AudioSource = null; @property({ type: AudioSource, tooltip: '敌人音效播放器' }) public enemySoundAudioSource: AudioSource = null; @property({ type: AudioSource, tooltip: '环境音效播放器' }) public environmentSoundAudioSource: AudioSource = null; @property({ type: AudioSource, tooltip: '武器音效播放器' }) public weaponSoundAudioSource: AudioSource = null; private musicVolume: number = 0.8; private uiSoundVolume: number = 0.8; private enemySoundVolume: number = 0.8; private environmentSoundVolume: number = 0.8; private weaponSoundVolume: number = 0.8; private soundEffectVolume: number = 0.8; private currentMusicClip: AudioClip = null; private bundleLoader: BundleLoader = null; private audioClipCache: Map = new Map(); // 并发播放保护 private _musicRequestId: number = 0; private _currentMusicPath: string | null = null; // 单例实例 private static _instance: AudioManager = null; onLoad() { // 设置单例(在 onLoad 阶段更早创建,确保静态调用可用) if (AudioManager._instance === null) { AudioManager._instance = this; // 如需跨场景保留,可取消注释以下两行 // this.node.parent = null; // director.addPersistRootNode(this.node); } else { this.node.destroy(); return; } this.bundleLoader = BundleLoader.getInstance(); this.initializeAudioSources(); } public static getInstance(): AudioManager { return AudioManager._instance; } private initializeAudioSources() { console.log('[AudioManager] 开始初始化音频源'); if (!this.musicAudioSource) { console.log('[AudioManager] 创建音乐播放器AudioSource'); const musicNode = new Node('MusicPlayer'); this.node.addChild(musicNode); this.musicAudioSource = musicNode.addComponent(AudioSource); this.musicAudioSource.volume = this.musicVolume; console.log('[AudioManager] 音乐播放器AudioSource创建完成'); } else { console.log('[AudioManager] 音乐播放器AudioSource已存在'); } if (!this.uiSoundAudioSource) { console.log('[AudioManager] 创建UI音效播放器AudioSource'); const uiSfxNode = new Node('UISoundPlayer'); this.node.addChild(uiSfxNode); this.uiSoundAudioSource = uiSfxNode.addComponent(AudioSource); this.uiSoundAudioSource.volume = this.uiSoundVolume; } if (!this.enemySoundAudioSource) { const enemySfxNode = new Node('EnemySoundPlayer'); this.node.addChild(enemySfxNode); this.enemySoundAudioSource = enemySfxNode.addComponent(AudioSource); this.enemySoundAudioSource.volume = this.enemySoundVolume; } if (!this.environmentSoundAudioSource) { const envSfxNode = new Node('EnvironmentSoundPlayer'); this.node.addChild(envSfxNode); this.environmentSoundAudioSource = envSfxNode.addComponent(AudioSource); this.environmentSoundAudioSource.volume = this.environmentSoundVolume; } if (!this.weaponSoundAudioSource) { const weaponSfxNode = new Node('WeaponSoundPlayer'); this.node.addChild(weaponSfxNode); this.weaponSoundAudioSource = weaponSfxNode.addComponent(AudioSource); this.weaponSoundAudioSource.volume = this.weaponSoundVolume; } console.log('[AudioManager] 音频源初始化完成'); console.log(`[AudioManager] musicAudioSource: ${this.musicAudioSource ? '已创建' : '未创建'}`); // 设置初始音量 this.updateAllVolumes(); } public async playMusic(musicPath: string, loop: boolean = true) { if (!this.musicAudioSource) { console.warn('[AudioManager] 音乐播放器未初始化'); return; } // 避免重复请求同一首已在播放的音乐 if (this._currentMusicPath === musicPath && this.musicAudioSource.playing) { this.musicAudioSource.loop = loop; console.log(`[AudioManager] 已在播放目标音乐,忽略重复请求: ${musicPath}`); return; } const requestId = ++this._musicRequestId; // 为本次播放分配请求ID console.log(`[AudioManager] 发起音乐播放请求#${requestId}: ${musicPath}`); try { const bundlePath = musicPath.replace('data/', '').replace(/\.(mp3|wav|ogg)$/i, ''); const clip = await this.bundleLoader.loadAssetFromBundle('data', bundlePath, AudioClip); // 若在加载期间有新的播放请求,丢弃本次播放 if (requestId !== this._musicRequestId) { console.log(`[AudioManager] 丢弃过期音乐请求#${requestId}: ${musicPath}(当前请求ID为#${this._musicRequestId})`); return; } // 保险:在开始新音乐前停止当前播放,避免叠加 if (this.musicAudioSource.playing) { this.musicAudioSource.stop(); } this.currentMusicClip = clip; this._currentMusicPath = musicPath; this.musicAudioSource.clip = clip; this.musicAudioSource.loop = loop; this.musicAudioSource.play(); console.log(`[AudioManager] 播放音乐#${requestId}: ${musicPath}`); } catch (error) { console.error(`[AudioManager] 加载音乐失败: ${musicPath}`, error); } } public stopMusic() { console.log('[AudioManager] stopMusic方法被调用'); console.log(`[AudioManager] musicAudioSource状态: ${this.musicAudioSource ? '存在' : '不存在'}`); // 取消所有正在进行的音乐加载/播放请求 this._musicRequestId++; console.log(`[AudioManager] 取消到请求ID#${this._musicRequestId}(后续仅处理更新后的请求)`); if (this.musicAudioSource) { console.log(`[AudioManager] musicAudioSource.playing: ${this.musicAudioSource.playing}`); console.log(`[AudioManager] currentMusicClip: ${this.currentMusicClip ? this.currentMusicClip.name : '无'}`); if (this.musicAudioSource.playing) { this.musicAudioSource.stop(); console.log('[AudioManager] 停止音乐播放'); } else { console.log('[AudioManager] 音乐未在播放,无需停止'); } this.currentMusicClip = null; this._currentMusicPath = null; this.musicAudioSource.clip = null; console.log('[AudioManager] 已清除音乐剪辑引用'); } else { console.warn('[AudioManager] 音乐播放器未初始化,无法停止音乐'); } } public pauseMusic() { if (this.musicAudioSource && this.musicAudioSource.playing) { this.musicAudioSource.pause(); console.log('[AudioManager] 暂停音乐播放'); } } public resumeMusic() { if (this.musicAudioSource && this.currentMusicClip && !this.musicAudioSource.playing) { this.musicAudioSource.play(); console.log('[AudioManager] 恢复音乐播放'); } } public playSoundEffect(sfxPath: string, volume?: number) { this.playSound(this.uiSoundAudioSource, sfxPath, volume, this.soundEffectVolume, '音效'); } public playUISound(sfxPath: string, volume?: number) { this.playSound(this.uiSoundAudioSource, sfxPath, volume, this.uiSoundVolume, 'UI音效'); } public playEnemySound(sfxPath: string, volume?: number) { this.playSound(this.enemySoundAudioSource, sfxPath, volume, this.enemySoundVolume, '敌人音效'); } public playEnvironmentSound(sfxPath: string, volume?: number) { this.playSound(this.environmentSoundAudioSource, sfxPath, volume, this.environmentSoundVolume, '环境音效'); } public playWeaponSound(sfxPath: string, volume?: number) { this.playSound(this.weaponSoundAudioSource, sfxPath, volume, this.weaponSoundVolume, '武器音效'); } private async playSound(audioSource: AudioSource, sfxPath: string, volume: number | undefined, baseVolume: number, soundType: string) { if (!audioSource) { console.warn(`[AudioManager] ${soundType}播放器未初始化`); return; } try { const cleanPath = sfxPath.replace('data/', '').replace(/\.(mp3|wav|ogg)$/i, ''); const bundlePath = cleanPath; const clip = await this.bundleLoader.loadAssetFromBundle('data', bundlePath, AudioClip); if (volume !== undefined) { audioSource.volume = Math.max(0, Math.min(1, volume)); } else { audioSource.volume = baseVolume; } audioSource.playOneShot(clip); console.log(`[AudioManager] 播放${soundType}: ${cleanPath}`); } catch (error) { console.error(`[AudioManager] 加载${soundType}失败: ${sfxPath}`, error); } } public setMusicVolume(volume: number) { this.musicVolume = Math.max(0, Math.min(1, volume)); this.updateMusicVolume(); //console.log(`[AudioManager] 设置音乐音量: ${this.musicVolume}`); } public setSoundEffectVolume(volume: number) { this.soundEffectVolume = Math.max(0, Math.min(1, volume)); //console.log(`[AudioManager] 设置音效音量: ${this.soundEffectVolume}`); } public setUISoundVolume(volume: number) { this.uiSoundVolume = Math.max(0, Math.min(1, volume)); this.updateUISoundVolume(); } public setEnemySoundVolume(volume: number) { this.enemySoundVolume = Math.max(0, Math.min(1, volume)); this.updateEnemySoundVolume(); } public setEnvironmentSoundVolume(volume: number) { this.environmentSoundVolume = Math.max(0, Math.min(1, volume)); this.updateEnvironmentSoundVolume(); } public setWeaponSoundVolume(volume: number) { this.weaponSoundVolume = Math.max(0, Math.min(1, volume)); this.updateWeaponSoundVolume(); } public setAllSoundVolume(volume: number) { this.setSoundEffectVolume(volume); this.setUISoundVolume(volume); this.setEnemySoundVolume(volume); this.setEnvironmentSoundVolume(volume); this.setWeaponSoundVolume(volume); } public getMusicVolume(): number { return this.musicVolume; } public getSoundEffectVolume(): number { return this.soundEffectVolume; } public getUISoundVolume(): number { return this.uiSoundVolume; } public getEnemySoundVolume(): number { return this.enemySoundVolume; } public getEnvironmentSoundVolume(): number { return this.environmentSoundVolume; } public getWeaponSoundVolume(): number { return this.weaponSoundVolume; } private updateMusicVolume() { if (this.musicAudioSource) { this.musicAudioSource.volume = this.musicVolume; } } private updateUISoundVolume() { if (this.uiSoundAudioSource) { this.uiSoundAudioSource.volume = this.uiSoundVolume; } } private updateEnemySoundVolume() { if (this.enemySoundAudioSource) { this.enemySoundAudioSource.volume = this.enemySoundVolume; } } private updateEnvironmentSoundVolume() { if (this.environmentSoundAudioSource) { this.environmentSoundAudioSource.volume = this.environmentSoundVolume; } } private updateWeaponSoundVolume() { if (this.weaponSoundAudioSource) { this.weaponSoundAudioSource.volume = this.weaponSoundVolume; } } private updateAllVolumes() { this.updateMusicVolume(); this.updateUISoundVolume(); this.updateEnemySoundVolume(); this.updateEnvironmentSoundVolume(); this.updateWeaponSoundVolume(); } public muteAll() { this.setMusicVolume(0); this.setAllSoundVolume(0); } public unmuteAll() { this.setMusicVolume(0.8); this.setAllSoundVolume(0.8); } public muteAllSounds() { this.setAllSoundVolume(0); } public unmuteAllSounds() { this.setAllSoundVolume(0.8); } public isMusicPlaying(): boolean { return !!(this.musicAudioSource && this.musicAudioSource.playing); } public clearAudioCache() { console.log(`[AudioManager] 清理音频缓存,共${this.audioClipCache.size}个音效`); this.audioClipCache.clear(); } public getCacheInfo(): {count: number, paths: string[]} { return { count: this.audioClipCache.size, paths: Array.from(this.audioClipCache.keys()) }; } onDestroy() { this.clearAudioCache(); if (AudioManager._instance === this) { AudioManager._instance = null; } } } export class Audio { static playMusic(musicPath: string, loop: boolean = true) { const manager = AudioManager.getInstance(); if (manager) { manager.playMusic(musicPath, loop); } } static playSFX(sfxPath: string, volume?: number) { const manager = AudioManager.getInstance(); if (manager) { manager.playSoundEffect(sfxPath, volume); } } static playUISound(sfxPath: string, volume?: number) { const manager = AudioManager.getInstance(); if (manager) { manager.playUISound(sfxPath, volume); } } static playEnemySound(sfxPath: string, volume?: number) { const manager = AudioManager.getInstance(); if (manager) { manager.playEnemySound(sfxPath, volume); } } static playEnvironmentSound(sfxPath: string, volume?: number) { const manager = AudioManager.getInstance(); if (manager) { manager.playEnvironmentSound(sfxPath, volume); } } static playWeaponSound(sfxPath: string, volume?: number) { const manager = AudioManager.getInstance(); if (manager) { manager.playWeaponSound(sfxPath, volume); } } static setMusicVolume(volume: number) { const manager = AudioManager.getInstance(); if (manager) { manager.setMusicVolume(volume); } } static setSFXVolume(volume: number) { const manager = AudioManager.getInstance(); if (manager) { manager.setSoundEffectVolume(volume); } } static setUISoundVolume(volume: number) { const manager = AudioManager.getInstance(); if (manager) { manager.setUISoundVolume(volume); } } static setEnemySoundVolume(volume: number) { const manager = AudioManager.getInstance(); if (manager) { manager.setEnemySoundVolume(volume); } } static setEnvironmentSoundVolume(volume: number) { const manager = AudioManager.getInstance(); if (manager) { manager.setEnvironmentSoundVolume(volume); } } static setWeaponSoundVolume(volume: number) { const manager = AudioManager.getInstance(); if (manager) { manager.setWeaponSoundVolume(volume); } } static setAllSoundVolume(volume: number) { const manager = AudioManager.getInstance(); if (manager) { manager.setAllSoundVolume(volume); } } static stopMusic() { const manager = AudioManager.getInstance(); if (manager) { manager.stopMusic(); } } static pauseMusic() { const manager = AudioManager.getInstance(); if (manager) { manager.pauseMusic(); } } static resumeMusic() { const manager = AudioManager.getInstance(); if (manager) { manager.resumeMusic(); } } static muteAll() { const manager = AudioManager.getInstance(); if (manager) { manager.muteAll(); } } static unmuteAll() { const manager = AudioManager.getInstance(); if (manager) { manager.unmuteAll(); } } static muteAllSounds() { const manager = AudioManager.getInstance(); if (manager) { manager.muteAllSounds(); } } static unmuteAllSounds() { const manager = AudioManager.getInstance(); if (manager) { manager.unmuteAllSounds(); } } static clearAudioCache() { const manager = AudioManager.getInstance(); if (manager) { manager.clearAudioCache(); } } static getCacheInfo(): {count: number, paths: string[]} { const manager = AudioManager.getInstance(); if (manager) { return manager.getCacheInfo(); } return { count: 0, paths: [] }; } static ensureManager(): AudioManager | null { let manager = AudioManager.getInstance(); if (manager) return manager; try { const canvas = find('Canvas') as Node | null; const sceneRoot: any = director.getScene(); const parent: Node | null = canvas || (sceneRoot as Node | null); if (!parent) { console.warn('[Audio] 场景未准备好,无法创建AudioManager'); return null; } // 先尝试在场景中查找已存在的 AudioManager 组件 if (canvas && typeof (canvas as any).getComponentInChildren === 'function') { const existing = (canvas as any).getComponentInChildren(AudioManager); if (existing) { return existing; } } if (sceneRoot && typeof sceneRoot.getComponentInChildren === 'function') { const existingInScene = sceneRoot.getComponentInChildren(AudioManager); if (existingInScene) { return existingInScene; } } // 查找名为 AudioManager 的节点,复用其组件 let node = parent.getChildByName && parent.getChildByName('AudioManager'); if (!node) { node = new Node('AudioManager'); parent.addChild(node as Node); } const reused = (node as Node).getComponent(AudioManager); manager = reused ? reused : (node as Node).addComponent(AudioManager); return manager; } catch (e) { console.error('[Audio] 创建AudioManager失败:', e); return null; } } static updateBGMByTopUI(): void { try { const manager = Audio.ensureManager(); if (!manager) { console.warn('[Audio] AudioManager未初始化,稍后重试BGM切换'); return; } const mainUI = find('Canvas/MainUI'); const gameUI = find('Canvas/GameLevelUI'); const isMainVisible = !!(mainUI && (mainUI as Node).activeInHierarchy); const isGameVisible = !!(gameUI && (gameUI as Node).activeInHierarchy); let playTarget: 'main' | 'game' | null = null; if (isMainVisible && !isGameVisible) { playTarget = 'main'; } else if (!isMainVisible && isGameVisible) { playTarget = 'game'; } else if (isMainVisible && isGameVisible) { const mainIndex = (mainUI as Node).getSiblingIndex(); const gameIndex = (gameUI as Node).getSiblingIndex(); playTarget = gameIndex >= mainIndex ? 'game' : 'main'; } else { Audio.stopMusic(); return; } Audio.stopMusic(); Audio.setMusicVolume(0.8); if (playTarget === 'game') { Audio.playMusic('data/弹球音效/fight bgm', true); console.log('[Audio] 切换到游戏战斗音乐'); } else { Audio.playMusic('data/弹球音效/ui bgm', true); console.log('[Audio] 切换到主界面背景音乐'); } } catch (err) { console.error('[Audio] updateBGMByTopUI 失败:', err); } } }