V7投稿|在Cocos中使用3d空间音效(附完整代码文件)

image

背景

在3d游戏中,合理搭配音效元素,可以显著提升游戏沉浸感。利用双通道管线和自定义音频混合创建具有3d空间效果的音频,已经被用于大部分的3d游戏场景,尤其是fps类游戏,开发者在场景中添加具有3d效果的脚步声、枪声,玩家即可借助专业的耳机设备,可以通过3d空间音效做到“听声辨位”。从而丰富游戏的玩法。

本篇教程用于引导大家如何在cocos中使用浏览器自带AudioContext api实现3d音效。官方文档参见: Web audio spatialization basics - Web APIs | MDN (mozilla.org)

AudioContext (上下文)

音频主体结构的上下文,表示由连接在一起的音频模块构建的音频处理图,其实类似于我们在游戏开发过程中编辑器自带的那种“节点编辑器”或者叫“路由图”,比如blender的着色器编辑器,cocos creater的动画状态机编辑器。只是这里没有可视化。在代码中,我们可以通过aNode.connect(bNode)的方式来连接所有的音效节点,来达成类似于混合和编排的效果。(如图所示)

image
创建AudioContext的代码:

this.ac = new AudioContext();

Source(音频源)

在cocos中,我们推荐以buffer的形式获取音频源,因为这不会创建html元素,并且我们可以利用cocos的audioClip属性,快速拿到我们资产库中的音频源audiobuffer数据;

代码:

@property({
     type: AudioClip,
     displayName: "音频源",
 })
audioClip: AudioClip;
//...略
this.source = this.ac.createBufferSource();
this.source.buffer = this.audioClip._player._player._audioBuffer;
this.source.start();

AudioListener(收听者)

类似于我们场景中的相机,排除一些特殊情况(如renderTexture、shader、自定义屏幕后效),大部分情况下我们的场景中仅有一台激活的相机,listener也是如此。

this.listener = this.ac.listener;

和相机一样,我们可以对listener设置位置和方向:(请注意,这里传给setOrientation的是cocos节点的forwardup向量);一般情况下,这里的listenerNode可以传相机的节点。对应的就是以相机的视角为listener的坐标计算空间音频的声道和衰减模型;

  @property({
    type: Node,
    displayName: "收听者",
  })
  listenerNode: Node;
  //...
  this.listener.setOrientation(
      this.listenerNode.forward.x || 0,
      this.listenerNode.forward.y || 0,
      this.listenerNode.forward.z || 0,
      this.listenerNode.up.x || 0,
      this.listenerNode.up.y || 1,
      this.listenerNode.up.z || 0
   );

  this.listener.setPosition(
      this.listenerNode.worldPosition.x || 0,
      this.listenerNode.worldPosition.y || 0,
      this.listenerNode.worldPosition.z || 0
  );

PannerNode (发声节点)

listener对应,panner 对应“发声者”在3d空间中的对象,比如一个喇叭、脚步、枪声。常用的参数我们可以设置panner的位置(position)、最大距离( maxDistance )等等内容,代码如下:

@property({
 type: Node,
 displayName: "发声者",
})
pannerNode: Node;

//...略

this.panner = this.ac.createPanner();
this.panner.panningModel = "equalpower"; // 音频空间化算法模型
this.panner.distanceModel = "linear"; // 远离时的音量衰减算法
this.panner.maxDistance = this.maxDistance; // 最大距离
this.panner.refDistance = 5; // 开始衰减的参考距离
this.panner.rolloffFactor = 3; // 衰减速度
this.panner.coneInnerAngle = 360; // 声音360度扩散
this.panner.orientationX.value = 1; // 声源朝向x分量
this.panner.orientationY.value = 0;
this.panner.orientationZ.value = 0;
this.panner.setPosition(
  this.pannerNode.worldPosition.x,
  this.pannerNode.worldPosition.y,
  this.pannerNode.worldPosition.z
);

GainNode(增益节点)

还记得文章前面说的吗?AudioContext是一个类似于“路由图”的集合,那这里我们就增加一个GainNode(增益节点),增益是一个无单位的值,会对所有输入声道的音频进行相应的增加(相乘)。此处我们用于修改整体音频的音量大小。gain修改的效果会影响到panner的结果,举个例子:比如收音机的音量,如果音量很小,那么离太远就听不清了,反之音量很大,那离得很远也能听到。

代码如下:

