文档中心 > 开发指南

淘宝Cocos小游戏音频最佳实践

更新时间:2025/03/18 访问次数:1721

一、背景

Cocos2.X开发的小游戏,在淘宝环境运行时使用cc.audioEngine播放音频和音效,无法正常使用uncache/uncacheAll销毁音频实例。依赖音效较多时,会有较大概率连续报错(见下图),造成游戏卡顿的客诉。

二、推荐方案

使用以下《音频管理器》章节的代码片段作为基础,自行实现单例音频管理器,替换AudioEngine使用。

1. 音频管理器

const interval = 180;
const preload = 5;

export class SoundMgr {
  /**
     * 音乐和单次音效播放
     */
  private _audioComp: cc.AudioSource = new cc.AudioSource();

  /**
     * 循环音效播放
     */
  private _audioPool: cc.AudioSource[] = [];

  private _clipTime: Map<cc.AudioClip, number> = new Map()

  /**
      用于挂载的组件
    */
  private _soundNode = null;

  constructor() {
    this._soundNode = new cc.Node();
    cc.game.addPersistRootNode(this._soundNode);
    for (let i = 0; i < preload; i++) {
      const audio = this._soundNode.addComponent(cc.AudioSource);
      this._audioPool.push(audio);
    }

  }

  public play(audioClip: cc.AudioClip, isLoop: boolean = true) {
    let clip = audioClip;
    if (!clip) return;
    this._audioComp.clip = clip;
    this._audioComp.loop = isLoop;
    this._audioComp.play();
  }

  public stop() {
    this._audioComp.stop();
  }
  public setVolume(volume: number) {
    this._audioComp.volume = volume;
  }
  private getAudio(): cc.AudioSource {
    let audio: cc.AudioSource
    if (this._audioPool.length === 0) {
      audio = this._soundNode.addComponent(cc.AudioSource);
    } else {
      audio = this._audioPool.pop();
    }
    return audio;
  }
  private putAudio(audio: cc.AudioSource) {
    // @ts-ignore
    audio.clip.src = null;
    this._audioPool.push(audio);
  }
  /**
     * @en : play effect once 
     * @zh : 播放一次特效
     * @param {string} audioClip
     * @param {*} volume
     */
  public playEffect(audioClip: cc.AudioClip, volume = 0.5) {
    let clip = audioClip;

    if (!clip) return;
    const lastTime = this._clipTime.get(clip) || 0;
    const currentTime = cc.director.getTotalTime();
    /* if interval is too low, return it for performance */
    if ((currentTime - lastTime) < interval) {
      return;
    }
    this._clipTime.set(clip, currentTime);
    const audio = this.getAudio();
    audio.clip = clip;
    audio.play();
    const audioDuration = audio.getDuration();
    setTimeout(() => {
      if (this._audioPool.length >= preload) {
        audio.destroy();
      } else {
        this.putAudio(audio);
      }
    }, audioDuration * 1000)
  }
}

const SOUND = new SoundMgr();
export default SOUND;

2. 使用示例

import SOUND from "../frame/SoundMgr";
import RES from "../frame/ResMgr";

const { ccclass, property } = cc._decorator;

@ccclass
  export default class play extends cc.Component {
    @property(cc.Slider)
    volume: cc.Slider = null;

    protected onEnable(): void {
      this.volume.node.on('slide', this.callback, this);


    }
    protected onDisable(): void {
      this.volume.node.off('slide', this.callback, this);

    }

    callback(slider:cc.Slider) {
      SOUND.setVolume(slider.progress);
    }

    playBgm() {
      const clip = RES.getClip("bgm");
      SOUND.play(clip,0.65);
      this.volume.progress = 0.65;
    }

    stopBgm() {
      SOUND.stop();
    }

    playEffect() {
      const clip = RES.getClip("chick");
      SOUND.playEffect(clip);
    }

    playEffect1() {
      const clip = RES.getClip("btn");
      SOUND.playEffect(clip);
    }

  }

3. 体验Demo


FAQ

关于此文档暂时还没有FAQ
返回
顶部