1. 引言
在 3D 游戏或 2.5D 游戏中,音频的空间化 能极大增强沉浸感。玩家可以根据声音的方向与远近判断声源位置,例如:敌人脚步声从左后方靠近、远处炮火声逐渐变大。
Cocos2d 系列引擎原生支持 AudioEngine(Creator)与 experimental::AudioEngine(Cocos2d-x),结合 3D 音效距离衰减模型(如线性、对数、指数衰减)可实现真实的空间音频效果。
本方案提供一套 通用的 3D 音频空间化框架,实现:
-
-
-
-
-
跨平台兼容(Web / iOS / Android / PC)
2. 技术背景
2.1 相关模块
|
|
|
|
AudioEngine(Creator) / experimental::AudioEngine(Cocos2d-x)
|
|
|
节点世界坐标 (node.worldPosition)
|
|
|
|
|
|
|
|
|
|
|
2.2 3D 音效衰减模型
-
volume = maxVolume * (1 - distance / maxDistance)(distance ≤ maxDistance)
-
volume = maxVolume * (1 - log(distance + 1) / log(maxDistance + 1))
-
volume = maxVolume * exp(-distance / attenuationFactor)
通常还会加入 最小距离(minDistance):在此距离内音量保持最大,不会无限增大。
3. 应用使用场景
4. 核心原理与流程图
4.1 原理概述
-
Listener 位置:一般设为玩家摄像机或主角位置。
-
声源位置:每个发声物体(敌人、 NPC、环境音)维护一个 3D 坐标。
-
-
-
-
设置
AudioEngine.setVolume(audioId, volume)
-
距离限制:超出
maxDistance则停止播放或设为静音。
4.2 原理流程图
graph TD
A[初始化 Listener 位置] --> B[为每个声源创建 AudioID]
B --> C[每帧更新 Listener 位置]
C --> D[遍历所有活跃声源]
D --> E[计算声源与 Listener 的距离]
E --> F{距离 > maxDistance?}
F -- 是 --> G[停止或静音该声源]
F -- 否 --> H[根据衰减模型计算音量]
H --> I[设置 AudioEngine 音量]
G --> I
I --> J[继续下一个声源]
J --> D
5. 环境准备
5.1 Cocos Creator 3.x
-
构建目标:Web / Android / iOS / Windows
-
使用
AudioEngine播放音效(非 AudioSource组件,便于独立控制音量)
-
确保使用 立体声 音频资源(.mp3/.ogg 双声道),否则左右平衡无效
5.2 资源准备
-
短音效(如脚步声、枪声)时长 < 2s,便于频繁播放
-
6. 详细代码实现
6.2 衰减模型工具类
export enum AttenuationMode {
LINEAR,
LOGARITHMIC,
EXPONENTIAL
}
export class AudioAttenuation {
static computeVolume(
distance: number,
minDistance: number,
maxDistance: number,
maxVolume: number,
mode: AttenuationMode,
attenuationFactor: number = 1.0
): number {
if (distance <= minDistance) {
return maxVolume;
}
if (distance >= maxDistance) {
return 0;
}
let volume = 0;
switch (mode) {
case AttenuationMode.LINEAR:
volume = maxVolume * (1 - (distance - minDistance) / (maxDistance - minDistance));
break;
case AttenuationMode.LOGARITHMIC:
volume = maxVolume * (1 - Math.log(distance - minDistance + 1) / Math.log(maxDistance - minDistance + 1));
break;
case AttenuationMode.EXPONENTIAL:
volume = maxVolume * Math.exp(-(distance - minDistance) / attenuationFactor);
break;
}
return Math.max(0, Math.min(maxVolume, volume));
}
}
6.3 3D 空间化音频管理器(Creator TS)
import { _decorator, Component, Node, Vec3, AudioEngine, AudioPlayer } from 'cc';
import { AudioAttenuation, AttenuationMode } from './AudioAttenuation';
const { ccclass, property } = _decorator;
interface SoundEmitter {
id: number; // AudioEngine 返回的 audioId
clip: string;
node: Node; // 声源节点
looping: boolean;
minDistance: number;
maxDistance: number;
maxVolume: number;
mode: AttenuationMode;
attenuationFactor: number;
active: boolean;
}
@ccclass('SpatialAudioManager')
export class SpatialAudioManager extends Component {
@property(Node)
listener: Node = null!; // 通常是 Player 或 Camera
private emitters: Map<number, SoundEmitter> = new Map();
private nextEmitterId: number = 1;
start() {
// 全局单例
(window as any).spatialAudioMgr = this;
this.schedule(this.update, 0.05); // 20fps 更新足够
}
playSound(clip: string, emitterNode: Node, looping: boolean = false,
minDist: number = 1, maxDist: number = 20, maxVol: number = 1,
mode: AttenuationMode = AttenuationMode.LINEAR, attFac: number = 5): number {
const audioId = AudioEngine.getInstance().play(clip, looping, maxVol);
const id = this.nextEmitterId++;
this.emitters.set(id, {
id: audioId,
clip,
node: emitterNode,
looping,
minDistance: minDist,
maxDistance: maxDist,
maxVolume: maxVol,
mode,
attenuationFactor: attFac,
active: true
});
return id;
}
stopSound(emitterId: number) {
const em = this.emitters.get(emitterId);
if (em) {
AudioEngine.getInstance().stop(em.id);
em.active = false;
this.emitters.delete(emitterId);
}
}
update() {
if (!this.listener) return;
const listenerPos = this.listener.worldPosition;
this.emitters.forEach(em => {
if (!em.active) return;
const emitterPos = em.node.worldPosition;
const dist = Vec3.distance(listenerPos, emitterPos);
const volume = AudioAttenuation.computeVolume(
dist,
em.minDistance,
em.maxDistance,
em.maxVolume,
em.mode,
em.attenuationFactor
);
AudioEngine.getInstance().setVolume(em.id, volume);
// 超出 maxDistance 且非循环则停止
if (dist >= em.maxDistance && !em.looping) {
this.stopSound(this.getEmitterKey(em));
}
});
}
private getEmitterKey(em: SoundEmitter): number {
for (const [key, value] of this.emitters.entries()) {
if (value === em) return key;
}
return -1;
}
}
6.4 使用示例(在敌人脚本中)
import { _decorator, Component, Node } from 'cc';
import { SpatialAudioManager } from './SpatialAudioManager';
const { ccclass, property } = _decorator;
@ccclass('EnemyController')
export class EnemyController extends Component {
@property
footstepClip: string = "sounds/footstep";
private audioMgr!: SpatialAudioManager;
private emitterId: number = 0;
start() {
this.audioMgr = (window as any).spatialAudioMgr as SpatialAudioManager;
// 每隔 0.8s 播放一次脚步声
this.schedule(() => {
if (this.emitterId) this.audioMgr.stopSound(this.emitterId);
this.emitterId = this.audioMgr.playSound(
this.footstepClip,
this.node,
false,
1, 15, 1,
// 对数衰减
SpatialAudioManager.prototype['AudioAttenuation']?.AttenuationMode?.LOGARITHMIC || 1,
3
);
}, 0.8);
}
}
6.5 Cocos2d-x C++ 示例(核心逻辑)
#ifndef __SPATIAL_AUDIO_MANAGER_H__
#define __SPATIAL_AUDIO_MANAGER_H__
#include "cocos2d.h"
#include <unordered_map>
#include <string>
enum class AttenuationMode { Linear, Logarithmic, Exponential };
struct SoundEmitter {
int audioId;
std::string clip;
cocos2d::Node* node;
bool looping;
float minDistance;
float maxDistance;
float maxVolume;
AttenuationMode mode;
float attenuationFactor;
bool active;
};
class SpatialAudioManager : public cocos2d::Node {
public:
CREATE_FUNC(SpatialAudioManager);
virtual bool init() override;
int playSound(const std::string& clip, cocos2d::Node* emitterNode, bool looping = false,
float minDist = 1, float maxDist = 20, float maxVol = 1,
AttenuationMode mode = AttenuationMode::Linear, float attFac = 5.0f);
void stopSound(int emitterId);
void setListener(cocos2d::Node* listener);
void update(float dt) override;
private:
cocos2d::Node* listener = nullptr;
std::unordered_map<int, SoundEmitter> emitters;
int nextId = 1;
};
#endif
#include "SpatialAudioManager.h"
#include "AudioEngine.h"
#include "math.h"
USING_NS_CC;
bool SpatialAudioManager::init() {
if (!Node::init()) return false;
this->scheduleUpdate();
return true;
}
int SpatialAudioManager::playSound(const std::string& clip, Node* emitterNode, bool looping,
float minDist, float maxDist, float maxVol, AttenuationMode mode, float attFac) {
int audioId = experimental::AudioEngine::play2d(clip, looping, maxVol);
int id = nextId++;
emitters[id] = { audioId, clip, emitterNode, looping, minDist, maxDist, maxVol, mode, attFac, true };
return id;
}
void SpatialAudioManager::stopSound(int emitterId) {
auto it = emitters.find(emitterId);
if (it != emitters.end()) {
experimental::AudioEngine::stop(it->second.audioId);
it->second.active = false;
emitters.erase(it);
}
}
void SpatialAudioManager::setListener(Node* listenerNode) {
listener = listenerNode;
}
void SpatialAudioManager::update(float dt) {
if (!listener) return;
Vec3 listenerPos = listener->getWorldPosition();
for (auto& [id, em] : emitters) {
if (!em.active) continue;
Vec3 emitterPos = em.node->getWorldPosition();
float dist = emitterPos.distance(listenerPos);
float volume = maxVol;
if (dist <= em.minDistance) {
volume = em.maxVolume;
} else if (dist >= em.maxDistance) {
volume = 0;
} else {
switch (em.mode) {
case AttenuationMode::Linear:
volume = em.maxVolume * (1 - (dist - em.minDistance) / (em.maxDistance - em.minDistance));
break;
case AttenuationMode::Logarithmic:
volume = em.maxVolume * (1 - log(dist - em.minDistance + 1) / log(em.maxDistance - em.minDistance + 1));
break;
case AttenuationMode::Exponential:
volume = em.maxVolume * exp(-(dist - em.minDistance) / em.attenuationFactor);
break;
}
}
volume = MAX(0, MIN(em.maxVolume, volume));
experimental::AudioEngine::setVolume(em.audioId, volume);
if (dist >= em.maxDistance && !em.looping) {
stopSound(id);
}
}
}
7. 运行结果
-
当声源靠近 Listener,音量逐渐增大;远离则减小。
-
超出
maxDistance的非循环音效自动停止。
-
-
8. 测试步骤
-
-
移动 Listener(玩家),观察音量变化是否符合预期。
-
-
-
9. 部署场景
10. 疑难解答
11. 未来展望与技术趋势
12. 挑战
13. 总结
本方案基于 Cocos2d 实现了 音频 3D 空间化与距离衰减效果,通过 Listener 与 Emitter 位置实时计算距离,并应用多种衰减模型动态调整音量,完整 TypeScript 与 C++ 代码可直接集成。
适用于各类需要空间音频的游戏场景,并为未来 VR/AR 与 AI 音效发展奠定了基础。
评论(0)