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

アイテムを出現させよう

今回はアイテムを出現させてみましょう。

画像については以下のものを使います。




一つ目と三つ目、二つ目と四つ目の画像を組み合わせて描画します。
一つ目と二つ目の描画は回転させながら描画することにします。
Pと書いてあるのはパワーアップ形のアイテムで、
Sと書いてあるのは得点アップ系のアイテムにします。

次にITEMクラスを作成します。


class ITEM{
private:
	//グラフィックハンドル
	static int gh[2],ghs[2];

	//どっちのアイテムかを示す変数
	int type;

	//座標
	double x,y;
	double prev_y;

	//回転角度
	double rad;

	//カウント
	int count;

	//アイテムが落下し始めるフラグ
	bool fall_flag;

	//出現中かどうかのフラグ
	bool flag;

private:
	void Move();
	void Draw();
public:
	ITEM();
	void SetFlag(double x,double y,int type);
	bool GetFlag();
	void Delete();
	void GetPosition(double *x,double *y);
	int  GetType();
	void All();
};
//コンストラクタ
ITEM::ITEM()
{
	if(gh[0]==0){
		//0が青色で得点系、1が黄色でパワーアップ系
		gh[0] = LoadGraph("item1.png");
		gh[1] = LoadGraph("item2.png");
		ghs[0] = LoadGraph("items1.png");
		ghs[1] = LoadGraph("items2.png");

	}
	x=y=0;

	rad=0;

	count=0;

	fall_flag=false;

	flag=false;

}

変数については書いてあるとおりです。
コンストラクタで画像の読み込みや変数の初期値の設定を行います。
グラフィックハンドルについては、static指定子をつけているので、
インスタンス内で共有されます。
値が0の時、つまり初回だけ読み込むようにします。

次にアイテムの動きの部分のコードです。
色々な出現のさせ方がありますが、
最初勢いよく上に飛び出して、以降はゆっくり落下していく感じの動作にさせます。
その動作を担っているMove関数のコードです。

void ITEM::Move()
{

	double tempy;

	//0.04ラジアン(約2度)ずつ回転させる。
	rad=0.04*count;

	++count;

	if(!fall_flag){
		tempy=y;
		y+=(y-prev_y)+1;
		//頂点までいったらフラグ立てる
		if((y-prev_y)+1==0){
			fall_flag=true;
		}
		prev_y = tempy;
	}else{
		//落下時は一定速度で落下
		y+=1.5;
	}

	//画面の外にはみ出たらフラグを戻す。
	if(y>500){
		Delete();
	}
}

まず、円形のギザギザ画像は回転させる必要があるので、
徐々に増加していくラジアンの値が必要になります。
変数radには0.04(約二度)×countの値を代入することで、
1ループごとに約2度ずつ増加させながら値を代入することができます。
これで回転はOKです。

次にif文がありますが、fall_flagによって分岐しています。
fall_flagというのは、アイテムが勢いよく飛び出して、落下し始めると立つフラグです。
立っていないときは、アイテムを上昇させるコードを書いています。
ここの原理は、ゲームプログラミング「キャラをジャンプさせよう」の応用です。
上記のリンクを見ていただくと分かりますが、
ジャンプをして一番上まで到達した瞬間に、(y-prev_y)+1の値は0になります。
つまりこの瞬間にフラグを戻してやればよいということになります。

これでアイテムを飛び出させるコードは書けました。
次は落下する部分(else文)の方のコードです。
落下時は単純に一定速度で下げていくだけなので、Y座標を単純に増加させているだけです。
(Y座標は下がプラスなので足してます)

最後にアイテムが画面からはみ出たらフラグを戻す必要があります。
X座標方向にアイテムが移動するようにはしていないので、
Y座標だけで判定しています。
Yが500の座標を超えるとほぼはみ出したと判断できるので、その時にDelete関数を実行しています。
Delete関数は以下のようになっています。

void ITEM::Delete()
{
	count=0;
	fall_flag=false;
	flag=false;
}

単純にカウント、落下フラグ、出現中のフラグを元に戻しているだけです。
ここまででアイテムの移動部分のコードについては書けました。
次に描画部分のコードです。

void ITEM::Draw()
{
	DrawRotaGraph(x,y,1.0,rad,gh[type],TRUE);
	DrawRotaGraph(x,y,1.0,0,ghs[type],TRUE);
}


一つ目のDrawRotaGraphはギザギザの円盤の描画です。
取得した座標と回転角度のradを渡しています。
またグラフィックハンドルについては、添え字に変数typeを使っています。
これはアイテムの種類を表す値が入っています。
後で説明しますが、SetFlag関数というもので座標やアイテムの種類を設定するのですが、
その時に設定されるものです。
アイテムは2種類しかないので今のところ0か1の値が入っています。
0が得点系、1がパワーアップ系のアイテムを表します。
画像を読み込むときに、この値に対応する順番で読み込んでいるので、
そのままtypeの値を指定するだけで、目的の画像が描画できるようになっています。
二つ目のDrawRotaGraphは、SとかPとかの文字のことです。
これは単純に描画するだけなので、回転もさせずにただそのまま描画しています。

