问题
你想让声音循环播放,例如,播放背景音乐或播放连续的声音,例如一个汽车引擎的声音。
注意:因为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项目。
图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方法)。