Siv3Dで効果音の音量を調節して臨場感を出す方法


Siv3D Advent Calendar 2016 8日目の記事です。

Siv3Dを使ってゲームを作っていると効果音を使う機会はあると思います。
しかし、効果音を何十個も同時再生するゲームの場合、うるさすぎて聞くに堪えません笑
そこで、今日は複数の効果音を音量調節することで臨場感を出す方法を紹介していきたいと思います。
技術的な記事を書くのは初めてなので、変なところがあったらスミマセン。。。

# どういう方法を使うのか
現実世界では音量は距離の二乗に反比例します。
そこで、ゲームの中にマイクのような座標があると仮定して、音源からの距離を求め音量を決定させます。
3Dの場合はカメラ座標がマイクの座標で問題ないですが、2Dだとカメラがないため、3Dのようにカメラ座標があると仮定する必要があります。

#具体的なプログラム
音は目に見えるものではないので、実際にプログラムを実行してみましょう。

```
# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	Camera2D camera;

	const Sound sound(L"Example/風の丘.mp3");
	if (!sound) return;	//ロードに失敗した場合

	sound.play();

	while (System::Update())
	{
		camera.update();
		{
			const auto t1 = camera.createTransformer();


			sound.setVolume(camera.getScale()*0.1);

			Circle(320, 240, 100).draw(Palette::Red);
		}
		camera.draw(Palette::Orange);
	}
}
```

マウスホイールを回転させると音量が変わるのがわかると思います。

今回は2Dで描画位置を変更させるためにCamera2Dを利用していきます。
このプログラムでは毎フレームごとにカメラのスケールを取得して音量を調節しています。
しかし、これでは左右に移動した場合でも同じ音量です。

#座標によって音量を変えてみる

```
# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	Camera2D camera;

	const Sound sound(L"Example/風の丘.mp3");
	if (!sound) return;	//ロードに失敗した場合

	sound.play();

	while (System::Update())
	{
		camera.update();
		{
			const auto t1 = camera.createTransformer();

			const Vec3 cameraPos(camera.getPos(), 320 / camera.getScale());

			const Vec2 speakerPos(320, 240);
		
			const double volume = Pow(1 / Vec3(speakerPos, 0).distanceFrom(cameraPos) * 100, 2);

			Window::SetTitle(L"音量 = ",volume);

			sound.setVolume(volume);

			Circle(speakerPos, 100).draw(Palette::Red);
		}
		camera.draw(Palette::Orange);
	}
}
```

かなりそれらしくなったのではないでしょうか。
今度はカメラ座標`cameraPos`と音源座標`speakerPos`を設定し、その距離の二乗を音量`volume`に代入しています。
それぞれの定数を変えることで聞こえ方が変わります。
`const double volume = Pow(1 / Vec3(speakerPos, 0).distanceFrom(cameraPos) * 100, 2);`
の最後に100をかけていますが、これは音源の音量です。

#複数の音源を使ってみる
```
# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	Camera2D camera;

	const Sound sound1(L"Example/風の丘.mp3");
	if (!sound1) return;	//ロードに失敗した場合
	sound1.play();

	const Sound sound2(L"Example/風の丘.mp3");
	if (!sound2) return;	//ロードに失敗した場合
	sound2.play();

	while (System::Update())
	{
		camera.update();
		{
			const auto t1 = camera.createTransformer();

			const Vec3 cameraPos(camera.getPos(), 320 / camera.getScale());

			const Vec2 speakerPos1(120, 240);
			const Vec2 speakerPos2(600, 240);

			const double volume1 = Pow(1 / Vec3(speakerPos1, 0).distanceFrom(cameraPos) * 100, 2);
			const double volume2 = Pow(1 / Vec3(speakerPos2, 0).distanceFrom(cameraPos) * 100, 2);

			Window::SetTitle(L" 音量1 = ",volume1, L" 音量2 = ", volume2);

			sound1.setVolume(volume1);
			sound2.setVolume(volume2);

			Circle(speakerPos1, 100).draw(Palette::Red);
			Circle(speakerPos2, 100).draw(Palette::Blue);
		}
		camera.draw(Palette::Orange);
	}
}
```
近づいたほうの音量だけ上がっていくのがわかると思います。
今回は分かりやすいようにBGMを音源として使いましたが、効果音を対象として使うとより効果的です。

#効果音を使ってみる
```
# include <Siv3D.hpp>
# include <HamFramework.hpp>

struct Speaker
{
	Vec2 Pos = RandomVec2({ -2048, 2048 }, { -2048, 2048 });
	int Count = 0;
};

void Main()
{
	Camera2D camera;

	const Sound sound(L"Example/Sound.mp3");
	if (!sound) return;	//ロードに失敗した場合

	Array<Speaker> speakers;

	for (int i = 0; i < 100; i++)
		speakers.push_back(Speaker());

	while (System::Update())
	{
		camera.update();
		{
			const auto t1 = camera.createTransformer();

			const Vec3 cameraPos(camera.getPos(), 320 / camera.getScale());

			for (auto& speaker : speakers)
			{
				const double volume = Pow(1 / Vec3(speaker.Pos, 0).distanceFrom(cameraPos) * 100, 2);
				if (RandomBool(0.002) && speaker.Count == 0)
				{
					speaker.Count = 30;
					sound.playMulti(volume);
				}

				if (speaker.Count > 0)
				{
					Circle(speaker.Pos, 40 - speaker.Count).drawFrame(1, 0, Color(255, 255, 255, speaker.Count*4));
					Circle(speaker.Pos, 10).draw(Palette::Red);
					--speaker.Count;
				}
				else
				{
					Circle(speaker.Pos, 10).draw(Palette::Green);
				}
			}
		}
		camera.draw(Palette::Orange);
	}
}
```
これなら効果を実感しやすいのではないでしょうか
近いところの音だけが強調されて、まるでその場にいるような感じがでます。
この方法には一つ問題があって、それは一度出した音は音量調節しないため、３秒以上続くような効果音（エンジン音や川の音とか）は使用を避けるか、別にサウンドとして登録して毎フレームごとに音量調節するといいと思います。

#最後に
なぜ、こんなものを作り始めたか、というと現在作っているゲームが大量の効果音を使うものだからです。
https://twitter.com/LPC1768_ にある動画を見てもらえればわかると思います。

この方法は視点移動するゲーム全てに有効で、そこまで難しいことはしていないので使ってみるといいかもしれません。
最後まで見ていただいてありがとうございました。

#.

明日の Siv3D Advent Calendar の記事は @Hatanas さんです。
よろしくお願いします。