小白也能写框架之【六、音频管理器】

接上篇:
小白也能写框架之【五、资源管理器】

一、上代码:

assets\Core\Scripts\Managers\AudioMgr.ts

import { Node, AudioSource, Director, director, AudioClip } from "cc";

import { EDITOR } from "cc/env";

import { dataMgr } from "./DataMgr";

import { resMgr } from "./ResMgr";

/**

 * 音频管理器

 * 提供音乐和音效的播放、暂停、复位功能。

 */

class AudioMgr {

    /** 音乐播放组件 */

    private musicSource: AudioSource;

    /** 音效播放组件池 */

    private effectSourcePool: AudioSource[] = [];

    /** 音乐音量 */

    private musicVolume: number;

    /** 音效音量 */

    private effectVolume: number;

   

    /** 当前音效组件池索引 */

    private effectSourceIndex: number = 0;

    /** 私有构造函数,确保外部无法直接通过new创建实例 */

    private constructor() {

        if (!EDITOR) {

            director.once(Director.EVENT_AFTER_SCENE_LAUNCH, this.init, this);

        }

    }

    /** 单例实例 */

    public static readonly instance: AudioMgr = new AudioMgr();

    /** 初始化 */

    private init(): void {

        this.musicVolume = dataMgr.getNumber("musicVolume") ?? 0.5;

        this.effectVolume = dataMgr.getNumber("effectVolume") ?? 0.5;

        /** 创建常驻节点 */

        const audioMgrNode = new Node("__AudioMgr__");

        director.getScene().addChild(audioMgrNode);

        director.addPersistRootNode(audioMgrNode);

        this.musicSource = this.createAudioSource(audioMgrNode, this.musicVolume);

        for (let i = 0; i < 5; i++) {

            this.effectSourcePool.push(this.createAudioSource(audioMgrNode, this.effectVolume));

        }

    }

    /**

     * 创建音频源

     * @param node 节点

     * @param volume 音量

     * @returns AudioSource 音频源组件

     */

    private createAudioSource(node: Node, volume: number): AudioSource {

        const source = node.addComponent(AudioSource);

        source.loop = false;

        source.playOnAwake = false;

        source.volume = volume;

        return source;

    }

    /**

     * 播放音乐

     * @param path 音乐路径

     * @param loop 是否循环播放,默认为'true'

     * @param volume 音量大小,默认为'1.0'

     * @returns Promise<void> 播放完成后的Promise

     */

    public async playMusic(path: string, loop: boolean = true, volume: number = 1.0): Promise<void> {

        const clip = await resMgr.loadRes<AudioClip>(path);

        this.musicSource.stop();

        this.musicSource.clip = clip;

        this.musicSource.loop = loop;

        this.musicSource.volume = this.musicVolume * volume;

        this.musicSource.play();

    }

    /** 重播当前音乐 */

    public replayMusic(): void {

        this.musicSource.stop();

        this.musicSource.play();

    }

    /** 暂停当前播放的音乐 */

    public pauseMusic(): void {

        this.musicSource.pause();

    }

    /** 停止当前播放的音乐 */

    public stopMusic(): void {

        this.musicSource.stop();

    }

    /**

     * 播放音效

     * @param path 音效路径

     * @param volume 音量大小,默认为'1.0'

     * @returns Promise<void> 播放完成后的Promise

     */

    public async playEffect(path: string, volume: number = 1.0): Promise<void> {

        const clip = await resMgr.loadRes<AudioClip>(path);

        const source = this.getNextEffectSource();

        source.playOneShot(clip, this.effectVolume * volume);

    }

    /**

     * 获取下一个音效组件

     * @returns AudioSource 下一个音效组件

     */

    private getNextEffectSource(): AudioSource {

        const source = this.effectSourcePool[this.effectSourceIndex];

        this.effectSourceIndex = (this.effectSourceIndex + 1) % this.effectSourcePool.length;

        return source;

    }

    /**

     * 设置音乐音量

     * @param volume 音量大小,范围为 0.0 到 1.0

     */

    public setMusicVolume(volume: number): void {

        this.musicVolume = volume;

        this.musicSource.volume = volume;

        dataMgr.setData("musicVolume", volume);

    }

    /**

     * 获取当前音乐音量

     * @returns 当前音乐音量,范围为 0.0 到 1.0

     */

    public getMusicVolume(): number {

        return this.musicVolume;

    }

    /**

     * 设置音效音量

     * @param volume 音量大小,范围为 0.0 到 1.0

     */

    public setEffectVolume(volume: number): void {

        this.effectVolume = volume;

        this.effectSourcePool.forEach((source) => (source.volume = volume));

        dataMgr.setData("effectVolume", volume);

    }

    /**

     * 获取当前音效音量

     * @returns 当前音效音量,范围为 0.0 到 1.0

     */

    public getEffectVolume(): number {

        return this.effectVolume;

    }

}

/** 音频管理器实例 */