辺り判定部分のコードを説明する前に、
このアイテムクラスの座標を設定したり、取得したりする部分の関数のコードを貼っておきます。

void ITEM::SetFlag(double x,double y,int type)
{
	this->x=x;
	this->y=y-8;
	prev_y=y;
	this->type=type;

	flag=true;
}

bool ITEM::GetFlag()
{
	return flag;
}

void ITEM::GetPosition(double *x,double *y)
{
	*x = this->x;
	*y = this->y;
}

int ITEM::GetType()
{
	return type;
}

void ITEM::All()
{
	Move();
	Draw();
}

最初のSetFlag関数ですが、引数に指定された座標や、アイテムの種類を示すtypeなどの代入を行っています。
y座標をマイナス8しているのは、ジャンプの説明のリンクにもあるとおり、
ジャンプするように勢いよく飛び出させるには、初回だけ少し大きめの値を引いておく必要があります。
そのために8を引いてるだけです。
それ以外の関数については、座標、フラグ、アイテムの種類を取得したり、セットしたりする関数です。

当たり判定部分のコードを書く前に、以下の定数を先にdefine.hで定義しておきます。

//アイテムの当たり判定用半径
#define ITEM_COLLISION 16
//アイテムの総数
#define ITEM_NUM 30

単純にアイテムの当たり判定用の半径と、アイテムクラスの総数です。
30としているのは、一画面中に最大でも30個ぐらいまでしか表示しないだろうという予測から30としているだけです。
また、コードは省略しますが、
CONTROLクラスでこれらのインスタンスの生成と、アイテム取得音の音声ファイルの読み込み、
再生部分のコードを新たに追加しています。
さらに、BOARDクラスにも、現在のパワー数を表示するための、
文字と数値を描画するようにコードを追加しています。
この辺は特にコードは一緒で中身だけ変えただけなので説明は省略します。

では、当たり判定部分のコードを見てみましょう。

void CONTROL::CollisionAll()
{
	double px,py,ex,ey;

	bool tempflag=false;
	bool gtempflag=false;

	//操作キャラの弾と敵との当たり判定
	for(int i=0;i<PSHOT_NUM;++i){
		if(player->GetShotPosition(i,&px,&py)){
			for(int s=0;s<ENEMY_NUM;++s){
				//敵クラスのポインタがNULLじゃない、かつdeadflagがfalse(死んでない&帰還してない)
				if(enemy[s]!=NULL && !enemy[s]->GetDeadFlag()){
					enemy[s]->GetPosition(&ex,&ey);
					//当たり判定
					if(CircleCollision(PSHOT_COLLISION,ENEMY1_COLLISION,px,ex,py,ey)){
						//当たっていれば、deadflagを立てる
						enemy[s]->SetDeadFlag();
						//当たった弾のフラグを戻す
						player->SetShotFlag(i,false);
						//敵消滅音フラグセット
						edead_flag=true;
						//敵消滅エフェクトセット
						EnemyDeadEffect(ex,ey);
						//得点を加える
						score->SetScore(CURRENT_SCORE,100);
						//アイテム出現
						for(int z=0;z<ITEM_NUM;++z){
							if(!item[z]->GetFlag()){
								item[z]->SetFlag(ex,ey,enemy[s]->GetItem());
								break;
							}
						}
					}
				}
			}
		}
	}


	//敵の弾と操作キャラとの当たり判定
	//プレイヤーが生きてれば
	if(!player->GetDamageFlag()){
		player->GetPosition(&px,&py);
		for(int i=0;i<ENEMY_NUM;++i){
			if(enemy[i]!=NULL){
				for(int s=0;s<ENEMY_SNUM;++s){
					//弾フラグが立っていればtrueを返す
					if(enemy[i]->GetShotPosition(s,&ex,&ey)){
						//弾によって当たり判定が違うのでswitch文で分岐
						switch(enemy[i]->GetShotType()){
							case 0:
								//グレイズ判定
								if(CircleCollision(GRAZE_COLLISION,ESHOT0_COLLISION,px,ex,py,ey)){
									gtempflag=true;
								}
								//通常の当たり判定
								if(CircleCollision(PLAYER_COLLISION,ESHOT0_COLLISION,px,ex,py,ey)){
									tempflag=true;
								}
								break;

							case 1:
								if(CircleCollision(GRAZE_COLLISION,ESHOT1_COLLISION,px,ex,py,ey)){
									gtempflag=true;
								}
								if(CircleCollision(PLAYER_COLLISION,ESHOT1_COLLISION,px,ex,py,ey)){
										tempflag=true;
								}
								break;

							case 2:
								if(CircleCollision(GRAZE_COLLISION,ESHOT2_COLLISION,px,ex,py,ey)){
									gtempflag=true;
								}
								if(CircleCollision(PLAYER_COLLISION,ESHOT2_COLLISION,px,ex,py,ey)){
										tempflag=true;
								}
								break;
						}
						//グレイズ当たり判定フラグがtrueなら
						if(gtempflag){
							//まだ
							if(!enemy[i]->GetGrazeFlag(s)){
								enemy[i]->SetGrazeFlag(s);
								//グレイズのインスタンス検索
								for(int z=0;z<GRAZE_NUM;++z){
									if(!graze[z]->GetFlag()){
										graze[z]->SetFlag(px,py);
										break;
									}
								}
								//グレイズの得点を加える
								score->SetScore(GRAZE_SCORE,1);
								score->SetScore(CURRENT_SCORE,20);
								//グレイズ音セット
								graze_flag=true;
							}
							gtempflag=false;
						}

						if(tempflag){
							//操作キャラのdamageflagを立てる
							player->SetDamageFlag();
							//弾を消す
							enemy[i]->SetShotFlag(s,false);
							//プレイヤー消滅音フラグを立てる
							pdead_flag=true;
							//一時フラグを戻す
							tempflag=false;
						}
					}
				}
			}
		}

		double ix,iy;

		//アイテムとプレイヤーの当たり判定
		for(int i=0;i<ITEM_NUM;++i){
			if(item[i]->GetFlag()){
				item[i]->GetPosition(&ix,&iy);
				if(CircleCollision(PLAYER_COLLISION,ITEM_COLLISION,px,ix,py,iy)){
					switch(item[i]->GetType()){
						case 0:
							score->SetScore(CURRENT_SCORE,300);
							break;
						case 1:
							score->SetScore(POWER_SCORE,1);
							break;
					}
					item[i]->Delete();
					//アイテム取得音をセット
					item_flag=true;
				}
			}
		}
	}

	//ライフは毎回取得
	score->SetScore(LIFE_SCORE,player->GetLife());

}


