问题

你想让声音循环播放,例如,播放背景音乐或播放连续的声音,例如一个汽车引擎的声音。

注意:因为Zune不支持Xact,你需要使用SoundEffect.Play()的重载方法循环播放声音,可见教程7-1中的对应解释。

解决方案

使用XAct audio tool,可以很容易地表示一个声音是否要循环播放。你将在XNA代码中创建一个Cue对象,因为你需要能够在播放过程中进行暂停或停止操作。

你还可以检测一个声音是否已经完成播放,这在你想切换背景音乐时很重要,这个操作可以通过检查Cue的IsStopped属性做到。

工作原理

循环播放一个声音你可以通过在Xact工具中将一个声音的LoopEvent属性设为Infinite让它循环播放。要做到这点,打开你的Xact项目,在sound bank中选择需要循环播放的sound。选择了sound后,在Sound Bank面板的右上方的Play Wave会变得可见,点击Play Wave节点,如图7-2所示。

在点击了Play Wave节点后,它的属性在XAct 窗口的左下方的属性窗口中可见,找到LoopEvent属性,设置为Infinite,别忘了保存Xact项目。

image

图7-2 当选择了sound后Play Wave节点会变得可见

现在当你重新编译XNA项目并使用前面的代码播放cue时,它就会无限循环播放。因为你想进行控制,可以在适当的时候停止播放这个cue,所以需要创建一个Cue对象储存对这个cue的引用:

Cue cue1; 

通过调用soundBank变量的GetCue方法对这个变量赋值,你可以通过调用Play方法播放这个cue:

cue1 = soundBank.GetCue("audio1"); 
cue1.Play();

这会让cue循环播放,这是你在XAct audio tool中设置号好的。但这次,你有了指向cue的引用,所以可以暂停、继续或停止这个cue:

cue1.Pause(); 
cue1.Resume(); 
cue1.Stop(AudioStopOptions.Immediate);

当心:当你从播放中停止一个cue后,你无法简单地在这个cue再次调用Play。你首先需要调用它的GetCue 方法才能从soundBank中再次调用它。

检查一个Sound是否已经停止播放/改变背景音乐

你可以通过sound的IsStopped属性检查一个sound cue是否已经完成播放:

if (currentCue.IsStopped) 
    //do something

如果你想让XNA程序循环播放一些背景音乐,你需要创建一个数组保存cue的名称而不是cue本身,当cue开始播放后cue本身会变得无用。你需要一些额外的变量创建一个背景循环系统:

string[] bgCueNames; 
Cue currentCue;
int currentCueNr = 0;

这个数组保存要播放的背景cue的名称,currentCue保存当前正在播放的cue,这样你可以检查它是否已经结束,currentCueNr变量用于激活下一个cue。

下面的方法初始化cun名称数组并开始第一个cue:

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

    bgCueNames = new string[5]; 
    bgCueNames[0] = "bgAudio1"; 
    bgCueNames[1] = "bgAudio2"; 
    bgCueNames[2] = "bgAudio3"; 
    bgCueNames[3] = "bgAudio4"; 
    bgCueNames[4] = "bgAudio5";

    PlayBGCue(0); 
}

首先你需要创建一个教程7-1所示的Xact项目,包含5个cue。开始一个cue的PlayBGCue方法是简单的:

private void PlayBGCue(int cueNr)
...{
    currentCue = soundBank.GetCue(bgCueNames[cueNr]); 
    currentCue.Play();
}

它在currentCue变量中存储了当前正在播放的cue的引用,所以你可以在UpdateSounds方法中检查它的IsPlayed属性,而UpdateSounds方法放在了主Update方法中:

private void UpdateSounds() 
...{
    if (currentCue.IsStopped) 
    ...{
        if (++currentCueNr == bgCueNames.Length)
            currentCueNr = 0;

        PlayBGCue(currentCueNr); 
    }
    audioEngine.Update(); 
}

如果当前cue已经完成播放,则增加currentCueNr,如果它大于播放的cue数量(本例中为5)则设为0。最后调用PlayBGCue播放下一个sound。

注意:在currentCueNr之前的 ++ 表示你想在求值前增加currentCueNr的值。如果写成currentCueNr++,当currentCueNr为4时判断结果为false,之后当值为5时,会抛出 a GetCue方法的OutOfRange错误。通过写成++currentCueNr,如果currentCueNr为4,这个值首先被增加到5,这样判断结果为true,这个值会被设置为0。

通过在这个方法中放置audioEngine的Update方法,在Update调用的所有东西就是这个UpdateSounds方法。

代码

前面的章节中你可以找到循环播放背景音乐列表的所有代码,下面的代码通过按下空格键开始循环播放本教程第一部分中定义的sound,按Enter键停止:

protected override void Update(GameTime gameTime)
...{
    GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);

    if (gamePadState.Buttons.Back == ButtonState.Pressed) 
        this.Exit();

    KeyboardState keyState = Keyboard.GetState();

    if (keyState.IsKeyDown(Keys.Space) || (gamePadState.Buttons.B == ButtonState.Pressed))
...    {
        if ((cue1 == null) || (cue1.IsStopped))
...        {
            cue1 = soundBank.GetCue("audio1");
            cue1.Play(); 
        }
    }
    if (keyState.IsKeyDown(Keys.Enter) || (gamePadState.Buttons.A ButtonState.Pressed))
        if (cue1 != null)
            cue1.Stop(AudioStopOptions.Immediate);

    audioEngine.Update();
    base.Update(gameTime); 
}

当空格键第一次被按下时,cue1变量为null。只要用户按下了Enter键,cue就会停止。当用户再次按下空格键,cue会重新播放。

再次提醒,在cue停止后你需要重新创建cue1变量(通过调用GetCue方法)。