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

敵に弾を発射させてみよう

今回は敵に弾を発射させてみましょう。

まず、敵に弾を発射させるための構造体を用意します。
define.hで下記の構造体を定義しています。

struct E_SHOT{
	bool flag;//弾が発射中かどうか
	double x;//x座標
	double y;//y座標
	int gh;//グラフィックハンドル
	int width,height;//画像の幅と高さ
	int pattern;//ショットパターン
	int speed;//弾スピード
};

#define ENEMY_SNUM 50

メンバの説明はコメントで記入してあるとおりです。
ショットのパターンもいくつか用意しようと思ったので、patternという変数を持たせました。
define定義しているENEMY_SNUMというのは、
敵が弾を何発所持しているかという上限数を表しています。

この構造体を敵クラスに持たせるのですが、
他にも以下のように変数を増やしてます。

#include "pch.h"

class ENEMY{
private:
	//座標とグラフィックハンドル
	double x,y;
	int gh[3];

	//画像サイズ
	int width,height;

	//出現、停止、帰還、発射タイミング
	int in_time,stop_time,out_time,shot_time;

	//敵の種類
	int type;
	//弾の種類
	int stype;
	//移動パターン
	int m_pattern;
	//ショットパターン
	int s_pattern;

	//敵が出現してからのカウント
	int count;

	
	//敵消滅フラグ
	bool deadflag;
	//敵クラス消滅フラグ
	bool endflag;

	//弾構造体
	E_SHOT shot[ENEMY_SNUM];
	//ショットが撃てるようになったかのフラグ
	bool sflag;
	//ショットが打てるようになってからのカウント
	int scount;


public:
	bool All();
	void Move();
	void Shot();
	void Draw();
	ENEMY(char *c_filename,int type,int m_pattern,int x,int y,int in_time,int stop_time, int shot_time,int out_time,char *s_filename,int stype,int s_pattern,int speed);
};

敵種類と弾種類と敵の移動パターンとショットパターン用の変数を持たせ、
敵消滅フラグというものを新たに持たせました。
また、弾構造体と弾が打てる状態になったかを示すフラグ、
弾が撃てるようになったからのカウント用の変数を持たせました。

関数は新たにShot関数を追加しました。
さらに、コンストラクタで、敵の画像や移動パターン初期座標、出現時間、
弾の設定なども一括でできるようにしています。
引数の順番は、
敵画像のファイル名、敵タイプ、移動パターン、x座標、Y座標、出現時間、停止時間、弾発射時間、帰還時間、
弾画像ファイル名、弾の種類、ショットパターン、弾スピードの順です。
コンストラクタは現在以下のようになっています。

ENEMY::ENEMY(char *c_filename,int type,int m_pattern,int x,int y,int in_time,int stop_time, int shot_time,int out_time,char *s_filename,int stype,int s_pattern,int speed)
{
	//読み込み
	LoadDivGraph(c_filename,3,1,3,27,25,gh);

	//サイズ
	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;

	//弾画像とサイズ取得
	int temp=LoadGraph(s_filename);
	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;

}

仮引数の値をそのまま変数に代入しているだけです。
弾画像もその敵が発射する弾はとりあえず1種類だけにするので、
全ての弾に同じグラフィックハンドルやスピード、ショットパターン等を代入してます。
今回の弾の画像は、

の画像を使います。

次に、新たに増やしたShot関数です。

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

	//フラグ立ってるときだけ
	if(sflag){
		
		switch(s_pattern){
			case 0:
				//10回に一回発射。40までなので5発発射。
				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;
							break;
						}
					}

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

		//フラグ立ってる弾だけ、弾の移動を行う
		for(int i=0;i<ENEMY_SNUM;++i){
			if(shot[i].flag){
				shot[i].y+=shot[i].speed;

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

	}
	
}

まず最初にshot_timeとg_countがイコールになった時、つまり、
発射タイミングが来たら、それを示すsflagをtrueにします。
その次のif文は、sflagがtrueの時しか実行されません。
これによって、弾が発射するまではこのif文内の処理は実行されません。

まず最初にショットパターンによって、switch文で分岐をしています。
まだ最初なのでショットパターンは一つだけにしてます。
ショットパターンが0の時の処理を見てください。

