Audio3D

更新页 :
页面创建日期 :

提示中的注意事项

此示例基于以下网站上发布的程序。 我稍微更改了代码,以便更容易理解,并用日语解释它。 基本上,我们使用原始代码,因此,如果您确实希望将其用于游戏程序,请及时修复它。

参考网站

此外,我解释的假设是,我有一些关于单游戏和XNA的基本知识。 有关基本部分,请参阅单 游戏提示XNA 提示

特别是,要知道矢量三角函数矩阵作为数学的必要程度。

环境

平台
  • Windows 10
  • 代码可以转移到其他支持 MonoGame 的平台上。
Visual Studio
  • Visual Studio 2019
.NET Core
  • 3.1
MonoGame
  • 3.8

关于示例

根据相机的位置,狗的位置猫的位置会分别发出叫声。 猫绕着原点转,玩家可以使用钥匙操作摄像机的位置。 确保每次相机或猫的位置发生变化时,声音会发生变化。 我认为使用耳机很容易理解。 我在 5.1 频道上未验证。

操作方法

操作内容 键盘游戏板 (XInput) 鼠标触摸
摄像机前进和后退 ↑↓ 左摇杆(向上和向下) - -
更改摄像机的方向 ←→ 左摇杆(左和右) - -
游戏结束 Esc Back - -

准备什么

  • 狗的叫声文件
  • 三个猫叫声文件
  • 狗图片文件
  • 猫图片文件
  • 地面图像文件

官方网站的原始示例使用预构建的内容 .xnb 文件。 如果要按原样使用官方网站示例,请直接将其添加到 Visual Studio 解决方案中,并在生成时复制它,而无需使用 MGCB。

我不知道为什么原始示例采用这种方法,但这可能是因为我正在迁移项目的旧版本。 当然,使用 MGCB 生成相同的文件没有问题。 可在此网站上下载的项目已修改为 MGCB 版本。

程序

有关完整代码,请参阅下载程序。

由于此示例旨在使代码在运行时更易于理解,但代码更易于使用,因此代码有点冗长。 如果仅使用 Audio3D,则编写代码要少得多,但下面是一个原始示例。 对于不太重要的部分,请快速冲洗。

项目由以下代码文件组成:

  • Audio3DGame
  • AudioManager
  • Cat
  • Dog
  • IAudioEmitter
  • Program
  • QuadDrawer
  • SpriteEntity

四维绘制器类

此类是绘制矩形多边形的帮助器类。 矩形多边形主要用于显示 3D 子画面(广告牌)。 此提示还用于猫和狗广告牌以及地面的矩形显示。

此类与音频 3D 无关。

字段

readonly GraphicsDevice _graphicsDevice;
readonly AlphaTestEffect _effect;
readonly VertexPositionTexture[] _vertices;

绘制多边形所需的最低信息。 从顶点数据绘制多边形,而不提供模型数据。

构造函数

/// <summary>
/// 新しい四辺形の描画ワーカーを作成します。
/// </summary>
public QuadDrawer(GraphicsDevice device)
{
  _graphicsDevice = device;

  _effect = new AlphaTestEffect(device);

  _effect.AlphaFunction = CompareFunction.Greater;
  _effect.ReferenceAlpha = 128;

  // 4つの頂点の配列を事前に割り当てます。
  _vertices = new VertexPositionTexture[4];

  _vertices[0].Position = new Vector3(1, 1, 0);
  _vertices[1].Position = new Vector3(-1, 1, 0);
  _vertices[2].Position = new Vector3(1, -1, 0);
  _vertices[3].Position = new Vector3(-1, -1, 0);
}

此类是通用的,因此在 Game 类的初始化过程中创建实例并 GraphicsDevice 接收 。

在这里,您可以设置绘制子画面所需的效果,以及矩形多边形所需的四个顶点的位置。 大小在绘制时缩放,因此从 -1 到 1。

