目录

  • 前言
  •       一、新建AudioMgr.ts
  •       二、提问AI
  •       三、优化修改
  •       四、最终版本
  •       五、总结
  • 我们接上一篇【资源模块】,现在继续使用AI帮我们写一个【音频管理器】。

    前言

    音频管理器,主要提供音乐和音效的播放、暂停、复位功能。
    我们对ccc现有的音频以及音效的播放、暂停、复位功能进行了简单封装,配合【资源管理器】使用。

    小白必读:音频管理器的用处?
    :音频播放是游戏中一个常用的功能,背景长音乐的循环播放;游戏中各种音频特效单次播放,如:攻击、受伤、死亡、技能释放等等。有些复杂场景下可能还需要用到音效池,提供资源的复用性,在这里我们只封装基础的音频功能。

    一、新建AudioMgr.ts

    我们继续在Core分包目录下的Scripts\Managers目录新建AudioMgr.ts
    新建AudioMgr.ts

    二、提问AI

    注意:现在我们需要结合ResMgr.ts的资源加载读取,来写代码。
    1、点击提问区的 + 号
    2、选择ResMgr.ts
    3、输入问题

    结合ResMgr.ts,用ccc 3.8.x 内置 API 帮我们写一个AudioMgr.ts音频管理器,实现:
    1、音乐播放、暂停、重播
    2、音效播放
    3、设置音乐音量
    4、设置音效音量
    5、加一个音效组件池,数量初始为5个,用于支持同时播放多个音效
    6、参考ResMgr单例方式,并导出

    提问AI1
    提问AI2

    三、优化修改

    从上2图中我们看到有几个地方需要修改:
    1、我们是独立模块,需要去掉Component继承
    2、在 constructor 中 使用 director.once的Director.EVENT_AFTER_SCENE_LAUNCH事件执行init
    3、在init中添加组件到场景根节点,但在编辑器模式下不执行
    4、直接在新场景下添加一个__AudioMgr__的新节点,用于保存音频组件
    5、播放音乐加2个参数、是否循环,音量,播放音效也加一个音量参数
    6、getAvailableEffectSource中会判断音效组件是否播放状态,如果是会返回null,这样不符合我们的需求,可以采用动态增加音效池的方式解决。

    优化修改

    四、最终版本

    import { Node, AudioSource, AudioClip, director, Director, find } from 'cc';
    import { resMgr } from './ResMgr';
    import { EDITOR } from 'cc/env';
    
    /**
     * 音频管理器
     * 用于音乐和音效的播放、暂停、重播和音量设置
     */
    class AudioMgr {
        private musicSource: AudioSource | null = null;
        private effectSources: AudioSource[] = [];
        private effectVolume: number = 1;
        private musicVolume: number = 1;
        private audioMgrNode: Node | null = null;
    
        // 私有构造函数,防止外部实例化
        private constructor() {
            director.once(Director.EVENT_AFTER_SCENE_LAUNCH, this.init, this);
        }
    
        /** 单例实例 */
        public static readonly instance: AudioMgr = new AudioMgr();
    
        private init() {
            if (EDITOR) return; // 在编辑器模式下不执行
    
            // 初始化音效组件池并添加到场景根节点
            this.audioMgrNode = new Node('__AudioMgr__');
            director.getScene().addChild(this.audioMgrNode);
    
            for (let i = 0; i < 5; i++) {
                const effectSource = this.audioMgrNode.addComponent(AudioSource);
                this.effectSources.push(effectSource);
            }
            this.musicSource = this.audioMgrNode.addComponent(AudioSource);
        }
    
        /**
         * 播放音乐
         * @param {string} bundleName - 分包名称
         * @param {string} url - 音乐资源路径
         * @param {boolean} loop - 是否循环播放
         * @param {number} volume - 音量值(0-1)
         */
        public playMusic(bundleName: string, url: string, loop: boolean = true, volume: number = 1) {
            resMgr.loadAsset(bundleName, url, AudioClip, (err, clip) => {
                if (err) {
                    console.error(`加载音乐失败: ${url}`, err);
                    return;
                }
                if (this.musicSource) {
                    this.musicSource.clip = clip;
                    this.musicSource.loop = loop;
                    this.musicSource.volume = volume * this.musicVolume;
                    this.musicSource.play();
                }
            });
        }
    
        /**
         * 暂停音乐
         */
        public pauseMusic() {
            if (this.musicSource) {
                this.musicSource.pause();
            }
        }
    
        /**
         * 重播音乐
         */
        public resumeMusic() {
            if (this.musicSource) {
                this.musicSource.play();
            }
        }
    
        /**
         * 播放音效
         * @param {string} bundleName - 分包名称
         * @param {string} url - 音效资源路径
         * @param {number} volume - 音量值(0-1)
         */
        public playEffect(bundleName: string, url: string, volume: number = 1) {
            resMgr.loadAsset(bundleName, url, AudioClip, (err, clip) => {
                if (err) {
                    console.error(`加载音效失败: ${url}`, err);
                    return;
                }
                const effectSource = this.getAvailableEffectSource();
                effectSource.clip = clip;
                effectSource.volume = volume * this.effectVolume;
                effectSource.play();
            });
        }
    
        /**
         * 设置音乐音量
         * @param {number} volume - 音量值(0-1)
         */
        public setMusicVolume(volume: number) {
            this.musicVolume = volume;
            if (this.musicSource) {
                this.musicSource.volume = volume * this.musicVolume;
            }
        }
    
        /**
         * 设置音效音量
         * @param {number} volume - 音量值(0-1)
         */
        public setEffectVolume(volume: number) {
            this.effectVolume = volume;
            this.effectSources.forEach(source => {
                source.volume = volume;
            });
        }
    
        /**
         * 获取可用的音效组件
         * @returns {AudioSource} 可用的音效组件
         */
        private getAvailableEffectSource(): AudioSource {
            for (const source of this.effectSources) {
                if (!source.playing) {
                    return source;
                }
            }
            if (this.audioMgrNode) {
                const newSource = this.audioMgrNode.addComponent(AudioSource);
                this.effectSources.push(newSource);
                return newSource;
            } else {
                throw new Error('未找到音频管理节点,无法添加新的音效组件');
            }
        }
    }
    
    /** 导出实例 */
    export const audioMgr = AudioMgr.instance;

    五、总结

    由于游戏中的特效音频会有很多,我们采用音效池+动态添加的方式,可以满足日常的游戏需求。

    作者:ccs2d.com  创建时间:2024-09-28 12:00
    最后编辑:ccs2d.com  更新时间:2024-10-06 09:43
    上一篇:
    下一篇: