Sound类

创建Sound类真的很简单。你已经在第一章看到了一些Sound类,sound cue回放非常简单。为了使使用起来更简单,您可以定义一个枚举包含所有的声音cue名称。通过这种方式可以确保每一个sound cue确实存在,而且你不会输错名称。这样做的另一个优点是智能感知,它将会显示隐藏在XACT项目中所有可用的sound cue。花费两分钟时间写这些枚举是值得的。在Sound类中的Play方法帮助下,现在播放sound cue很容易。

/// <summary>
/// Play
/// </summary>
/// <param name="soundName">Sound name</param>
public static void Play(string soundName)
{
  if (soundBank == null)
    return;

  try
  {
    soundBank.PlayCue(soundName);
  } // try
  catch (Exception ex)
  {
    Log.Write("Playing sound " + soundName + " failed: " +
      ex.ToString());
  } // catch
} // Play(soundName)

/// <summary>
/// Play
/// </summary>
/// <param name="sound">Sound</param>
public static void Play(Sounds sound)
{
  Play(sound.ToString());
} // Play(sound)

您也可以通过让字符串私有使得只允许声音枚举。如果sound bank无法在构造函数中初始化(例如,如果该文件丢失)此方法将立即返回而您无法播放任何声音。错误会被记录到日志文件中。接着你调用PlayCue播放并忘记sound cue。如果失败你只写入日志文件,因为你不想因为某些声音文件的丢失打断正常的程序逻辑。

正如你之前所见,您也可以使用GetCue方法记住一个sound cue,然后做多个事情。如果您想自己停止cue而不是等待声音放完,这样做是很有用的。作为一个例子,下面的代码用来停止播放音乐:

someSoundCue = soundBank.GetCue("SomeSound");
someSoundCue.Play();

...
someSoundCue.Stop(AudioStopOptions.Immediate);

本书中的游戏的Sound类中的大部分代码是相同的,下面看看不同和特殊功能(见图9-14)。

通过XACT添加声音——Sound类-风君雪科技博客
图 9-14

首先你会发现在Sound枚举中有不同的sound cue名称。Rocket Commander和赛车游戏使用自定义的picth cue。XNA Shooeter不使用任何特殊设置,你只需在游戏开始时播放音乐,然后播放所有音效。

为了使输入播放声音的代码更舒适,创建了几个辅助方法。例如,PlayExplosionSound只是取代了下面的代码使其能更快并更容易编写:

Sound.Play(Sound.Sounds.Explosion);

对于音乐播放添加了两个额外的方法:StartMusic和StopMusic,它们也保存了music cue的音轨。

每个sound类中还有一个单元测试用来检查回放声音是否正常,并检查音量是否正确。下面是Rocket Commander声音类中的单元测试:

/// <summary>
/// Test play sounds
/// </summary>
public static void TestPlaySounds()
{
  TestGame.Start(
    delegate
    {
      if (Input.MouseLeftButtonJustPressed ||
        Input.GamePadAJustPressed)
        Sound.Play(Sounds.Bomb);
      else if (Input.MouseRightButtonJustPressed ||
        Input.GamePadBJustPressed)
        Sound.Play(Sounds.Click);
      else if (Input.KeyboardKeyJustPressed(Keys.D1))
         Sound.Play(Sounds.GameMusic);
      else if (Input.KeyboardKeyJustPressed(Keys.D2))
         Sound.Play(Sounds.MenuMusic);
      else if (Input.KeyboardKeyJustPressed(Keys.D3))
         Sound.Play(Sounds.Explosion);
      else if (Input.KeyboardKeyJustPressed(Keys.D4))
         Sound.Play(Sounds.Fuel);
      else if (Input.KeyboardKeyJustPressed(Keys.D5))
         Sound.Play(Sounds.Victory);
      else if (Input.KeyboardKeyJustPressed(Keys.D6))
         Sound.Play(Sounds.Defeat);
      else if (Input.KeyboardKeyJustPressed(Keys.D7))
      {
        Sound.PlayRocketMotorSound(0.75f);
        Sound.ChangeRocketMotorPitchEffect(0.5f);
      } // else if
      else if (Input.KeyboardKeyJustPressed(Keys.D8))
        Sound.StopRocketMotorSound();
      TextureFont.WriteText(2, 30,
        "Press 1-8 or A/B or left/right "+
        "mouse buttons to play back sounds!");
  });
} // TestPlaySounds()