this.gainNode = this.ac.createGain();
//修改音量
this.gainNode.gain.value = 0.2;

connect(连接)

把我们创建的节点连接起来,用connect方法;(还记得吗?路由图)

this.gainNode.connect(this.ac.destination);
this.gainNode.connect(this.panner);
this.source.connect(this.panner);
this.panner.connect(this.gainNode);

onDestroy的时候

由于我们是手动创建的音频上下文节点,所以cocos并不会在销毁组件的时候自动释放这些音频实例,这可能导致我们已经切换场景了,但上一个场景的音频还在继续播放,要解决这个问题,我们只需要在组件销毁的生命周期钩子函数中手动释放我们创建的音频实例即可;

代码如下:

onDestroy() {
  try {
    this.gainNode.disconnect(this.ac.destination);
    this.gainNode.disconnect(this.panner);

    this.source.disconnect(this.panner);
    this.panner.disconnect(this.gainNode);

    this.source.stop();
    this.ac.close();
  } catch (e) {
    console.log(e);
  }
}

大功告成

以上就是在cocos中应用3d音效的全部内容,欢迎各位大佬查漏补缺,新人一枚,如文章中存在错误,请在本帖中回复。希望这篇教程能对您的工作提供帮助。

源码附件和参数:

audio3DComponent.zip (1.3 KB)
image

题外话

1.这里有一个我在3d空间音频在坦克对战(3D)项目中的应用,这里是视频效果的传送门: Cocos坦克-引擎3D空间音效_网络游戏热门视频 (bilibili.com)

2.如对物理载具的绑定感兴趣,我也做了一个ammojs物理载具绑定系统,原价40元,现价2元: Cocos Store

5赞

要是原生端也支持了就吊了

不错,物理载具能支持bullet吗?还有这个3D音效支持小游戏应用不

物理载具暂不支持bullet库,接的是一个第三方引擎ammo,是bullet的另一个分支,集成度和cocos的bullet稍有差别,但使用方式类似。3D音效目前没有在小程序测试过。我晚点试一下。

要是原生端也支持了就好了

小游戏平台里只有微信的AudioContext有PanelNode这个API,支持3D音效,其它平台如抖音、OV、小米没有PanelNode,只能通过更改2D音效的大小来模拟远近,但区分不了左右。原生平台如安卓,苹果,鸿蒙和windows都支持webview,可以把3D音效放到webview里处理。如果是H5平台那就完美支持web audio api,如facebook,华为快游戏。经实践,微信小游戏、原生、H5都可以使用3D音效。其它平台暂不支持。下面是我以前发的关于3D音效的帖子: 基于Web Audio API实现的3D音效 - Creator 3.x - Cocos中文社区

1赞

测试了一下,对于微信小游戏,需要做一些改造来支持
首先是上下文的创建方式要区分一下:

   if (window["wx"]) {
      this.ac = new window["wx"].createWebAudioContext();
   } else {
      this.ac = new AudioContext();
   }

其次audioClip组件在微信小游戏模式下是拿不到audioBuffer的(或许有办法,但我没找到),所以需要通过加载文件的方式来支持。

加载项目音频文件代码:

 loadAudioFile(audioCtx, url): any {
    const fs = window["wx"].getFileSystemManager();
    let file = fs.readFileSync(url);
    return new Promise((resolve, reject) => {
      audioCtx.decodeAudioData(
        file,
        (buffer) => {
          resolve(buffer);
        },
        (err) => {
          console.error("decodeAudioData fail", err);
          reject();
        }
      );
    });
  }

加载远程文件音频代码

loadAudio(audioCtx, url): any {
    console.log(url);
    return new Promise((resolve, reject) => {
      wx.request({
        url,
        responseType: "arraybuffer",
        success: (res) => {
          console.log("res.data", res.data);
          audioCtx.decodeAudioData(
            res.data,
            (buffer) => {
              resolve(buffer);
            },
            (err) => {
              console.error("decodeAudioData fail", err);
              reject();
            }
          );
        },
        fail: (res) => {
          console.error("request fail", res);
          reject();
        },
      });
    });
  }

但是请注意,我没有在真机调试环境成功播放音频,仅在微信的开发工具里面成功播放,具体原因我不清楚。对于小游戏环境,我觉得采用楼上的方案就不错,判断一下环境,然后在内部兼容一下,通过listener和gain的距离和设定的maxDistance调整一下声音的大小好了。缺点就是只可以模拟远近,不能模拟左右。

2赞