export const audioMgr = AudioMgr.instance;

二、代码分析
1、全局只有一个音乐播放组件,仅用于播放背景音乐,带音量记忆功能
2、全局直接初始5个音效播放组件,可以播放音乐,也可以播放音效,带音量记忆功能
3、为什么是5个呢,我就是简单粗暴用于同时播放特效声音,
比如:肉鸽游戏,有发射子弹的声音,同时可能产生怪物受伤的声音,同时还有金币掉落的声音

**

4、按理cocos的音效播放本来就是异步的,下一个音效不会覆盖当前音效。但是实际测试中同时只能播放一个音效,不知道是不是我自己的问题,索性干到5个简单粗暴。

**

5、如果有懂哥请指点一下,谢谢。

三、全局映射

四、示例代码

对于新手请记住:

1、分包资源路径一般是从分包名开始,然后到具体的文件名结束,不包括后缀

如实际路径为:
“分包名称/资源目录/MP3/点击.mp3”

但是cocos的资源加载路径为:
“分包名称/资源目录/MP3/点击”

2、当然有个B资源很特殊,就是用的最多的精灵资源,需要加.spriteFrame后缀,很操蛋

如实际路径为:
“分包名称/资源目录/图片/背景.png”

但是cocos的资源加载路径为:
“分包名称/资源目录/图片/背景.spriteFrame”

楼主大好人 简易框架确实是刚入门的小白福音了

我是在b站看飞羽视频自学的 我觉得教学视频或者大纲就应该是先语言基础 然后编辑器基础再到基础组件的使用 最后小白也需要封装一个自己的简易框架方便后续学习和开发

这里还是得吐槽一下,
引擎不是一个人开发的,是很多技术参与的
然后个别技术的脑袋长的也不一样,你会发现cocosIDE有些隐藏的反人类的设计和功能

知道什么是反人类么?就是 明明1+1=2 就可以了

他要来个

1+1-(2+2)x 0 =2

最G8操蛋的是提了N年没人去修改优化,成为牛皮藓一样根深蒂固的长在那里

虽然不碍事,但是就很操蛋

为什么要封装自己的框架:

1、自用舒服,熟悉

2、算是对自己学习的一个总结,能封装一个简单易用的框架,也代表自己能进行一些小游戏的开发了

3、不用额外学习别人的框架API,减少学习成本

4、还是那句话,适合自己的才是最好的

完全赞同 :grimacing:

学习cocos其实很快的:

一般的开发者基本都会一门语言,然后语言之间又有互通关系

所以TS熟悉下 数据类型、基本的语法、基本2天就了解了

不用深究TS,我们平时用不到那么多,我是开着GPT一边问一边实际操作,不懂就问

你会发现TS不用学就会了

然后COCOS,不懂得你可以查官网文档,另外GPT也可以问

我自学90%知识都来自GPT

最后就是阅读别人的框架代码,结合GPT改造为自己想要的,完后就成了

关于AudioSource管理可以做个对象池,如果有播完的就拿来复用,不够就新增,这样就不用纠结设置5个还是几个了。

是的,之前也是这么弄的

然后估计是自己的代码问题,像捕鱼这种游戏短时间有可能同时播放很多很多音效文件

采用音效池经常界面卡顿,后来改为5个就很丝滑了,再也不卡顿了

播放音频不需要常驻节点

那每次 都要新建 播放对象?

请指教下为什么不需要常驻啊,谢谢

目前我是大厅+子游戏,就是单空壳场景+多预制体的开发模式

我是把播放组件节点常驻在单空壳场景下,所有音频文件播放都是使用这个常驻组件播放的,目前没什么问题

但是不知道大佬的建议是什么?我好优化一下

因为音频节点只要创建了,就能响。。。,不用放在场景里

哎呀我C忘记了

之前我有个加载场景在框架初始之前,要播放背景音乐,然后这个加载场景还反复使用

顺便就把播放节点常驻了后来 框架也直接复制的代码,忘记去掉常驻了

谢谢提醒

弄一个全局的AudioSource对象,比如叫player
播音乐:
player.clip = 音乐AudioClip;
player.play();
播音效:
player.playOneShot(音效AudioClip,1);
也不存在数量限制,调用一次就播一次。
不过我是搞了2个全局AudioSource,音乐和音效分开,方便单独调音量

为什么不早几年发?你知道我这几年是怎么过来的吗?一步一步的踩过多少坑,百度了多少次,加了多少班才掌握了你发的这些

大佬有没有遇到同时播放 5个短音效文件的情况下

cocos始终只有一个音效的声音

你这~

嘿嘿我有罪,你当替我赎罪了

同时播没问题,5个一起播倒没测过,应该是声音叠一起不好分辨吧,你自己再试下

我用cocos的playOneShot 计次循环5次播放不同的音效文件

但是只会有一个声音

按理playOneShot属于异步的,应该同时能出现 不同的音效啊