绘制四边形方法

/// <summary>
/// 3Dワールドの一部として四角形を描画します。
/// </summary>
public void DrawQuad(Texture2D texture, float textureRepeats, Matrix world, Matrix view, Matrix projection)
{
  // 指定されたテクスチャとカメラマトリックスを使用するようにエフェクトを設定します。
  _effect.Texture = texture;

  _effect.World = world;
  _effect.View = view;
  _effect.Projection = projection;

  // 指定された数のテクスチャの繰り返しを使用するように頂点配列を更新します。
  _vertices[0].TextureCoordinate = new Vector2(0, 0);
  _vertices[1].TextureCoordinate = new Vector2(textureRepeats, 0);
  _vertices[2].TextureCoordinate = new Vector2(0, textureRepeats);
  _vertices[3].TextureCoordinate = new Vector2(textureRepeats, textureRepeats);

  // 矩形を描画します。
  _effect.CurrentTechnique.Passes[0].Apply();

  _graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, _vertices, 0, 2);
}

将传入的纹理设置为矩形并绘制。 这是一个基本过程,所以没有什么值得注意的。

如果有一个点,则可以在参数中 textureRepeats 指定变量 ,从而更改纹理的坐标。 如果将 VertexPosition 文本.文本集合设置为超出 0 到 1 的范围,则默认情况下将重复绘制纹理。 它可用于重复排列磁贴等。 事实上,地面上的方格图案似乎排列了多个简单的黑白图像。

IAudioEmitter 接口

/// <summary>
/// 3D サウンドを再生するエンティティの位置と速度を検索するために AudioManager が使用するインターフェイス。
/// </summary>
public interface IAudioEmitter
{
  Vector3 Position { get; }
  Vector3 Forward { get; }
  Vector3 Up { get; }
  Vector3 Velocity { get; }
}

定义为发出声音的实体。 发出声音的实体是狗和猫,因此它们在每个类中都继承了它们。 作为埃米特,您需要的信息包括:

属性 用途
Position 实体的位置
Forward 实体指向的方向(矢量)
Up 实体的向上。 人说是头存在的方向
Velocity 实体的移动速度。 用于计算多普勒值。

SpriteEntity 类

用于表示 3D 子画面(广告牌)的类。 我继承了,因为 IAudioEmitter 这也是一个发射器。 狗和猫继承了这个类。

属性

/// <summary>エンティティの3D位置を取得または設定します。</summary>
public Vector3 Position { get; set; }

/// <summary>エンティティが向いている方向を取得または設定します。</summary>
public Vector3 Forward { get; set; }

/// <summary>このエンティティの上方向を取得または設定します。</summary>
public Vector3 Up { get; set; }

/// <summary>このエンティティの移動速度を取得または設定します。</summary>
public Vector3 Velocity { get; protected set; }

/// <summary>このエンティティの表示に使用されるテクスチャを取得または設定します。</summary>
public Texture2D Texture { get; set; }

具有实体的位置等。 基本上,你有信息作为发射器。 它还具有要显示的图像,因为它是绘图实体。

更新方法

/// <summary>
/// エンティティの位置を更新し、サウンドを再生できるようにします。
/// </summary>
public abstract void Update(GameTime gameTime, AudioManager audioManager);

描述实体的位置和播放声音的过程,但在派生类中实现。

绘制方法

/// <summary>
/// エンティティをビルボードスプライトとして描画します。
/// </summary>
public void Draw(QuadDrawer quadDrawer, Vector3 cameraPosition, Matrix view, Matrix projection)
{
  Matrix world = Matrix.CreateTranslation(0, 1, 0) *
                 Matrix.CreateScale(800) *
                 Matrix.CreateConstrainedBillboard(Position, cameraPosition, Up, null, null);

  quadDrawer.DrawQuad(Texture, 1, world, view, projection);
}

