问题

要让你的3D游戏更真实,你想让每个声音都位于3D空间的某个位置。通过这种方式,在相机右侧发生的爆炸会主要通过右声道播放,这样用户就会感到爆炸是真的发生在他的右侧。即使爆炸不在相机的视野中,玩家也可以知道在他的右侧发生了什么事。

注意:Zune不支持Xact和下面的Apply3D()方法,只支持Cue对象,3D音效只被PC和Xbox 360支持。

解决方案

XNA可以让这些事变得简单。对每个声音,你必须设置相机的3D位置和使用声音Cue对象的Apply3D方法设置声源的位置。注意当相机或声源移动时,你需要调用这个方法更新Cue。工作原理如果你想设置声源的3D位置,首先必须为这个音源设置一个AudioEmitter对象和为相机设置一个AudioListener对象。当将这两者传递到Cue的Apply3D方法中时,XNA会计算音响(或耳机)左右声道声音的分配。

下面的方法创建了两个对象:

private void UpdateSoundPosition(Cue cue, Vector3 sourcePos, Vector3 camPos, Vector3 camForward, Vector3 camUp)
...{
    AudioEmitter emitter = new AudioEmitter();
    emitter.Position = sourcePos;

    AudioListener listener = new AudioListener(); 
    listener.Position = camPos;
    listener.Forward = camForward;
    listener.Up = camUp;

    cue.Apply3D(listener, emitter); 
}

对emitter(音源)来说,你只需设置位置。对listener(相机)来说,你还要设置Forward和Up向量,因为当相机旋转了180度后,左右声道会互换。

当创建了这两个变量后,你可以将它们传递到Cue的Apply3D方法中,当音源或相机发生移动时都要进行这个操作。

你需要在第一次调用Play方法前调用Apply3D方法。所以,确保在Cue中还没有调用 Play方法:

protected override void Initialize() 
...{
    fpsCam = new QuakeCamera(GraphicsDevice.Viewport, new Vector3(0,0,5) , 0, 0);

    audioEngine = new AudioEngine("Content/Audio/MyXACTproject.xgs"); 
    waveBank = new WaveBank(audioEngine, "Content/Audio/myWaveBank.xwb"); 
    soundBank = new SoundBank(audioEngine, "Content/Audio/mySoundBank.xsb");

    cue1 = soundBank.GetCue("audio1");

    base.Initialize(); 
}

在Update方法中,如果你移动了相机位置或音源位置,就需要调用UpdateSoundPosition方法:

protected override void Update(GameTime gameTime)
...{
    GamePadState gamePadState = GamePad.GetState(PlayerIndex.One); 
    if (gamePadState.Buttons.Back == ButtonState.Pressed) 
        this.Exit();

    MouseState mouseState = Mouse.GetState(); 
    KeyboardState keyState = Keyboard.GetState();

    fpsCam.Update(mouseState, keyState, gamePadState);

    float time = (float)gameTime.TotalGameTime.TotalMilliseconds/1000.0f; 
    Vector3 startingPos = new Vector3(0, 0, -10);
    Matrix rotMatrix = Matrix.CreateRotationY(time);
    modelPos = Vector3.Transform(startingPos, rotMatrix);

    UpdateSoundPosition(cue1, modelPos, fpsCam.Position, fpsCam.Forward, fpsCam.UpVector);

    if (cue1.IsPrepared)
        cue1.Play();
    audioEngine.Update();

    base.Update(gameTime); 
}

这个方法的前半部分检查用户输入并将输入传递到相机,解释请见教程2-3。当中的代码块更新音源位置。当相机和音源更新后,你需要调用UpdateSoundPosition方法告知cue1对象最后的位置。如果cue1还没有播放,则开始播放。

当心:因为3D effect是通过控制左右声道的音量实现的,所以声音中的任何立体声信息会丢失:首先将声音转换为单声道,然后对应物体的3D位置设置左右声道的音量。

代码

所有的Initialize,UpdateSoundPosition和Update方法前面已经写过了。

扩展阅读

Xact音频工具让你可以在cue中添加一些很棒的3D音效,诸如距离衰减和多普勒效应。因为是图形界面,在视频中介绍要比在书中好得多。在http://creators.xna.com 网站上有一些很好的视频介绍如何将3D音效添加到cue中。

译者注:http://creators.xna.com/en-US/sample/3daudio网页上也有一个3D音效的例子,实现了距离衰减,多普勒效应。