>> シューティングゲーム作成入門トップに戻る

音を鳴らしてみよう

今回は音を鳴らす方法を説明します。

とりあえず、プレイヤーと敵のショット音を鳴らすようにしてみましょう。
方法としては、プレイヤー、敵クラスに音が鳴ったかどうかを示すフラグ用の変数を持たせます。
そして、そのフラグをCONTROLクラスから取得し、
フラグが立っていたら音を鳴らす、という仕組みにします。
ですので、音声ファイルはCONTROLクラスで読み込むことにします。
また、敵が複数居ますので、1ループ中に複数の敵が発射する可能性もあります。
その数だけ鳴らしてても処理が無駄になるだけですし、同じ音なので1度ならせばOKなはずです。
ですので、CONTROLクラスにもフラグを持たせ、フラグが立っているときだけ鳴らすようにコードを組みます。
こうすれば、1ループにつき1回だけ鳴らすことができるようになります。
現在のCONTROLクラスのヘッダと、コンストラクタを見てください。
●ヘッダ

#include "player.h"
#include "back.h"
#include "enemy.h"

class CONTROL{

	//プレイヤークラス
	PLAYER *player;

	//背景クラス
	BACK *back;

	//敵クラス
	ENEMY *enemy[ENEMY_NUM];

	//サウンドハンドル
	int s_eshot;
	int s_pshot;

	//サウンドを鳴らすかどうかのフラグ
	bool eshot_flag;
	bool pshot_flag;

private:
	CONTROL();
	~CONTROL();
	void SoundAll();
public:
	void All();
	void GetPlayerPosition(double *x,double *y);
	void GetEnemyPosition(int index,double *x,double *y);
	static CONTROL& GetInstance(){
		static CONTROL control;
		return control;
	}
};

●コンストラクタ

CONTROL::CONTROL()
{
	player = new PLAYER;

	back = new BACK;

	FILE *fp;
	ENEMYDATA data[ENEMY_NUM];
	char buf[100];
	int c;
	int col=1;
	int row=0;

	memset(buf,0,sizeof(buf));
	fp = fopen("enemydata.csv","r");

	//ヘッダ読み飛ばし
	while(fgetc(fp)!='\n');

	while(1){

		while(1){

			c=fgetc(fp);

			//末尾ならループを抜ける。
			if(c==EOF)
				goto out;

			//カンマか改行でなければ、文字としてつなげる
			if(c!=',' && c!='\n')
				strcat(buf,(const char*)&c);
			//カンマか改行ならループ抜ける。
			else
				break;
		}
		//ここに来たということは、1セル分の文字列が出来上がったということ
		switch(col){
			//1列目は敵種類を表す。atoi関数で数値として代入。
			case 1:	data[row].type=atoi(buf); break;
			//2列目は弾種類。以降省略。
			case 2: data[row].stype=atoi(buf); break;
			case 3: data[row].m_pattern=atoi(buf); break;
			case 4: data[row].s_pattern=atoi(buf); break;
			case 5: data[row].in_time=atoi(buf); break;
			case 6: data[row].stop_time=atoi(buf); break;
			case 7: data[row].shot_time=atoi(buf); break;
			case 8: data[row].out_time=atoi(buf); break;
			case 9: data[row].x=atoi(buf); break;
			case 10: data[row].y=atoi(buf); break;
			case 11: data[row].speed=atoi(buf); break;
			case 12: data[row].hp=atoi(buf); break;
			case 13: data[row].item=atoi(buf); break;
		}
		//バッファを初期化
		memset(buf,0,sizeof(buf));
		//列数を足す
		++col;

		//もし読み込んだ文字が改行だったら列数を初期化して行数を増やす
		if(c=='\n'){
			col=1;
			++row;
		}
	}
out:

	//敵クラス生成
	for(int i=0;i<ENEMY_NUM;++i){
		enemy[i]=new ENEMY(data[i].type,data[i].stype,data[i].m_pattern,data[i].s_pattern,data[i].in_time,data[i].stop_time,data[i].shot_time,
			data[i].out_time,data[i].x,data[i].y,data[i].speed,data[i].hp,data[i].item);
	}


	//サウンドファイル読み込み
	s_eshot=LoadSoundMem("enemyshot.wav");
	s_pshot=LoadSoundMem("playershot.wav");

	eshot_flag=false;
	pshot_flag=false;

}

サウンドハンドル用の変数、
int s_eshot;
int s_pshot;
と、
サウンドを鳴らすかどうかのフラグを表す
bool eshot_flag;
bool pshot_flag;
を宣言してます。
コンストラクタでは音声ファイルを読み込み、フラグをfalseに設定してます。

