クラスまみれのゲームプログラミング入門

31:効果音の同時再生(1)

 ミサイルの発射など、短い効果音では特に気にならないのですが、同じ長めの効果音を連続して再生しようとすると、そのたびにはじめから再生され、同時に再生することができません。だからといって、再生する効果音の分だけIDirectSoundBufferを作成して領域を確保するのも無駄なような気がします。

 DirectXはいろいろと考えられているようで、音楽データだけは同じだけど、再生制御は個別に行う機能が用意されています。それがIDirectSound8::DuplicateSoundBuffer()です。これを利用すると、無駄にメモリを消費せずに、同じ効果音を同時に再生することができるようになります。複製されたデータをRelease()で解放しても、最後のひとつが解放されるまでデータは保持されるので、何回データをロードして、何回削除したかなんてことをプログラムで考える必要はありません。

 では、クラスからクラスへ複製する方法を考えてみましょう。おそらく真っ先に思いつくのは

subst.cpp
CSound sound(_T("sample.wav"));
CSound sound2 = sound;

のように、「=」を使って代入する方法でしょう。しかしこのままでは、DuplicateSoundBuffer()を呼び出すことができないので、「=」を使って代入が行われたときの処理をクラス内に記述することにしましょう。

 代入が行われたときの処理の宣言は次のように行います。

[クラス名]& operator=(const [クラス名]&);

 今回はCSoundクラス内で宣言を行うので、CSoundクラス内には

CSound& operator=(const CSound&);

と記述することになります。一方、処理の実行部分では

operator.cpp
CSound& CSound::operator =(const CSound &s)
{
	RELEASE(pDSBuffer);
	if(pDSound) pDSound->DuplicateSoundBuffer(s.pDSBuffer, &pDSBuffer);

	return *this;
}

 となります。3行目で念のための解放処理を行い、4行目でIDirectSoundBufferの複製処理を行っています。戻り値は自分自身(thisの実体)です。

 これで「=」を使ってクラスを代入したときの処理が完成したのですが、「=」を使ってもこの処理が実行されないケースがひとつだけあります。それはクラスの宣言と代入を同時に行っているときです。

CSound sound2 = sound(_T("sample.wav"));

 上記のように代入を行った場合、operator=による代入演算の関数は呼び出されず、コピーコンストラクタという独特の関数が呼び出されます。コピーコンストラクタと代入演算は似て非なるもので、コピーコンストラクタ用の宣言と処理は個別に記述する必要があります。その宣言と処理は以下の通りとなります。

copycons.h
// コピーコンストラクタの宣言
class CSound : public CGameObject
{
public:
	CSound(const CSound&);
}

// コピーコンストラクタの処理
CSound::CSound(const CSound &s)
{
	RELEASE(pDSBuffer);
	if(pDSound) pDSound->DuplicateSoundBuffer(s.pDSBuffer, &pDSBuffer);
}

 実際にクラスに埋め込んだら、このような感じになります。

Sound.h
class CSound : public CGameObject
{
private:
	/* 省略 */

public:
	/* 省略 */

	CSound(const CSound&);
	CSound& operator=(const CSound&);

	/* 省略 */
};

Sound.cpp
CSound::CSound(const CSound &s)
{
	RELEASE(pDSBuffer);
	if(pDSound) pDSound->DuplicateSoundBuffer(s.pDSBuffer, &pDSBuffer);
}

CSound& CSound::operator =(const CSound &s)
{
	RELEASE(pDSBuffer);
	if(pDSound) pDSound->DuplicateSoundBuffer(s.pDSBuffer, &pDSBuffer);

	return *this;
}

 例えばこのようにプログラミングすれば、ファイル単位ではなく、キャラクター単位で効果音を発生させることができるようになります。

playsample.cpp
// 効果音の再生
shootsound = *(CSound*)FindObject("tama");
shootsound.Play();