|
|
@@ -1,14 +1,9 @@
|
|
|
-import { _decorator, Component, AudioClip, AudioSource, resources, Node } from 'cc';
|
|
|
+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;
|
|
|
|
|
|
@@ -24,33 +19,32 @@ export class AudioManager extends Component {
|
|
|
@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;
|
|
|
|
|
|
- // Bundle加载器
|
|
|
private bundleLoader: BundleLoader = null;
|
|
|
|
|
|
- // 音频缓存
|
|
|
private audioClipCache: Map<string, AudioClip> = new Map();
|
|
|
-
|
|
|
+
|
|
|
+ // 并发播放保护
|
|
|
+ private _musicRequestId: number = 0;
|
|
|
+ private _currentMusicPath: string | null = null;
|
|
|
+
|
|
|
// 单例实例
|
|
|
private static _instance: AudioManager = null;
|
|
|
|
|
|
- start() {
|
|
|
- // 设置单例
|
|
|
+ onLoad() {
|
|
|
+ // 设置单例(在 onLoad 阶段更早创建,确保静态调用可用)
|
|
|
if (AudioManager._instance === null) {
|
|
|
AudioManager._instance = this;
|
|
|
- // 防止场景切换时被销毁
|
|
|
+ // 如需跨场景保留,可取消注释以下两行
|
|
|
// this.node.parent = null;
|
|
|
// director.addPersistRootNode(this.node);
|
|
|
} else {
|
|
|
@@ -62,26 +56,19 @@ export class AudioManager extends Component {
|
|
|
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('MusicAudioSource');
|
|
|
- musicNode.parent = this.node;
|
|
|
+ const musicNode = new Node('MusicPlayer');
|
|
|
+ this.node.addChild(musicNode);
|
|
|
this.musicAudioSource = musicNode.addComponent(AudioSource);
|
|
|
- this.musicAudioSource.loop = true;
|
|
|
+ this.musicAudioSource.volume = this.musicVolume;
|
|
|
console.log('[AudioManager] 音乐播放器AudioSource创建完成');
|
|
|
} else {
|
|
|
console.log('[AudioManager] 音乐播放器AudioSource已存在');
|
|
|
@@ -89,31 +76,31 @@ export class AudioManager extends Component {
|
|
|
|
|
|
if (!this.uiSoundAudioSource) {
|
|
|
console.log('[AudioManager] 创建UI音效播放器AudioSource');
|
|
|
- const uiSfxNode = new Node('UISoundAudioSource');
|
|
|
- uiSfxNode.parent = this.node;
|
|
|
+ const uiSfxNode = new Node('UISoundPlayer');
|
|
|
+ this.node.addChild(uiSfxNode);
|
|
|
this.uiSoundAudioSource = uiSfxNode.addComponent(AudioSource);
|
|
|
- this.uiSoundAudioSource.loop = false;
|
|
|
+ this.uiSoundAudioSource.volume = this.uiSoundVolume;
|
|
|
}
|
|
|
|
|
|
if (!this.enemySoundAudioSource) {
|
|
|
- const enemySfxNode = new Node('EnemySoundAudioSource');
|
|
|
- enemySfxNode.parent = this.node;
|
|
|
+ const enemySfxNode = new Node('EnemySoundPlayer');
|
|
|
+ this.node.addChild(enemySfxNode);
|
|
|
this.enemySoundAudioSource = enemySfxNode.addComponent(AudioSource);
|
|
|
- this.enemySoundAudioSource.loop = false;
|
|
|
+ this.enemySoundAudioSource.volume = this.enemySoundVolume;
|
|
|
}
|
|
|
|
|
|
if (!this.environmentSoundAudioSource) {
|
|
|
- const envSfxNode = new Node('EnvironmentSoundAudioSource');
|
|
|
- envSfxNode.parent = this.node;
|
|
|
+ const envSfxNode = new Node('EnvironmentSoundPlayer');
|
|
|
+ this.node.addChild(envSfxNode);
|
|
|
this.environmentSoundAudioSource = envSfxNode.addComponent(AudioSource);
|
|
|
- this.environmentSoundAudioSource.loop = false;
|
|
|
+ this.environmentSoundAudioSource.volume = this.environmentSoundVolume;
|
|
|
}
|
|
|
|
|
|
if (!this.weaponSoundAudioSource) {
|
|
|
- const weaponSfxNode = new Node('WeaponSoundAudioSource');
|
|
|
- weaponSfxNode.parent = this.node;
|
|
|
+ const weaponSfxNode = new Node('WeaponSoundPlayer');
|
|
|
+ this.node.addChild(weaponSfxNode);
|
|
|
this.weaponSoundAudioSource = weaponSfxNode.addComponent(AudioSource);
|
|
|
- this.weaponSoundAudioSource.loop = false;
|
|
|
+ this.weaponSoundAudioSource.volume = this.weaponSoundVolume;
|
|
|
}
|
|
|
|
|
|
console.log('[AudioManager] 音频源初始化完成');
|
|
|
@@ -123,40 +110,57 @@ export class AudioManager extends Component {
|
|
|
this.updateAllVolumes();
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放背景音乐
|
|
|
- * @param musicPath 音乐资源路径
|
|
|
- * @param loop 是否循环播放
|
|
|
- */
|
|
|
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 {
|
|
|
- // 转换路径格式:从 "data/弹球音效/fight bgm" 转换为 "弹球音效/fight bgm"
|
|
|
const bundlePath = musicPath.replace('data/', '');
|
|
|
const clip = await this.bundleLoader.loadAssetFromBundle<AudioClip>('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] 播放音乐: ${musicPath}`);
|
|
|
+ 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 : '无'}`);
|
|
|
@@ -167,8 +171,8 @@ export class AudioManager extends Component {
|
|
|
} else {
|
|
|
console.log('[AudioManager] 音乐未在播放,无需停止');
|
|
|
}
|
|
|
- // 清除当前音乐剪辑引用
|
|
|
this.currentMusicClip = null;
|
|
|
+ this._currentMusicPath = null;
|
|
|
this.musicAudioSource.clip = null;
|
|
|
console.log('[AudioManager] 已清除音乐剪辑引用');
|
|
|
} else {
|
|
|
@@ -176,9 +180,6 @@ export class AudioManager extends Component {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 暂停背景音乐
|
|
|
- */
|
|
|
public pauseMusic() {
|
|
|
if (this.musicAudioSource && this.musicAudioSource.playing) {
|
|
|
this.musicAudioSource.pause();
|
|
|
@@ -186,69 +187,33 @@ export class AudioManager extends Component {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 恢复背景音乐
|
|
|
- */
|
|
|
public resumeMusic() {
|
|
|
- if (this.musicAudioSource && this.currentMusicClip) {
|
|
|
+ if (this.musicAudioSource && this.currentMusicClip && !this.musicAudioSource.playing) {
|
|
|
this.musicAudioSource.play();
|
|
|
console.log('[AudioManager] 恢复音乐播放');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放音效(向后兼容)
|
|
|
- * @param sfxPath 音效资源路径
|
|
|
- * @param volume 音量(可选,0-1)
|
|
|
- */
|
|
|
public playSoundEffect(sfxPath: string, volume?: number) {
|
|
|
- this.playUISound(sfxPath, volume);
|
|
|
+ this.playSound(this.uiSoundAudioSource, sfxPath, volume, this.soundEffectVolume, '音效');
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放UI音效
|
|
|
- * @param sfxPath 音效资源路径
|
|
|
- * @param volume 音量(可选,0-1)
|
|
|
- */
|
|
|
public playUISound(sfxPath: string, volume?: number) {
|
|
|
this.playSound(this.uiSoundAudioSource, sfxPath, volume, this.uiSoundVolume, 'UI音效');
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放敌人音效
|
|
|
- * @param sfxPath 音效资源路径
|
|
|
- * @param volume 音量(可选,0-1)
|
|
|
- */
|
|
|
public playEnemySound(sfxPath: string, volume?: number) {
|
|
|
this.playSound(this.enemySoundAudioSource, sfxPath, volume, this.enemySoundVolume, '敌人音效');
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放环境音效
|
|
|
- * @param sfxPath 音效资源路径
|
|
|
- * @param volume 音量(可选,0-1)
|
|
|
- */
|
|
|
public playEnvironmentSound(sfxPath: string, volume?: number) {
|
|
|
this.playSound(this.environmentSoundAudioSource, sfxPath, volume, this.environmentSoundVolume, '环境音效');
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放武器音效
|
|
|
- * @param sfxPath 音效资源路径
|
|
|
- * @param volume 音量(可选,0-1)
|
|
|
- */
|
|
|
public playWeaponSound(sfxPath: string, volume?: number) {
|
|
|
this.playSound(this.weaponSoundAudioSource, sfxPath, volume, this.weaponSoundVolume, '武器音效');
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 通用音效播放方法
|
|
|
- * @param audioSource 音频源
|
|
|
- * @param sfxPath 音效资源路径
|
|
|
- * @param volume 音量(可选,0-1)
|
|
|
- * @param baseVolume 基础音量
|
|
|
- * @param soundType 音效类型(用于日志)
|
|
|
- */
|
|
|
private async playSound(audioSource: AudioSource, sfxPath: string, volume: number | undefined, baseVolume: number, soundType: string) {
|
|
|
if (!audioSource) {
|
|
|
console.warn(`[AudioManager] ${soundType}播放器未初始化`);
|
|
|
@@ -256,286 +221,164 @@ export class AudioManager extends Component {
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- // 去掉.mp3后缀(如果存在)
|
|
|
- const cleanPath = sfxPath.endsWith('.mp3') ? sfxPath.slice(0, -4) : sfxPath;
|
|
|
-
|
|
|
- // 检查缓存中是否已有该音频
|
|
|
- let clip = this.audioClipCache.get(cleanPath);
|
|
|
-
|
|
|
- if (!clip) {
|
|
|
- // 转换路径格式:从 "data/弹球音效/xxx" 转换为 "弹球音效/xxx"
|
|
|
- const bundlePath = cleanPath.replace('data/', '');
|
|
|
- console.log(`[AudioManager] 从Bundle加载新音效: ${bundlePath}`);
|
|
|
- clip = await this.bundleLoader.loadAssetFromBundle<AudioClip>('data', bundlePath, AudioClip);
|
|
|
-
|
|
|
- // 缓存音频资源
|
|
|
- this.audioClipCache.set(cleanPath, clip);
|
|
|
- console.log(`[AudioManager] 音效已缓存: ${cleanPath}`);
|
|
|
- } else {
|
|
|
- console.log(`[AudioManager] 使用缓存音效: ${cleanPath}`);
|
|
|
- }
|
|
|
+ const cleanPath = sfxPath.replace('data/', '');
|
|
|
+ const bundlePath = cleanPath;
|
|
|
+ const clip = await this.bundleLoader.loadAssetFromBundle<AudioClip>('data', bundlePath, AudioClip);
|
|
|
|
|
|
- // 设置临时音量(如果指定)
|
|
|
- const originalVolume = audioSource.volume;
|
|
|
if (volume !== undefined) {
|
|
|
- audioSource.volume = volume * baseVolume;
|
|
|
+ audioSource.volume = Math.max(0, Math.min(1, volume));
|
|
|
+ } else {
|
|
|
+ audioSource.volume = baseVolume;
|
|
|
}
|
|
|
|
|
|
audioSource.playOneShot(clip);
|
|
|
-
|
|
|
- // 恢复原始音量
|
|
|
- if (volume !== undefined) {
|
|
|
- audioSource.volume = originalVolume;
|
|
|
- }
|
|
|
-
|
|
|
console.log(`[AudioManager] 播放${soundType}: ${cleanPath}`);
|
|
|
} catch (error) {
|
|
|
console.error(`[AudioManager] 加载${soundType}失败: ${sfxPath}`, error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置音乐音量
|
|
|
- * @param volume 音量值(0-1)
|
|
|
- */
|
|
|
public setMusicVolume(volume: number) {
|
|
|
this.musicVolume = Math.max(0, Math.min(1, volume));
|
|
|
this.updateMusicVolume();
|
|
|
//console.log(`[AudioManager] 设置音乐音量: ${this.musicVolume}`);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置音效音量(向后兼容)
|
|
|
- * @param volume 音量值(0-1)
|
|
|
- */
|
|
|
public setSoundEffectVolume(volume: number) {
|
|
|
this.soundEffectVolume = Math.max(0, Math.min(1, volume));
|
|
|
- this.setUISoundVolume(volume);
|
|
|
//console.log(`[AudioManager] 设置音效音量: ${this.soundEffectVolume}`);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置UI音效音量
|
|
|
- * @param volume 音量值(0-1)
|
|
|
- */
|
|
|
public setUISoundVolume(volume: number) {
|
|
|
this.uiSoundVolume = Math.max(0, Math.min(1, volume));
|
|
|
this.updateUISoundVolume();
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置敌人音效音量
|
|
|
- * @param volume 音量值(0-1)
|
|
|
- */
|
|
|
public setEnemySoundVolume(volume: number) {
|
|
|
this.enemySoundVolume = Math.max(0, Math.min(1, volume));
|
|
|
this.updateEnemySoundVolume();
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置环境音效音量
|
|
|
- * @param volume 音量值(0-1)
|
|
|
- */
|
|
|
public setEnvironmentSoundVolume(volume: number) {
|
|
|
this.environmentSoundVolume = Math.max(0, Math.min(1, volume));
|
|
|
this.updateEnvironmentSoundVolume();
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置武器音效音量
|
|
|
- * @param volume 音量值(0-1)
|
|
|
- */
|
|
|
public setWeaponSoundVolume(volume: number) {
|
|
|
this.weaponSoundVolume = Math.max(0, Math.min(1, volume));
|
|
|
this.updateWeaponSoundVolume();
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置所有音效音量
|
|
|
- * @param volume 音量值(0-1)
|
|
|
- */
|
|
|
public setAllSoundVolume(volume: number) {
|
|
|
+ this.setSoundEffectVolume(volume);
|
|
|
this.setUISoundVolume(volume);
|
|
|
this.setEnemySoundVolume(volume);
|
|
|
this.setEnvironmentSoundVolume(volume);
|
|
|
this.setWeaponSoundVolume(volume);
|
|
|
- this.setSoundEffectVolume(volume);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取音乐音量
|
|
|
- */
|
|
|
public getMusicVolume(): number {
|
|
|
return this.musicVolume;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取音效音量(向后兼容)
|
|
|
- */
|
|
|
public getSoundEffectVolume(): number {
|
|
|
return this.soundEffectVolume;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取UI音效音量
|
|
|
- */
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * 更新UI音效音量
|
|
|
- */
|
|
|
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;
|
|
|
+ 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())
|
|
|
- };
|
|
|
+ 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) {
|
|
|
@@ -543,9 +386,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放音效(向后兼容)
|
|
|
- */
|
|
|
static playSFX(sfxPath: string, volume?: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -553,9 +393,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放UI音效
|
|
|
- */
|
|
|
static playUISound(sfxPath: string, volume?: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -563,9 +400,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放敌人音效
|
|
|
- */
|
|
|
static playEnemySound(sfxPath: string, volume?: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -573,9 +407,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放环境音效
|
|
|
- */
|
|
|
static playEnvironmentSound(sfxPath: string, volume?: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -583,9 +414,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 播放武器音效
|
|
|
- */
|
|
|
static playWeaponSound(sfxPath: string, volume?: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -593,9 +421,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置音乐音量
|
|
|
- */
|
|
|
static setMusicVolume(volume: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -603,9 +428,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置音效音量(向后兼容)
|
|
|
- */
|
|
|
static setSFXVolume(volume: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -613,9 +435,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置UI音效音量
|
|
|
- */
|
|
|
static setUISoundVolume(volume: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -623,9 +442,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置敌人音效音量
|
|
|
- */
|
|
|
static setEnemySoundVolume(volume: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -633,9 +449,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置环境音效音量
|
|
|
- */
|
|
|
static setEnvironmentSoundVolume(volume: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -643,9 +456,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置武器音效音量
|
|
|
- */
|
|
|
static setWeaponSoundVolume(volume: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -653,9 +463,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 设置所有音效音量
|
|
|
- */
|
|
|
static setAllSoundVolume(volume: number) {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -663,21 +470,13 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 停止音乐
|
|
|
- */
|
|
|
static stopMusic() {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
manager.stopMusic();
|
|
|
- } else {
|
|
|
- console.warn('[Audio] AudioManager实例未找到,无法停止音乐');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 暂停音乐
|
|
|
- */
|
|
|
static pauseMusic() {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -685,9 +484,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 恢复音乐
|
|
|
- */
|
|
|
static resumeMusic() {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -695,9 +491,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 静音所有音频
|
|
|
- */
|
|
|
static muteAll() {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -705,9 +498,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 恢复所有音频
|
|
|
- */
|
|
|
static unmuteAll() {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -715,9 +505,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 静音所有音效(保留音乐)
|
|
|
- */
|
|
|
static muteAllSounds() {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -725,9 +512,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 恢复所有音效(保留音乐)
|
|
|
- */
|
|
|
static unmuteAllSounds() {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -735,9 +519,6 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 清理音频缓存
|
|
|
- */
|
|
|
static clearAudioCache() {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
@@ -745,14 +526,93 @@ export class Audio {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取缓存信息
|
|
|
- */
|
|
|
static getCacheInfo(): {count: number, paths: string[]} {
|
|
|
const manager = AudioManager.getInstance();
|
|
|
if (manager) {
|
|
|
return manager.getCacheInfo();
|
|
|
}
|
|
|
- return {count: 0, paths: []};
|
|
|
+ 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);
|
|
|
+ }
|
|
|
}
|
|
|
}
|