次にプレイヤーと敵側のクラスのコードです。
それぞれ、ショット音が鳴ったかどうかを示すフラグ変数s_shotを持たせて、
それぞれのshot関数でショットを発射したときだけ、フラグを立てるように設定しています。
●プレイヤークラスのshot関数

void PLAYER::Shot()
{
	s_shot=false;

	//キーが押されててかつ、6ループに一回発射
	if(key[KEY_INPUT_Z]==1 && count%6==0){
		for(int i=0;i<PSHOT_NUM;++i){
			if(shot[i].flag==false){
				shot[i].flag=true;
				shot[i].x=x;
				shot[i].y=y;
				break;
			}
		}
		//ショットサウンドフラグを立てる
		s_shot=true;
	}

	//弾を移動させる処理
	for(int i=0;i<PSHOT_NUM;++i){
		//発射してる弾だけ
		if(shot[i].flag){
			shot[i].y-=PSHOT_SPEED;

			//画面の外にはみ出したらフラグを戻す
			if(shot[i].y<-10){
				shot[i].flag=false;
			}
		}
	}
}

●ENEMYクラスのshot関数

void ENEMY::Shot()
{

	//CONTROLクラスの参照
	CONTROL &control = CONTROL::GetInstance();
	double px,py;

	
	//発射タイミングになったら、フラグを立てる
	if(shot_time==g_count){
		sflag=true;
	}

	//フラグ立ってるときだけ
	if(sflag){
		//ショット音フラグを戻す
		s_shot = false;

		//プレイヤーの一取得
		control.GetPlayerPosition(&px,&py);

		//敵とプレイヤーとの座標の差から逆正接を求める。
		//初回だけ実行
		if(scount==0)
			rad=atan2(py-y,px-x);

		
		switch(s_pattern){
			//前方にショット
			case 0:
				//5ループに一回発射。20までなので5発発射。
				if(scount%5==0 && scount<=20){
					for(int i=0;i<ENEMY_SNUM;++i){
						//フラグが立ってない弾を探して、座標等をセット
						if(shot[i].flag==false){
							shot[i].flag=true;
							shot[i].x=x;
							shot[i].y=y;
							shot[i].rad=rad;
							break;
						}
					}
					//ショットサウンドフラグを立てる
					s_shot=true;

				}
				break;

			//プレイヤーに向かって直線ショット
			case 1:
				//6ループに一回発射。54までなので10発発射。
				if(scount%6==0 && scount<=54){
					for(int i=0;i<ENEMY_SNUM;++i){
						//フラグが立ってない弾を探して、座標等をセット
						if(shot[i].flag==false){
							shot[i].flag=true;
							shot[i].x=x;
							shot[i].y=y;
							shot[i].rad=rad;
							break;
						}
					}
					//ショットサウンドフラグを立てる
					s_shot=true;

				}
				break;

			//3直線ショット
			case 2:
				//10ループに一回発射。1ループに3発なので5ループさせると15発発射
				if(scount%10==0 && scount<=40){
					for(int i=0;i<ENEMY_SNUM;++i){
						//フラグが立ってない弾を探して、座標等をセット
						if(shot[i].flag==false){
							shot[i].flag=true;
							shot[i].x=x;
							shot[i].y=y;
							
							//0はキャラクターに向かって発射
							if(num==0){

								//敵とプレイヤーとの逆正接から30度引いたラジアンを代入
								shot[i].rad=rad-(10*3.14/180);

							}else if(num==1){
								//敵とプレイヤーとの逆正接を代入
								shot[i].rad=rad;
								
							}else if(num==2){
								//敵とプレイヤーとの逆正接から30度足したラジアンを代入
								shot[i].rad=rad+(10*PI/180);

							}
								++num;

							//3発発射したら,numを0にしてループを抜ける。
							if(num==3){
								num=0;
								break;
							}
						}
					}
					//ショットサウンドフラグを立てる
					s_shot=true;

				}
				break;

			//乱射ショット
			case 3:
				//10ループに一回発射。42までなので15発発射。
				if(scount%3==0 && scount<=42){
					for(int i=0;i<ENEMY_SNUM;++i){
						//フラグが立ってない弾を探して、座標等をセット
						if(shot[i].flag==false){
							shot[i].flag=true;
							shot[i].x=x;
							shot[i].y=y;
							//初回だけ乱数初期化
							if(num==0)
								srand((unsigned int)time(NULL));

							shot[i].rad=atan2(py-y,px-x)-(60*PI/180)+((rand()%120)*PI/180);
							++num;
							break;
						}
					}
					//ショットサウンドフラグを立てる
					s_shot=true;

				}
				break;



		}
		
		
		//フラグが立ってる弾の数
		int s=0;

		//フラグ立ってる弾だけ、弾の移動を行う
		for(int i=0;i<ENEMY_SNUM;++i){
			if(shot[i].flag){
				switch(shot[i].pattern){
					//単純に下に発射
					case 0:
						shot[i].y+=shot[i].speed;
						break;

					case 1:
						shot[i].x+=shot[i].speed*cos(shot[i].rad);
						shot[i].y+=shot[i].speed*sin(shot[i].rad);
						break;
					case 2:
						shot[i].x+=shot[i].speed*cos(shot[i].rad);
						shot[i].y+=shot[i].speed*sin(shot[i].rad);
						break;
					case 3:
						shot[i].x+=shot[i].speed*cos(shot[i].rad);
						shot[i].y+=shot[i].speed*sin(shot[i].rad);
						break;
						
				}

				//弾が画面をはみ出たらフラグを戻す。
				if(ShotOutCheck(i)){
					shot[i].flag=false;
					continue;
				}
				++s;
			}
		}
		//sがゼロということは発射中の弾がない。
		//かつdeadflagがTRUEということはこの敵のクラスは消滅させてもよい
		if(s==0 && deadflag){
			//敵クラス消滅フラグをTRUEにする
			endflag=true;
		}
		
		++scount;

	}
	
}