狗和猫的图像和位置是不同的,但除此之外,绘制机制是完全相同的,所以我在这里描述。

每个坐标变换和纹理都传递给四绘图器以显示矩形多边形。 坐标转换是基本知识,所以我省略了解释。

Matrix.CreateConstrainedBillboard 使用方法和相机信息,您可以轻松地表示广告牌,并充分利用它。

狗类

狗画,玩叫是类。 SpriteEntity 继承类。 狗在 3D 空间中保持固定位置。

字段

/// <summary>サウンドを開始または停止するまでの時間。</summary>
TimeSpan _timeDelay = TimeSpan.Zero;

/// <summary>現在再生中のサウンド(ある場合)。</summary>
SoundEffectInstance _activeSound = null;

_timeDelay 每隔一段时间播放一次。

SoundEffectInstance _activeSound 是播放声音的实例。 SoundEffectInstance 类在声音播放中看起来不错,但您可以在 3D 声音中使用它。 顺便说一下 _activeSound ,它只会从以后的音频管理器类接收引用,因此不要在 Dot 类中销毁它。

更新方法

/// <summary>
/// 犬の位置を更新し、音を鳴らします。
/// </summary>
public override void Update(GameTime gameTime, AudioManager audioManager)
{
  // エンティティを固定位置に設定します。
  Position = new Vector3(0, 0, -4000);
  Forward = Vector3.Forward;
  Up = Vector3.Up;
  Velocity = Vector3.Zero;

  // 時間遅延がなくなった場合は、ループ音を開始または停止します。 これは通常は永久に続きますが、6秒の遅延後に停止し、さらに4秒後に再起動します。
  _timeDelay -= gameTime.ElapsedGameTime;

  if (_timeDelay < TimeSpan.Zero)
  {
    if (_activeSound == null)
    {
      // 現在再生中のサウンドがない場合は、トリガーします。
      _activeSound = audioManager.Play3DSound("DogSound", true, this);

      _timeDelay += TimeSpan.FromSeconds(6);
    }
    else
    {
      // それ以外の場合は、現在のサウンドを停止します。
      _activeSound.Stop(false);
      _activeSound = null;

      _timeDelay += TimeSpan.FromSeconds(4);
    }
  }
}

在此示例中,狗始终位于同一位置,因此前四个参数由确定和指定。 绘图在基类中工作。

_timeDelay 用作停止的剩余时间,然后播放振铃。

AudioManager 稍后我们将向 您展示,它Play3DSound 通过在 3D 空间中播放鸣叫的名称、循环信息以及作为发射器的信息来播放。

Dog 类是专为循环播放声音而设计的,因此无需深入思考,因为分支处理(如播放和停止)只是调整。

猫类

这是一个类,使猫画和播放叫声。 SpriteEntity 继承类。 猫绕着原点转。

字段

/// <summary>次の音を鳴らすまでの時間。</summary>
TimeSpan _timeDelay = TimeSpan.Zero;

/// <summary>サウンドバリエーションから選択するための乱数ジェネレータ。</summary>
static readonly Random _random = new Random();

_timeDelay 与 Dog 类相同,在下一个类发出蜂鸣音之前的剩余时间使用。

猫的叫声是随机选择的,有三种类型,但与音频3D无关,因为它是一个补充元素。

更新方法