かなり縦に長くなってしまいましたが、
まず、敵とプレイヤーの弾の当たり判定部分の一番下の部分を見てください。
アイテムの数だけループさせ、フラグがfalse、つまりまだ出現してないインスタンスを探して、
見つかれば、SetFlag関数を使って、座標やアイテムの種類をセットしてます。
アイテムの出現に関してはこれでOKです。

次に、一番下の方にある、アイテムとプレイヤーの当たり判定の部分を見てください。
まずこの部分のコードは、ちょっと分かりづらいですが、
更に上にある、プレイヤーが生きているときだけ実行するというif文に囲まれています。
つまりプレイヤーが生きているときしか実行されないということを頭に置いといてください。
まず最初のforループでアイテムの数だけループさせています。
フラグを取得し、フラグが立っている、つまりアイテムが出現中だった場合、その座標を取得しています。
その後円形の当たり判定用の関数にプレイヤーとアイテムの座標等を渡し、当たり判定を実行しています。
当たっていたら、アイテムの種類によってswitch文で処理を分岐させ、
スコアを設定しています。
種類が0、つまり得点系のアイテムであれば、スコアに300点加算し、
種類が1、つまりパワーアップ系のアイテムであれば、パワーを1増加させています。
最後に、アイテムを取得したらアイテムを消す必要があるので、Delete関数を実行しフラグを戻しています。

あとは、All関数でITEMクラスのインスタンスでフラグが立っているものだけ、
All関数を実行させます。

void CONTROL::All()
{
	//サウンドフラグを初期化
	eshot_flag=pshot_flag=edead_flag=pdead_flag=graze_flag=item_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;
			}
		}
	}

	CollisionAll();

	//グレイズ描画
	for(int i=0;i<GRAZE_NUM;++i){
		graze[i]->All();
	}

	//敵消滅エフェクト
	for(int i=0;i<EFFECT_EDEADNUM;++i){
		if(effect_edead[i]->GetFlag()){
			effect_edead[i]->All();
		}
	}

	//アイテム描画
	for(int i=0;i<ITEM_NUM;++i){
		if(item[i]->GetFlag()){
			item[i]->All();
		}
	}


	//描画領域を指定
	SetDrawArea(0,0,640,480);

	//スコア描画
	score->All();

	SoundAll();

	++g_count;
}


こうすることで、以下の動画のようにアイテムを出現させることができます。

さらにゲームっぽくなってきましたね?
アイテムの種類を増やすとか、アイテムの大きさを変えて獲得ポイントを変えるなどした方が、
更にゲームっぽくなると思います。
今回はその辺は省略します。

今回の説明は以上です。
次回は、プレイヤーのショットをパワーアップさせてみましょう。

>> 【プレイヤーのショットを強化(三方向発射)】に進む
>> シューティングゲーム作成入門トップに戻る