scountを10で割って余りがゼロ、かつscountが40以下の時に処理を実行する、と書いてあります。
scountというのはsflagが立ってからのカウント数です。
これを10で割って余りがゼロということは10ループに一回この条件に当てはまります。
かつ、scountが40以下ということは、
scountが0,10,20,30,40の時だけこのif文の条件に当てはまることになります。
つまり、10ループに一回のタイミングで5回弾が発射されることになります。
次の処理でforループ文で弾の数だけループさせています。
フラグが立っていない弾が見つかったらフラグを立て、初期座標をセットしてます。
初期座標は当然敵が今居る座標になるので、敵の座標をそのまま代入してます。
一発セットできたらもうループする必要はないので、すぐbreakしてます。

ここまでで弾のセットまでの処理はかけました。
次はそのセットされた弾を「移動」させる処理を書く必要があります。

switch文より下の部分を見てください。
変数sはフラグが立ってる弾の数を表す変数です。
まず、forループでループさせ、フラグが立ってる弾だけ処理するようにしています。
今回の弾はただ、下に移動させるだけにするので、
単純にy座標に弾のスピードを足してるだけです。
斜めに飛ばしたりする方法な後の講座で説明します。

その下のif文で、弾が画面からはみ出してないかチェックし、
はみ出していたらフラグを戻すようにしています。
if文の条件は単純に画面の座標から弾の座標がはみ出てないかをチェックしてるだけです。
こうすることで、後で行う当たり判定や描画の処理のところで、不要な弾の処理を減らすことができます。
はみ出てる場合は、フラグはもう立っていないのでsは増加させずにcontinueし、次のループに映っています。

forループが終わった後のif文を見てください。
sが0かつ、deadflagがtrueの時にendflagを立てるようにしています。
deadflagというのは敵が死んだか、画面の外にはみ出た場合にtrueにするようにします。
前回の講座で敵が画面の外にはみ出た場合は以下のように
if(y<-40){
endflag=true;
}
とendflagをtrueにしていましたが、
endflagをtrueにすると、敵クラスも消滅する仕様にしていたので、
このままでは発射中の弾も突然消えてしまうことになります。
それでは不自然ですので、この部分の処理は、
if(y<-40){
deadflag=true;
}
とdeadflagをtrueにするようにしています。

つまり、sがゼロかつ、deadflagがtrueということは、
発射中の弾が画面上にない、かつ敵が死んでいるか画面の外に退避している時、ということになります。
その時にendflagをtrueにします。
これなら、敵クラスを消滅(解放)させても問題ありませんよね?

次の処理で、弾発射フラグが立ってからのカウント数を示すscountをインクリメントさせてます。

最後に描画部分の処理です。
Draw関数は以下のようになっています。

void ENEMY::Draw()
{
	int temp;

	//弾から最初に描画
	for(int i=0;i<ENEMY_SNUM;++i){
		if(shot[i].flag){
			DrawGraph(shot[i].x,shot[i].y,shot[i].gh,true);
		}
	}


	if(!deadflag){

		temp= count%40/10;
		if(temp==3)
			temp=1;

		DrawGraph(x,y,gh[temp],TRUE);
	}
}

ここは特に難しくありません。
敵を描画する前に弾を描画しています。
弾を後から描画して敵が隠れてしまったら、わけわからなくなりますからね。
処理は単純です。
forループで弾数分ループさせ、フラグがたっているものだけ描画しているだけです。

なお、現在のAll関数と、controlクラスのコンストラクタは以下のようになっています。
●All関数

bool ENEMY::All()
{
	Move();

	Shot();

	Draw();

	++count;

	return endflag;
}

●CONTROLクラスのコンストラクタ

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

	back = new BACK;

	enemy = new ENEMY("enemy.png",0,50,-40,120,180,181,360,"enemyshot1.png",0,4);

}

以上のコードを書くと、以下の動画のように敵が出現して、弾を発射させることができます。

当たり判定はまだないので、当然弾は当たりません。
今回の説明は以上です。
次回は敵を複数出現させてみます。

>> 【敵を複数出現させてみよう】に進む
>> シューティングゲーム作成入門トップに戻る