本文
GitHub : https://github.com/foriver4725/ForiverEngine
低レイヤーの勉強目的で、
DirectX12 を使ってマイクラをフルスクラッチで作っています。
コードをコピペせず、なるべく手入力で作成。
今日はサウンド再生の機能を作りました。
サウンドをロードして再生。同じ音源を何回も重ねて再生できます。
SoundHelper クラスを作成して、サウンドクリップデータは ISound というインターフェースに抽象化しています。
Windows の IXAudio2 を用いていますが、ロジックを密結合させたくなかったので、PImpl というテクニックを用いて、実装を .cpp 側に隠蔽しています。
これはこの実装で学んだのですが、D3D12Defines.h でも同じようにした方が良かったかも。
private:
struct Impl; // 中身はここでは定義しない
std::unique_ptr<Impl> impl; // 実装を所有する
内部実装は、API 部分は Microsoft のチュートリアルをコピペ。
RAII で初期化/解放をしています。
Impl()
{
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
return;
coInitialized = true;
if (FAILED(::XAudio2Create(&xAudio2)))
return;
if (FAILED(xAudio2->CreateMasteringVoice(&masteringVoice)))
return;
initializeSucceeded = true;
}
~Impl()
{
if (masteringVoice != nullptr)
{
masteringVoice->DestroyVoice();
masteringVoice = nullptr;
}
if (xAudio2 != nullptr)
{
xAudio2->Release(); // NOTE: 戻り値は無視している
xAudio2 = nullptr;
}
if (coInitialized)
{
CoUninitialize();
coInitialized = false;
}
}
個々のサウンドは、ロードすると IXAudio2SourceVoice というオブジェクトになるのですが、これだと Play() するたびに構築コストがかかってしまう。
ということで、ゲームプログラミングの基本のき、オブジェクトプーリングでキャッシュするようにしました。
使っていないかどうかは、このように判定できます。
// 使えるものがあればそれを返す
for (auto& sourceVoice : sourceVoices)
{
XAUDIO2_VOICE_STATE state{};
sourceVoice->GetState(&state);
if (state.BuffersQueued == 0)
return sourceVoice.get();
}
テスト用サウンドを Codex に作ってもらって、確認。
いい感じ! ![]()
今後やってみたいこと
- 個々の再生されているサウンドをStopしたい
- ブロック採掘とかでSEを鳴らしたいので
- ループ再生したい
- 波形データを Scriptable に生成したい
- ファイル読み込み、チャンク分割をリファクタリングする(主に異常系の処理)。ただしモチベーションは低い