両方とも、まずs_shotフラグをfalseにしています。
もし前回音が鳴っていたらフラグが立ったままですからね。
playerクラスの方は、単純に弾が発射した時にフラグを立てています。

ENEMYクラスの方も、最初のswitch文の中のそれぞれのショットパターンごとに
弾を発射したタイミングでフラグを立てる処理を書いています。

これで弾が発射したときにフラグを立てるようにしたので、
このフラグをCONTROLクラスから取得するために、フラグ取得用の関数GetShotSound関数を作っています。

bool PLAYER::GetShotSound()
{
	return s_shot;
}

単純にショットサウンドフラグを戻り値として返しているだけです。
敵クラスにも同じものを定義しています。
後はこれらをcontrolクラスより呼び出して、音を鳴らすだけです。
ControlクラスのAll関数と、音声再生用に作成した関数SoundAllを見てください。

void CONTROL::All()
{
	//サウンドフラグを初期化
	eshot_flag=pshot_flag=false;

	//描画領域を指定
	SetDrawArea(MARGIN,MARGIN,MARGIN+380,MARGIN+460);

	back->All();
		
	player->All();
	//プレイヤーショットサウンドフラグチェック
	if(player->GetShotSound()){
		pshot_flag=true;
	}
	
	for(int i=0;i<ENEMY_NUM;++i){
		if(enemy[i]!=NULL){
			//敵ショットサウンドフラグチェック
			if(enemy[i]->GetShotSound()){
				eshot_flag=true;
			}

			if(enemy[i]->All()){
				delete enemy[i];
				enemy[i]=NULL;
			}
		}
	}

	SoundAll();

	++g_count;
}

void CONTROL::SoundAll()
{
	
	if(pshot_flag){
		PlaySoundMem(s_pshot,DX_PLAYTYPE_BACK);
	}

	if(eshot_flag){
		PlaySoundMem(s_eshot,DX_PLAYTYPE_BACK);
	}

}

All関数では単純にGetShotSound関数を呼び出し、
フラグが立っていれば、それぞれのフラグを立てるようにしています。

その下のSoundAll関数では、フラグが立っていれば、単純に音声を鳴らしているだけです。
All関数内で敵クラスの方はforループでループさせてGetShotSound関数を複数回実行していますが、
複数の敵でサウンドフラグがたっていたとしても、ただeshot_flagを上書きするだけです。
SoundAll関数でそのeshot_flagが立っていれば一回だけ再生するだけなので、
無駄なサウンドは再生してないことがわかります。

これらの関数を追加して、プログラムを実行すると、以下の動画のように音を鳴らすことが出来ます。

さらにゲームっぽくなってきましたよね?
音がしょぼいのは勘弁してくださいw

今回の説明は以上です。
次回は操作キャラの弾と敵との当たり判定について説明します。

>> 【操作キャラの弾と敵との当たり判定】に進む
>> シューティングゲーム作成入門トップに戻る