/// <summary>
/// 猫の位置を更新し、音を鳴らします。
/// </summary>
public override void Update(GameTime gameTime, AudioManager audioManager)
{
  // 猫を大きな円で動かします。
  double time = gameTime.TotalGameTime.TotalSeconds;

  float dx = (float)-Math.Cos(time);
  float dz = (float)-Math.Sin(time);

  Vector3 newPosition = new Vector3(dx, 0, dz) * 6000;

  // エンティティの位置と速度を更新します。
  Velocity = newPosition - Position;
  Position = newPosition;
  if (Velocity == Vector3.Zero)
  {
    Forward = Vector3.Forward;
  }
  else
  {
    Forward = Vector3.Normalize(Velocity);
  }

  Up = Vector3.Up;

  // 時間遅延がなくなった場合は、別の単発音をトリガーします。
  _timeDelay -= gameTime.ElapsedGameTime;

  if (_timeDelay < TimeSpan.Zero)
  {
    // 異なる3つのサウンドバリエーション(CatSound0、CatSound1、CatSound2)からランダムに選択します。
    string soundName = "CatSound" + _random.Next(3);

    audioManager.Play3DSound(soundName, false, this);

    _timeDelay += TimeSpan.FromSeconds(1.25f);
  }
}

猫绕原点旋转,因此它们使用三角函数来移动圆的轨迹。 因此 位置、 方向速度 会逐个更改。 因此,当您运行程序时,您会发现猫的叫声在旋转,而无需移动相机。

_timeDelay 后续代码是定期播放猫的随机声音。 audioManager.Play3DSound 将发射器本身传递给方法,因此 你可以看到声音的播放位置一个接一个地改变。

AudioManager 类

最后,这是音频 3D 的本质。 3D 声音由听声音的人(听众)和发出声音的人(发射器)组成。 由于发射器已由狗或猫定义,因此音频管理器类定义侦听器。 通常有多个发射器,而只有一个侦听器。

此类 GameComponent 继承 ,并在Game 类中注册和使用组件。

ActiveSound 类

/// <summary>
/// アクティブな3Dサウンドを追跡し、アタッチされているエミッターオブジェクトを記憶するための内部ヘルパークラス。
/// </summary>
private class ActiveSound
{
  public SoundEffectInstance Instance;
  public IAudioEmitter Emitter;
}

在代码底部定义的类,用于保存正在播放的状态。 它具有正在播放的声音实例和正在播放的发射器的信息。

字段

// このマネージャーにロードされるすべてのサウンドエフェクトのリスト。
static string[] _soundNames =
  {
    "CatSound0",
    "CatSound1",
    "CatSound2",
    "DogSound",
  };

/// <summary>音を聞くリスナーの情報です。これは通常カメラに一致するように設定されます。</summary>
public AudioListener Listener { get; } = new AudioListener();

/// <summary>AudioEmitter は、3Dサウンドを生成しているエンティティを表します。</summary>
readonly AudioEmitter _emitter = new AudioEmitter();

/// <summary>再生可能なすべての効果音を保存します。</summary>
readonly Dictionary<string, SoundEffect> _soundEffects = new Dictionary<string, SoundEffect>();

/// <summary>現在再生中のすべての3Dサウンドを追跡します。また、再生が終わったインスタンスの破棄にも使用します。</summary>
readonly List<ActiveSound> _activeSounds = new List<ActiveSound>();

_soundNames 定义要导入的音频文件名(资产名称)。 因为它是一个示例程序,我定义它首先批量加载音频文件。 将导入的声音数据存储在 _soundEffects 中。

AudioListener Listener 是侦听器的定义。 侦听器信息在 中定义,因为它 public 是从游戏类设置的。

AudioEmitter _emitter 是将 3D 应用于声音播放时的发射器定义。 在此示例中,每个发射器对象的值 _emitter 在播放声音时设置为 ,因此实例共享一个。 当然,您可以为每个 AudioEmitter 对象设置 。

_activeSounds 包含有关您刚刚定义的 ActiveSound 类正在播放的声音的信息。 发射器信息会不断更改,因此用于更新位置等,并丢弃已播放的声音实例。

构造函数

public AudioManager(Game game) : base(game) { }

GameComponent 由于类是继承的,因此 Game 它接收实例并将其传递给基类。

Initialize 方法

