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

操作キャラから弾を発射してみよう

今回は操作キャラから弾を発射してみましょう。

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

struct SHOT{
	bool flag;//弾が発射中かどうか
	double x;//x座標
	double y;//y座標
	int gh;//グラフィックハンドル
	int width,height;//画像の幅と高さ
};


弾が発射中かどうかを示すフラグと、座標、グラフィックハンドル、画像のサイズ用の変数を持たせています。
フラグを持たせたのは、現在発射中の弾だけ描画するためです。
画面からはみ出した弾はもう描画する必要はありませんよね?
それに当たり判定等でも使う必要がないので、それを区別するために持たせてます。

操作キャラから発射する弾はとりあえず1列だけにします。
アイテムなどを取ったら弾が拡散して攻撃できたりするゲームがほとんどですが、
まずは初期段階なので一列だけにします。大分後の講座でその辺の要素は付け加えていきます。

発射する弾の数ですが、一画面中に何弾発射してればよいかという視点から考えます。
画面中に20発以上の弾が溢れることはないので、20発定義しておけば弾切れもないと思うので、
20発定義することにします。
player.hではこの弾構造体を20個配列として定義しています。

class PLAYER{
private:
	//x座標,y座標
	double x,y;

	//画像幅
	int width,height;

	//グラフィックハンドル格納用配列
	int gh[12];


	//移動係数
	float move;

	//横方向と縦方向のカウント数。
	int xcount,ycount;
	//添字用変数
	int ix,iy,result;

	//生きてるかどうかのフラグ
	bool life;

	//弾
	SHOT shot[PSHOT_NUM];

	//カウント
	int count;

private:
	void Move();
	void Draw();
	void Shot();

public:
	PLAYER();
	void All();

};


PSHOT_NUMという定数はdefine.hで20と定義しています。
こういう弾の最大数などの値は定数として定義しておくと後から変更があった場合に都合が良いです。

次に、コンストラクタで画像の読み込み等の処理を行います。

PLAYER::PLAYER()
{
	//画像読み込み
	
	if(-1==LoadDivGraph("charall.png",12,3,4,29,40,gh)){
		MSG("エラー発生");
	}

	width=29;
	height=40;
		

	//移動係数
	move=1.0f;

	//横方向と縦方向のカウント数。
	xcount=0,ycount=0;
	//添字用変数
	ix=0,iy=0,result=0;

	//初期位置
	x=180;
	y=400;

	//生きてるかどうか
	life=true;

	//弾初期化
	memset(shot,0,sizeof(shot));


	//弾画像読み込み
	int temp=LoadGraph("shot.png");
	int w,h;
	GetGraphSize(temp,&w,&h);

	//フラグを全部falseにしとく
	//グラフィックハンドルと画像のサイズを代入しとく
	for(int i=0;i<PSHOT_NUM;++i){
		shot[i].flag=false;
		shot[i].gh=temp;
		shot[i].width=w;
		shot[i].height=h;
	}

	count=0;


}


下の方に書いてある「弾初期化」と「弾画像読み込み」と書いてある部分が該当のコードです。
弾画像は、

を使います。
まず画像のグラフィックハンドルとサイズを取得しています。
次にforループ文で、弾数分だけループさせて、それぞれの要素の値を設定しています。
弾はまだ発射してないので、flagはfalse,
グラフィックハンドルはどの弾も一緒なので、先程読み込んだtempの値を指定してます。
width,とheightも先程取得しといた画像のサイズを代入しています。

これで下準備はできました。
次に、実際に弾を発射させる部分のコードと弾を移動させる部分のコードの説明をします。
新たに、Shot関数を作りました。

void PLAYER::Shot()
{
	//キーが押されててかつ、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;
			}
		}
	}

	//弾を移動させる処理
	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;
			}
		}
	}


}


弾はZキーで発射するようにします。
最初のif文でZキーが押されていて、かつcountを6で割って余りが0ならば、処理をする、
という条件になっています。
countという変数は、毎ループごとに1ずつ増えていくカウント用変数です。
All関数でインクリメントさせています。
つまり、このcountを6で割って余りが0の時というのは、
6ループに一回やってくることになります。
毎ループごとに弾を発射してしまうと、弾と弾の間隔が近すぎてしまいますからね。

その次に弾をセットするためにforループに入っています。
ここでflagがfalse、つまり発射されてない弾を見つけたら、
フラグをtrueにして初期座標を設定しています。
初期座標は操作キャラの中心から発射するので、操作キャラの座標をそのまま代入しています。

ここまでで弾のセット部分のコードは出来ました。
次に弾の移動部分のコードです。

その下のコードで、弾の数だけまたforループでループさせています。
ここでフラグがtrue、つまり弾が発射中のやつだけ、
y座標からPSHOT_SPEEDの値だけ引いています。
PSHOT_SPEEDはdefine.hで14と定義しています。
つまり毎回14ずつ上に移動するということです。

これで弾の移動までは出来ましたが、
次に弾が画面の外にはみ出たらフラグを戻してやる必要があります。
それが、その下のif文です。
画面の外にはみ出るということは、弾は上に飛んでいくので、
y座標が画面の上端よりも越えたらはみ出たということが分かります。
上端のy座標は余白分の10ですが、弾画像の高さもありますので、
余裕をもって-10よりも下回ったら、フラグを戻すようにしています。
こうすることで、弾が完全に画面から消えてから、フラグが戻されることになります。

ここまでで弾の移動制御に関する処理は全てかけました。
後は描画するだけです。
描画部分とAll関数は以下のようになっています。

void PLAYER::Draw()
{

	//弾描画
	for(int i=0;i<PSHOT_NUM;++i){
		if(shot[i].flag){
			DrawGraph(shot[i].x-shot[i].width/2,shot[i].y-shot[i].height/2,shot[i].gh,TRUE);
		}
	}

	//生きてれば描画
	if(life){
		//描画
		DrawGraph(x-width/2,y-height/2,gh[result],TRUE);
	}
}

void PLAYER::All()
{
	Move();
	Shot();
	Draw();

	++count;
}


キャラの描画よりも先に弾の描画を行っています。
弾を後から描画すると、キャラの上に被ってしまい違和感があるからです。
弾の描画は、forループで弾数分だけループさせ、
発射中の弾、つまりflagがtrueのやつだけ描画するようにしています。
指定している座標も配列の各要素の座標を指定しています。

All関数もShot関数を加えただけで特に変更はありません。
先程説明したカウント用のcount変数を加えて、インクリメントさせてるだけです。

こうすることで以下の動画のように操作キャラから弾を発射することが出来るようになります。

今回の説明は以上です。
次回は敵を表示させてみます。

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