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

音を鳴らしてみよう

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

とりあえず、プレイヤーと敵のショット音を鳴らすようにしてみましょう。
方法としては、プレイヤー、敵クラスに音が鳴ったかどうかを示すフラグ用の変数を持たせます。
そして、そのフラグを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

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

>> 【操作キャラの弾と敵との当たり判定】に進む
>> シューティングゲーム作成入門トップに戻る
●更新履歴
2016/08/16 Java入門ページにページを幾つか追加
2016/04/08 Java入門ページ作成
2016/03/09 メニューレイアウト変更。ブラウザキャッシュのクリアをお願い致します。
2016/03/09 PDOトランザクション、自動コミットモードをオフ追加
2016/03/09 PDO 例外処理 try catch追加
2016/03/09 PDO update文実行追加
2016/03/09 PDO delete文実行追加
2016/03/09 PDO insert文実行追加
2016/03/09 PDO selectでデータを取得、fetchAll、queryメソッド追加
2016/03/09 PDO bindValueとbindParamの違い追加
2016/03/09 PDO prepare プリペアドステートメントの使い方追加
2016/03/04 ソースコードをクリップボードにコピーする機能を追加
2016/03/04 C言語、C++のページのソースコードを一部修正
2014/01/31 C言語関数一覧ページに11ページほど追加
2014/01/31 C言語関数一覧ページに30ページほど追加
2014/01/30 C言語関数一覧ページ作成中
2013/07/01 レイアウト変更に伴いブラウザキャッシュのクリアをお願いします。
2013/07/01 MySQL入門ページ作成
2013/07/01 PHP入門ページにSQLite学習項目追加
2013/06/25 ドメイン変更、レイアウトを一部変更
2013/03/14 レイアウトを一部変更
2012/08/13 C言語よくある課題・宿題ページ開設!
2012/08/13 シューティングゲーム作成第33章追加!
2012/08/11 ドメイン変更&サーバ移設完了
2012/04/21 シューティングゲームプログラミング第2,3章の内容を修正
2012/04/19 シューティングゲームプログラミング第2章の内容を修正
2012/04/03 Googleカスタム検索を設置!
2012/04/03 シューティングゲームプログラミング第32章追加!
2012/04/03 シューティングゲームプログラミング第31章追加!
2012/03/31 サイトをリニューアルしました!
2012/03/25 シューティングゲームプログラミング第30章追加!
2012/03/19 シューティングゲームプログラミング第29章追加!
2012/03/16 シューティングゲームプログラミング第28章追加!
2012/02/27 シューティングゲームプログラミング第27章追加!
2012/02/03 シューティングゲームプログラミング第26章追加!
2012/01/31 シューティングゲームプログラミング第25章追加!
2012/01/20 シューティングゲームプログラミング第23,24章追加!
2012/01/11 シューティングゲームプログラミング第22章追加!
2012/01/05 トップページ、ゲームプログラミング関連のトップページのデザインを変更
2012/01/04 シューティングゲームプログラミング第21章追加!
2012/01/01 シューティングゲームプログラミング第20章追加!
2011/12/25 シューティングゲームプログラミング第19章追加!
2011/12/22 シューティングゲームプログラミング第18章追加!
2011/12/18 シューティングゲームプログラミング第17章追加!
2011/12/17 シューティングゲームプログラミングページOPEN!
2011/11/21 ゲームプログラミングページOPEN!
2011/11/21 サイトデザインを大幅に変更
2011/11/17 TOPページのデザインを変更。相互リンクページに、複数サイト追加。
2011/11/06 WINAPI学習ページ(33~36章)追加
2011/11/05 WINAPI学習ページ(20~32章)追加
2011/10/27 WINAPI学習ページ(14~19章)追加
2011/10/21 WINAPI学習ページ(13章)追加
2011/10/21 サイトマップ、連絡ページ追加
2011/10/17 WINAPI学習ページ(6~11章)追加
2011/10/16 WINAPI学習ページ(1~5章)追加
2011/10/13 全体のレイアウト変更
2011/10/07 PHP学習ページ(8~11章)追加
2011/10/06 PHP学習ページ(1~7章)作成
2011/10/06 JavaScriptリファレンスページ作成
2011/10/05 C言語学習ページ発展編(10~14章)追加
2011/10/04 C言語学習ページ発展編(1~9章)追加。
2011/10/03 HTML/CSSリファレンスのページ追加。(個々の詳細ページは作成中)
2011/09/30 HTML学習ページ(8章)追加
2011/09/29 JavaScript学習ページ(12~17章)追加
2011/09/28 JavaScript学習ページ(1~11章)追加
2011/09/27 HTML学習ページ(4~7章)追加
2011/09/26 C言語学習ページ(27章)追加、C++学習ページ(17章)、HTML学習ページ(1~3章)追加
2011/09/25 C言語学習ページ(23~26章)を追加
2011/09/24 C++学習ページ(9~16章)追加
2011/09/23 C++学習ページ(3~8章)追加
2011/09/22 C言語の学習ページ(22章)とC++学習ページ(1~2章)追加
2011/09/21 C言語の学習ページ(15章~21章)を追加
2011/09/20 C言語の学習ページ(10章~14章)を追加
2011/09/19 サイト作成(随時更新予定)