最后,Update方法被BaseGame类中的Update方法自动调用。Update方法只为您确保在XACT中所有参数,循环,时间值的更新。

火箭发动机声音

快速看一下Rocket Commander中的火箭发动机声音。前面你已经看到完成这个工作所需的代码,下面是如何整合在一起并配合Rocket Commander游戏引擎,一开始这是用DirectSound写的,但在XACT中工作得也很好:

/// <summary>
/// Play rocket motor sound
/// </summary>
public static void PlayRocketMotorSound()
{
  // Get new cue everytime this is called, else we get Xact throwing
  // this: The method or function called cannot be used in the manner
  // requested.
  rocketMotorSound = soundBank.GetCue(Sounds.RocketMotor.ToString());
  // Plays the sound looped, set in XACT
  rocketMotorSound.Play();
  // Make motor category a little silent
  motorCategory.SetVolume(0.86f);
} // PlayRocketMotorSound(volume)

/// <summary>
/// Change rocket motor pitch effect
/// </summary>
/// <param name="pitchFactor">Pitch factor</param>
public static void ChangeRocketMotorPitchEffect(float pitchFactor)
{
  rocketMotorSound.SetVariable("Pitch",
    55 * MathHelper.Clamp(pitchFactor - 1, -1, 1));
} // ChangeRocketMotorPitchEffect(pitchFactor)

/// <summary>
/// Stop rocket motor sound
/// </summary>
public static void StopRocketMotorSound()
{
  rocketMotorSound.Stop(AudioStopOptions.Immediate);
} // StopRocketMotorSound()

游戏开始后你只需调用PlayRocketMotorSound方法就能播放火箭的声音,然后通过ChangeRocketMotorPitchEffect方法修改它:

// Adjust rocket playback frequency to flying speed
Sound.ChangeRocketMotorPitchEffect(0.66f + speed * 0.9f);

如果你失败或任务结束,则调用StopRocketMotorSound方法停止播放火箭的声音。

赛车游戏中的声音逻辑工作方法类似,但更加复杂,因为你有13个不同的发动机声音。看一下赛车游戏的sound类了解更多细节。

Whoosh,那是什么呀?

初版本的Rocket Commander使用自己的3D音效计算公式并使用DirectSound的音量和和panning属性。在XNA中不能改变panning。所有声音必须以在XACT中创建和设置的方式播放。对于3D声音,你应该使用真实的3D声音代码,在XNA电测版本中我写了一些播放3D声音的代码,并实现了一个在三维世界中的3D监听对象。

这个3D监听对象在XNA中被移除,现在您不能使用3D音效。希望将来会做出改变,如果3D可以被实现,我一定会更新这本书中的代码,但现在你只能播放单声道的声音,没有立体声或环绕声。

如果你可以使用X3DAudio和X3DAudioListener (这些类可能有不同的名称,重要的是您可以设置和播放3D音频效果,并且在3D listener类的帮助下确定玩家的位置),在设置了所有重要的cue后,下面的代码用于播放3D声音:

// Setup 3D listener for 3D sound
Sound.Listener = new X3DAudioListener(
  zAxis, yAxis, cameraPosition, movementVector);

现在,为每个声音设置发射器并播放3D音频实例。目前还没有C#代码可以做到这一点,如果你对此感到困惑,你可以看看下面的链接去了解X3Daudio类是如何使用C++的。在C#中做法类似,但希望能更容易编写。

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/directx9_c/ audio_xact_overview_x3daudio.asp

菜单声音

这里有一些关于菜单提示声音的小技巧:

使用如PlayMenuHighlight或PlayMenuClick之类的辅助方法播放声音,而不是使用Sound枚举。
如果你想用不同的音量播放菜单声音,例如主要按钮声音响而小控件声音轻,可以在XACT中建立两个sound cue而不是编写自己的自定义代码,这样做工作量很大而且没有必要。

尝试写辅助方法判断鼠标是否移到了控件上并重用此代码,这样,你就无需每次都自己处理播放点击或高亮的声音。

请确认您还支持Xbox 360控制器和键盘。编写一个只支持鼠标的游戏在Xbox 360毫无意义,因为Xbox 360不支持鼠标。

当移动到菜单项时播放高亮的声音,用户按下手柄键或按下space或Enter键选择菜单时播放点击的声音。