/// <summary>
/// オーディオマネージャを初期化します。
/// </summary>
public override void Initialize()
{
  // ゲームの世界のスケールと一致するように、3Dオーディオのスケールを設定します。
  // DistanceScale は、離れるにつれて音量が変化する音の量を制御します。
  // DopplerScale は、サウンドを通過するときにピッチが変化するサウンドの量を制御します。
  SoundEffect.DistanceScale = 2000;
  SoundEffect.DopplerScale = 0.1f;

  // すべての効果音をロードします。
  foreach (string soundName in _soundNames)
  {
    _soundEffects.Add(soundName, Game.Content.Load<SoundEffect>(soundName));
  }

  base.Initialize();
}

GameComponent 类继承,因此在初始化时自动调用。

SoundEffect.DistanceScaleSoundEffect.DopplerScale 是专用 static 于 3D 声音的参数。

SoundEffect.DistanceScale 使大,你可以听到的声音,即使在远处。

SoundEffect.DopplerScale 是多普勒效应的影响。 数字越大,多普勒效果越大。

_soundNames 将 中定义的资产名称存储在循环加载中 _soundEffects

Dispose 方法

/// <summary>
/// 効果音データをアンロードします。
/// GameComponent として登録すればゲーム終了時に自動的に呼ばれます。
/// </summary>
protected override void Dispose(bool disposing)
{
  try
  {
    if (disposing)
    {
      foreach (SoundEffect soundEffect in _soundEffects.Values)
      {
        soundEffect.Dispose();
      }

      _soundEffects.Clear();
    }
  }
  finally
  {
    base.Dispose(disposing);
  }
}

GameComponent 类继承,因此在游戏结束时自动调用。

销毁所有导入的声音资产。

更新方法

/// <summary>
/// 3Dオーディオシステムの状態を更新します。
/// </summary>
public override void Update(GameTime gameTime)
{
  // 現在再生中のすべての3Dサウンドをループします。
  int index = 0;

  while (index < _activeSounds.Count)
  {
    ActiveSound activeSound = _activeSounds[index];

    if (activeSound.Instance.State == SoundState.Stopped)
    {
      // 音の再生が終了した場合は廃棄してください。
      activeSound.Instance.Dispose();

      // アクティブリストから削除します。
      _activeSounds.RemoveAt(index);
    }
    else
    {
      // サウンドがまだ再生されている場合は、3D設定を更新します。
      Apply3D(activeSound);

      index++;
    }
  }

  base.Update(gameTime);
}

GameComponent 类继承,因此在更新过程中自动调用。

检查当前正在播放的声音,如果正在播放,则 Apply3D 调用 方法以更新声音的位置以匹配发射器。 如果播放完任何声音,则销毁实例。

播放 3D 声音方法

/// <summary>
/// 新しい3Dサウンドを設定し再生します。
/// </summary>
public SoundEffectInstance Play3DSound(string soundName, bool isLooped, IAudioEmitter emitter)
{
  ActiveSound activeSound = new ActiveSound();

  // インスタンスを生成し、インスタンス、エミッターを設定します。
  activeSound.Instance = _soundEffects[soundName].CreateInstance();
  activeSound.Instance.IsLooped = isLooped;

  activeSound.Emitter = emitter;

  // 3D 位置を設定します。
  Apply3D(activeSound);

  activeSound.Instance.Play();

  // このサウンドがアクティブであることを保存します。
  _activeSounds.Add(activeSound);

  return activeSound.Instance;
}

从外部播放 3D 声音的过程。 参数具有要播放的声音的名称、是否循环以及发射器信息。

如果要播放声音,请从_soundEffects 中保存的声音资源创建新的声音实例。 SoundEffectInstance 设置循环信息,因为可以有一个循环。

由于正在创建 作为 ActiveSound 正在播放的状态,因此将发射器信息设置为声音的播放对象。 如果要将 3D 位置应用于声音, Emitter 请获取 所需的值。

Apply3D 方法如下所述,但您将从发射器将 3D 信息应用于声音。

