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

敵を複数出現させてみよう

前回は敵を一匹だけ出現させましたが、
今回は複数出現させてみましょう。

ENEMYクラスのコンストラクタで引数に初期値を指定すれば簡単にインスタンスを
生成できるようにはしましたが、何十匹も出現するとなると、同じことを何行も書かなければならなくなるので、
コードがかなり長くなってしまいます。

ですので、それを簡略化するために外部データを読み込んで、それを変数に入れてやるようにしましょう。
そうすれば、コードも短くてスマートに書くことができます。
エクセルで作るのが一番わかりやすくて簡単です。

今回は下記の画像のようにエクセルでデータをまとめました。

今後当たり判定やアイテムなども追加していくので、敵のHPやアイテムなどの列も追加してます。
これをCSVファイルで保存しておきます。

次にこのファイルを読み込んで、データを変数に保存しましょう。
保存用の構造体を以下のように定義します。

struct ENEMYDATA{
	int type;//敵種類
	int stype;//弾種類
	int m_pattern;//移動パターン
	int s_pattern;//発射パターン
	int in_time;//出現時間
	int stop_time;//停止時間
	int shot_time;//弾発射時間
	int out_time;//帰還時間
	int x;//x座標
	int y;//y座標
	int speed;//弾スピード
	int hp;//HP
	int item;//アイテム
};

#define ENEMY_NUM 5

エクセルで並べてあるとおりの順番で定義しています。
define定義しているENEMY_NUMというのは敵データがいくつあるかを示しています。
今回はとりあえず5にしてます。
次にこれを読み込みましょう。
下記のコードを見てください。

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);
	}


}


CSVの読み込み方法ですが、これは人によって読み込み方法が色々あるのでざっとだけ説明します。
まず、最初にwhileループで改行までよみ、ヘッダ部分を読み飛ばしています。
以降は、CSVの区切り文字であるカンマか改行文字が見つかるまで、バッファに文字を溜めて、
構造体の各メンバに代入しているだけです。
カンマや構造体が来たら、一つのセル分の文字列が連結できたということなので、
構造体にその値を代入した後は、列数を示す変数colの値を1増やしてます。
これによってswitch分の分岐が変わり、2列目は弾種類、3列目は移動パターン、
というふうに代入することができます。
また、読み込んだ文字が改行の時は、行の最初から読み込むことになるので、
行数を増やし、列数を初期化してます。
これを末尾のEOFまで繰り返しています。
EOFが来たらgo to文でループを抜けています。

これでファイルからの読み込みは終わったので、これらの値を使って敵クラスのインスタンスを生成します。
ENEMYクラスのコンストラクタも今回の変更に伴って以下のように変更してます。

ENEMY::ENEMY(int type,int stype,int m_pattern,int s_pattern,int in_time,int stop_time,int shot_time,int out_time,int x,int y,int speed,int hp,int item)
{
	

	//サイズ
	width=27;
	height=25;
	
	//敵の種類
	this->type=type;
	this->stype=stype;

	//移動パターンとショットパターン
	this->m_pattern=m_pattern;
	this->s_pattern=s_pattern;

	this->x=x;
	this->y=y;

	this->in_time=in_time;

	this->stop_time=stop_time;

	this->shot_time=shot_time;

	this->out_time=out_time;

	//hpとアイテム代入
	this->hp=hp;
	this->item=item;

	//敵画像読み込み
	if(type==0){
		LoadDivGraph("enemy.png",3,1,3,27,25,gh);
	}

	int temp;
	//弾画像読み込み
	if(stype==0){
		temp=LoadGraph("enemyshot1.png");
	}

	int w,h;
	GetGraphSize(temp,&w,&h);

	for(int i=0;i<ENEMY_SNUM;++i){
		shot[i].flag=false;
		shot[i].gh=temp;
		shot[i].width=w;
		shot[i].height=h;
		shot[i].pattern=s_pattern;
		shot[i].speed=speed;
		shot[i].x=x;
		shot[i].y=y;
	}

	count=0;
	scount=0;

	deadflag=false;
	endflag=false;
	sflag=false;

}


なお、コンストラクタの引数の順番も先程の構造体の順番と同じにしてあります。
このほうがわかりやすいですよね。
画像の読み込み部分だけ見てください。
敵画像と弾画像の種類を示す、typeとstypeの値によって読み込む画像を変更しています。
今は一種類だけですが、画像を増やす場合はここのif文を追加するか、
switch文に変更するとよいでしょう。

このようなコンストラクタに変更し、上記のようにENEMYクラスのインスタンスを生成すると、
以下の動画のように敵を複数出現させることができます。

ちょっとゲームっぽくなってきましたかね?
なお、今回の変更に伴って、CONTROLクラスのAll関数と、デストラクタを以下のように変更してます。

void CONTROL::All()
{

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

	back->All();
		
	player->All();
	
	for(int i=0;i<ENEMY_NUM;++i){
		if(enemy[i]!=NULL){
			if(enemy[i]->All()){
				delete enemy[i];
				enemy[i]=NULL;
			}
		}
	}

	

	++g_count;
}

CONTROL::~CONTROL()
{
	delete player;
	delete back;

	for(int i=0;i<ENEMY_NUM;++i){
		if(enemy[i]!=NULL){
				delete enemy[i];
		}
	}
}

敵クラスが複数になったので、All関数実行時の処理を
forループで複数実行できるようにしたぐらいです。
デストラクタは解放時に敵クラスを複数解放できるようにしただけです。

今回の説明は以上になります。
csvの読み込みさえクリアすればそんなに難しくありませんよね?
次回は敵の移動パターンを増やしてみましょう。

>> 【敵の移動パターンを増やそう】に進む
>> シューティングゲーム作成入門トップに戻る