react-three实现3D游戏(2)——空间音频

687 阅读4分钟

概述

空间音频,故名思意就是指放置在3d空间场景中的音源,拥有现实中立体声音的特性。 它远小近大,且会被墙壁等障碍物所阻隔。

对于一个立体的游戏世界来说是十分重要的元素,试想在吃鸡类游戏中不能听到逐渐靠近的脚步声,游戏的临场感和现实感就大打折扣!

这是threejs官网的一个示例:link,它很好的表现了空间音频的特性。

image.png

核心代码:

const listener = new THREE.AudioListener();
camera.add( listener );

const audioElement = document.getElementById( 'music' );
audioElement.play();
const positionalAudio = new THREE.PositionalAudio( listener );
positionalAudio.setMediaElementSource( audioElement );
positionalAudio.setRefDistance( 1 );
positionalAudio.setDirectionalCone( 180, 230, 0.1 );

你可以看到,它创建了一个“耳朵”(listener)并绑定到了相机上,并将audio元素加载的音频资源加载到positionalAudio。这样当音频播放时,相机这个听众就会因为方位和远近的不同听到不同的音乐效果。也就是所谓的立体音效。

react-three中使用空间音频

react-three/drei 工具库中封装了 PositionalAudio组件专门用来在场景中放置空间音频。它只是对原生three中的PositionalAudio的封装,它支持所有原生的api。
它做了以下事情:

  1. PositionalAudio被创建后就自动将listener加入到camera了,你不需要显示的创建AudioListener并加入camera中。
  2. 自动创建audio元素加载并播放音频文件,你不需要显示的创建。

你可以直接把PositionalAudio 放到某个网格或模型中,它会与其在空间上绑定。

 <mesh position={[42, -4, -15]}>
    <PositionalAudio
      url="/sound.mp3"
      distance={1}
      loop
      {...props} // All THREE.PositionalAudio props are valid
    />
   </mesh>

这是react-three/drei的官方示例:link

image.png

项目中添加音乐与音效

获取资源的网站

🔗 获取音效

🔗 获取纯音乐

添加全局音频

所谓全局音频,是指不需要空间效果的音频,也就是我们在网页中正常看到的音频。它不随着人物的空间变化而变化,但可以在设置中更改音量的大小,如背景音乐等。
这里我们有2个选择:

  1. 使用空间音频,将此音频绑定在人物身上,类似自带音响的效果。
  2. 使用正常的audio标签,在网页上播放。

大家可以自行选择,这里我为了一致性使用空间音频。 从网站上寻找到合适的bgm和脚本音效,放入public目录下的audios文件夹中。 修改代码如下:

...
const AUDIOS = {
  song: '/audios/song.mp3',
  steps: '/audios/steps.mp3',
}
  const playRef = useRef<THREE.Object3D | undefined>(null); // 玩家模型
  const bgmRef = useRef<THREE.PositionalAudio>(null); // bgm音频
  const stepsRef = useRef<THREE.PositionalAudio>(null); // 脚步音频

  // 将音频监听器添加到玩家身上
  useEffect(() => {
    if (!playRef?.current || !bgmRef?.current) return
    playRef.current.add(bgmRef.current.listener)
  }, [])

  // 当播放walk动画时播放脚步声
  useEffect(() => {
    if (curAnimation == animationSet.walk) {
      stepsRef.current?.play();
    } else {
      stepsRef.current?.stop();
    }
  }, [animationSet.walk, curAnimation])
  
...
<Ecctrl>
    <primitive ref={playRef} castShadow object={scene} position={[0, -0.8, 0]} />
...
   <PositionalAudio ref={bgmRef} url={AUDIOS.song} distance={1} autoplay loop />
   <PositionalAudio ref={stepsRef} url={AUDIOS.steps} distance={1} loop />
</Ecctrl>

空间音频失效问题

正常的PositionAudio,会将listener自动添加到camera。随着camera方位的变化听到的音效随之变化。

可Ecctrl库将原来的camera位置始终锁定在[0,0,0]的位置。这导致空间音频功能失效。

我在github上我提出这个issue后。 作者提供的解决方案是重新添加listener,正如上面代码中我将listener添加到了玩家身上。这样随着玩家的移动,空间音频再次生效。

image.png

添加环境音效

大家可以自行从网站上寻找到合适的音效,这里我共找了2种环境音效:沙滩边的海浪声、森林的鸟叫声。

将音频资源放入根目录public文件夹下的audios文件夹中。 在models文件夹下新建ambiance.tsx文件,用于存放所有的环境音。

附具体代码如下:

import { PositionalAudio } from "@react-three/drei";

const AUDIOS = {
  beach: '/audios/beach.mp3',
  forest: '/audios/forest.mp3',
}

/**
  * 环境音
  * 参数:
  * debug:显示音源的覆盖范围 
  */
export default function Ambiance({ debug = false }) {
  return (
    <group>
      {/* 沙滩海浪声 */}
      <mesh position={[61, -4, 70]}>
        {debug && (
          <>
            <sphereGeometry args={[30, 64, 64]} />
            <meshStandardMaterial transparent opacity={0.5} />
          </>
        )}
        <PositionalAudio url={AUDIOS.beach} distance={30} autoplay loop />
      </mesh>
      <mesh position={[42, -4, -15]}>
        {debug && (
          <>
            <sphereGeometry args={[10, 64, 64]} />
            <meshStandardMaterial transparent opacity={0.5} />
          </>
        )}
        <PositionalAudio url={AUDIOS.beach} distance={10} autoplay loop />
      </mesh>
      <mesh position={[0, 0, -100]}>
        {debug && (
          <>
            <sphereGeometry args={[5, 64, 64]} />
            <meshStandardMaterial transparent opacity={0.5} />
          </>
        )}
        <PositionalAudio url={AUDIOS.beach} distance={5} autoplay loop />
      </mesh>
      {/* 森林鸟叫声 */}
      <mesh position={[0, 5, 3]}>
        {debug && (
          <>
            <sphereGeometry args={[20, 64, 64]} />
            <meshStandardMaterial transparent opacity={0.5} />
          </>
        )}
        <PositionalAudio url={AUDIOS.forest} distance={2} autoplay loop />
      </mesh>
    </group>
  );
}

基本使用方式与示例无异。这里我多添加了一个球体网格,并设为透明度0.5,用来查看音源的位置和影响范围,方便调试其位置。

添加了debug参数用来控制球体网格的可见。 你可以更改meshStandardMaterial的color属性,让不同的音源有不同的颜色,更方便调试。

你也可以尝试使用原生three提供的 PositionalAudioHelper 用来辅助调试空间音频。

组件挂载

在models下的index.tsx中挂载组件

import Ambiance from './ambiance'
...
 <Suspense fallback={null}>
     <Lights />
     <Ambiance debug />
...

结语

本次完成

  • 添加音乐、音效

最终效果

添加音效后的效果图,位于中心位置的是森林鸟叫声,另外2个是海浪声。

image.png

项目地址

🔗 源码地址

🔗 react-three实现人物控制——进阶
🔗 react-three实现人物控制——基础

结语

后续我将会添加云朵、水流等。并且考虑添加一些可交互的物品和人物。 如果大家有什么想法的话,欢迎提出修改意见,或提出你想添加的东西。

每一篇文章都是在写了众多代码后写就的。希望诸位多多点赞和收藏,让我有更多写下去的动力!

如果你有什么疑问,欢迎在评论区提出,我们可以互相沟通交流。