3D 声音的基本处理是在设置 3D 信息后 SoundEffectInstance.Play 开始播放。 之后,您将定期更新 3D 信息,直到播放完声音。

应用 3D 方法

/// <summary>
/// 3Dサウンドの位置と速度の設定を更新します。
/// </summary>
private void Apply3D(ActiveSound activeSound)
{
  _emitter.Position = activeSound.Emitter.Position;
  _emitter.Forward = activeSound.Emitter.Forward;
  _emitter.Up = activeSound.Emitter.Up;
  _emitter.Velocity = activeSound.Emitter.Velocity;

  activeSound.Instance.Apply3D(Listener, _emitter);
}

使用侦听器和发射器将 3D 效果应用于指定的声音实例。

在这里,我们每次都为公共实例的 _emitter 设置值。 如果已 AudioEmitter 提供 ,则 SoundEffectInstance.Apply3D 只需将侦听器和发射器传递给 方法即可。

Audio3DGame 类

最后,我将解释游戏类的内容。

字段

readonly GraphicsDeviceManager _graphics;
readonly AudioManager _audioManager;
readonly SpriteEntity _cat;
readonly SpriteEntity _dog;

/// <summary>地面の描画に使用するテクスチャーです。</summary>
Texture2D _checkerTexture;

QuadDrawer _quadDrawer;

Vector3 _cameraPosition = new Vector3(0, 512, 0);
Vector3 _cameraForward = Vector3.Forward;
Vector3 _cameraUp = Vector3.Up;
Vector3 _cameraVelocity = Vector3.Zero;

KeyboardState _currentKeyboardState;
GamePadState _currentGamePadState;

AudioManager 注册 GameComponents ,但作为字段保留,以便单独访问。

其他人定义狗、猫实体、地面绘图纹理、矩形绘制辅助对象、摄像机信息和输入信息。

构造函数

public Audio3DGame()
{
  Content.RootDirectory = "Content";

  _graphics = new GraphicsDeviceManager(this);

  _audioManager = new AudioManager(this);

  // AudioManager を Components に追加して自動的に Update メソッドが呼ばれるようにします。
  Components.Add(_audioManager);

  _cat = new Cat();
  _dog = new Dog();
}

AudioManagerGameComponents 并注册 到 。

您还生成了 Cat 和 Dog 类。

LoadContent 方法

/// <summary>
/// グラフィックコンテンツをロードします。
/// </summary>
protected override void LoadContent()
{
  _cat.Texture = Content.Load<Texture2D>("CatTexture");
  _dog.Texture = Content.Load<Texture2D>("DogTexture");

  _checkerTexture = Content.Load<Texture2D>("checker");

  // 四角形ポリゴンを描画するためのクラス
  _quadDrawer = new QuadDrawer(_graphics.GraphicsDevice);
}

加载每个绘图所需的纹理。

QuadDrawer 需要 GraphicsDevice ,因此在此处生成。

更新方法

/// <summary>
/// ゲームがロジックを実行できるようにします。
/// </summary>
protected override void Update(GameTime gameTime)
{
  HandleInput();

  UpdateCamera();

  // 新しいカメラの位置について AudioManager に伝えます。
  _audioManager.Listener.Position = _cameraPosition;
  _audioManager.Listener.Forward = _cameraForward;
  _audioManager.Listener.Up = _cameraUp;
  _audioManager.Listener.Velocity = _cameraVelocity;

  // ゲームエンティティに動き回って音を鳴らすように伝えます。
  _cat.Update(gameTime, _audioManager);
  _dog.Update(gameTime, _audioManager);

  base.Update(gameTime);
}

HandleInput 方法和 UpdateCamera 方法是获取设备输入和摄像机操作,如下所述。

在大多数情况下,摄像机的位置和侦听器的位置相同,因此在摄像机更新过程后,侦听器将具有相同的值。

_cat 调用 和 _dog Update 方法以分别移动和处理振铃。

绘制方法

/// <summary>
/// ゲームが描画する必要があるときに呼び出されます。
/// </summary>
protected override void Draw(GameTime gameTime)
{
  var device = _graphics.GraphicsDevice;

  device.Clear(Color.CornflowerBlue);

  device.BlendState = BlendState.AlphaBlend;

  // カメラ行列を計算します。
  var view = Matrix.CreateLookAt(_cameraPosition, _cameraPosition + _cameraForward, _cameraUp);
  var projection = Matrix.CreatePerspectiveFieldOfView(1, device.Viewport.AspectRatio, 1, 100000);

  // チェッカーグラウンドポリゴンを描画します。
  var groundTransform = Matrix.CreateScale(20000) * Matrix.CreateRotationX(MathHelper.PiOver2);

  _quadDrawer.DrawQuad(_checkerTexture, 32, groundTransform, view, projection);

  // ゲームエンティティを描画します。
  _cat.Draw(_quadDrawer, _cameraPosition, view, projection);
  _dog.Draw(_quadDrawer, _cameraPosition, view, projection);

  base.Draw(gameTime);
}

从摄像机信息生成视图和投影转换。

对于地面,在四绘图器中绘制的矩形是垂直的,因此它们以水平方式旋转以适度放大。 _quadDrawer.DrawQuad 方法的第二个参数指定纹理的重复次数,并指定 32x32 件并排可见。

_cat 和 _dog 的内部广告牌处理,因此我们通过传递摄像机位置信息来绘制它们。

HandleInput 方法

/// <summary>
/// ゲームを終了するための入力を処理します。
/// </summary>
void HandleInput()
{
  _currentKeyboardState = Keyboard.GetState();
  _currentGamePadState = GamePad.GetState(PlayerIndex.One);

  // 終了を確認します。
  if (_currentKeyboardState.IsKeyDown(Keys.Escape) ||
      _currentGamePadState.Buttons.Back == ButtonState.Pressed)
  {
    Exit();
  }
}

获取键盘和游戏板信息,并确定游戏结束。

UpdateCamera 方法

/// <summary>
/// カメラを動かすための入力を処理します。
/// </summary>
void UpdateCamera()
{
  const float turnSpeed = 0.05f;
  const float accelerationSpeed = 4;
  const float frictionAmount = 0.98f;

  // 左または右に曲がります。
  float turn = -_currentGamePadState.ThumbSticks.Left.X * turnSpeed;

  if (_currentKeyboardState.IsKeyDown(Keys.Left)) turn += turnSpeed;
  if (_currentKeyboardState.IsKeyDown(Keys.Right)) turn -= turnSpeed;

  _cameraForward = Vector3.TransformNormal(_cameraForward, Matrix.CreateRotationY(turn));

  // 前方または後方に加速します。
  float accel = _currentGamePadState.ThumbSticks.Left.Y * accelerationSpeed;

  if (_currentKeyboardState.IsKeyDown(Keys.Up)) accel += accelerationSpeed;
  if (_currentKeyboardState.IsKeyDown(Keys.Down)) accel -= accelerationSpeed;

  _cameraVelocity += _cameraForward * accel;

  // 現在の位置に速度を追加します。
  _cameraPosition += _cameraVelocity;

  // 摩擦力を加えます。
  _cameraVelocity *= frictionAmount;
}

摄像机的移动控制。

它还包括加速度和摩擦等计算,但您可能还考虑了 3D 声音中的多普勒效果。 我省略了解释,因为计算本身没有特别之处。

总结

我详细介绍了如何播放 AudioManager 3D 声音,但类通常就行了。 事实上,我们所做的只是 SoundEffectInstance 将 3D 信息设置为 ,因此框架几乎可以执行大多数困难操作。 如果只将 3D 声音部分放在示例中,则只需在 Game 类中编写一些处理即可。 然而,它变得很长,因为我写提示的基础上,原来的故事。