画像(logo)

HOME/[C言語DXライブラリ]STGの作り方 目次/八日目 当たり判定

広告

[C言語 DXライブラリ STGの作り方]
八日目 当たり判定

広告

↓2016年02月29日発売↓

12歳からはじめる ゼロからのC言語 ゲームプログラミング教室

目次へ戻る

当たり判定

今回は当たり判定をつけてみます。

使い方はけっこう簡単なので先にプログラムから紹介します。

業界では有名な当たり判定だそうです。

プレイヤーや敵の構造体にあった当たり判定範囲「range」と体力を表す「power」を使います。

void my_init_player(){
	p1.x = 0;
	p1.y = -200;
	p1.draw_x = 0;
	p1.draw_y = 0;
	p1.speed = 4;
	p1.shot_type = 0;
	p1.bomb = 1;
	p1.power = 1;
	p1.life = 3;
	p1.range = 10;
}

とりあえずプレイヤーは体力「1」、当たり判定範囲を「10」に設定してありますがこのへんは敵の当たり判定範囲なども含めお好みで調整してください。

当たり判定

では当たり判定プログラムを見てみましょう。

まずはプレイヤーと敵がお互い体当たりした場合の当たり判定です。

お互いの距離と当たり判定の範囲を入れる為の変数「double x,y,range」を用意した上で、

x = p1.x - enemy[i].x;
y = p1.y - enemy[i].y;
range = p1.range + enemy[i].range;
if (x * x + y * y < range * range){
	p1.power--;
	enemy[i].power--;
}

こちらが当たり判定プログラムになります。

思ったより難しそうですね・・・。

計算が見慣れてないとちょっとややこしそうですが一つ一つ見ていけばやってる事はスゴく簡単なので心配しないでください。

まずはお互いの距離を調べます。

x = p1.x - enemy[i].x;
y = p1.y - enemy[i].y;

この部分ですね。

お互いの距離といっても直線ではなく「x」と「y」の距離をそれぞれ出します。

上記の計算では「プレイヤー 引く 敵」になってますが「敵 引く プレイヤー」でも大丈夫です。

次にお互いの当たり判定の範囲「range」を足します。

range = p1.range + enemy[i].range;

こちらですね。後はこの3つ「x」「y」「range」を使って計算するだけです。

if (x * x + y * y < range * range){
	/*当たっています!!!*/
}

「「x」の2乗 + 「y」の2乗 が 「range」の2乗より小さければ」

お互いの当たり判定範囲が重なってる、ぶつかっているという事になるので

お互いの体力「power」を減らせば当たり判定処理完了です。

当たり判定部分を関数に

このお互いの距離、当たり判定範囲を比べる計算は何度も登場するので関数にしてしまいます。

int my_pythago_theorem(double x,double x2,double y,double y2,double range,double range2){
	double x3 = x - x2;
	double y3 = y - y2;
	double range3 = range + range2;
	if (x3 * x3 + y3 * y3 < range3 * range3){
		return 1;
	}
	else{
		return 0;
	}
}		

ちょっと引数が多いですがさきほどの計算に使ったそれぞれのx座標、y座標、当たり判定範囲を渡して同じように計算しているだけです。

当たっていれば「return 1」、そうでなければ「return 0」を返します。

「my_pythago_theorem」ピタゴ・・・なんちゃらとか名付けられておりますね・・・。

そうです!この当たり判定にはかの有名なピタゴラスの定理が使われております。

このへんについては余談として後ほどまた説明したいと思います。

それぞれの当たり判定

それでは基本的な当たり判定のやり方がわかったトコロでそれぞれの部分に当たり判定を適用していきます。

ほとんど似たような処理になりますので例として「プレイヤー」と「敵」の当たり判定、「プレイヤーショット」と「敵」の当たり判定2つだけを例に説明します。

「プレイヤー」と「敵」の当たり判定

void my_collision_player_enemy(){
	for(int i = 0;i < DISP_ENEMY_MAX;i++){
		if(enemy[i].move_flag == 1 && enemy[i].init_flag == 1 && p1.power > 0){
			if(my_pythago_theorem(p1.x,enemy[i].x,
				p1.y,enemy[i].y,p1.range,enemy[i].range)){
				p1.power--;
				enemy[i].power--;
			}
		}
	}
}

最初に説明した「プレイヤー」と「敵」の当たり判定を関数化したものです。

あらためて説明します。

for(int i = 0;i < DISP_ENEMY_MAX;i++){
	if(enemy[i].move_flag == 1 && enemy[i].init_flag == 1 && p1.power > 0){
	}
}

まず全ての「敵の入れ物」の中から今現在初期化が済まされていて、尚且つ画面上で動いていている敵を探します。

ついでにプレイヤーの体力「p1.power」が「0」以上の時という条件も重ねておきます。

そして該当する敵が見つかった時は先ほどの当たり判定関数を使って当たっているかどうかを調べていきます。

if(my_pythago_theorem(p1.x,enemy[i].x,p1.y,enemy[i].y,p1.range,enemy[i].range)){
	/*当たっていた時の処理*/
}

あとは当たっていた時の処理を中に書けば完了ですね!

p1.power--;
enemy[i].power--;

「プレイヤー」と「敵」の当たり判定なので「プレイヤー」と「敵」両方の体力を引きます。

「プレイヤーショット」と「敵」の当たり判定

続いて「プレイヤーショット」と「敵」の当たり判定です。

void my_collision_player_shot_enemy(){
	for(int i = 0;i < DISP_ENEMY_MAX;i++){
		if(enemy[i].move_flag == 1 && enemy[i].init_flag == 1){
			for(int j = 0;j < PLAYER_SHOT_MAX;j++){
				if(ps1[j].move_flag == 1 && ps1[j].init_flag == 1){
					for(int k = 0;k < ps1[j].max_bullet;k++){
						if(ps1[j].flag[k] == 1 && ps1[j].x[k] > LEFT_LIMIT && ps1[j].x[k] < RIGHT_LIMIT &&
							ps1[j].y[k] > BOTTOM_LIMIT && ps1[j].y[k] < TOP_LIMIT){
							if(my_pythago_theorem(enemy[i].x,ps1[j].x[k],
								enemy[i].y,ps1[j].y[k],enemy[i].range,ps1[j].range)){
								enemy[i].power--;
								ps1[j].flag[k] = 0;
							}
						}	
					}
				}
			}
		}
	}
}

何やら少し複雑になりました。

先にも言いましたが「2way」・「5way」と分かれる「プレイヤーショット」一つ一つに当たり判定をつけるのでその分少し複雑になります。

for(int i = 0;i < DISP_ENEMY_MAX;i++){
	if(enemy[i].move_flag == 1 && enemy[i].init_flag == 1){
	}
}

まず同じように動いている「enemy[i].move_flag == 1」かつ初期化済み「enemy[i].init_flag == 1」の敵を探します。

該当する敵がいた場合、今度は同じように「プレイヤーショット」の方を調べます。

for(int j = 0;j < PLAYER_SHOT_MAX;j++){
	if(ps1[j].move_flag == 1 && ps1[j].init_flag == 1){
	}
}

ここまではわかりやすいですね。

ここからさらに「プレイヤーショット」は一発だけとは限らないのでその時の最大弾数分を調べます。

for(int k = 0;k < ps1[j].max_bullet;k++){
}

ここで終わりかと思いきや最後に「画面内に収まっている」かつ最初に少しだけ説明した「ps1[j].flag[k] == 1」というプレイヤーショットを探します。

if(ps1[j].flag[k] == 1 && ps1[j].x[k] > LEFT_LIMIT && ps1[j].x[k] < RIGHT_LIMIT &&
	ps1[j].y[k] > BOTTOM_LIMIT && ps1[j].y[k] < TOP_LIMIT){
}

見づらいコードで大変申し訳ございません。

ここでようやく当たり判定です。

if(my_pythago_theorem(enemy[i].x,ps1[j].x[k],enemy[i].y,ps1[j].y[k],enemy[i].range,ps1[j].range)){
		enemy[i].power--;
		ps1[j].flag[k] = 0;
}

「enemy[i].power--;」と敵の体力を減らすと共に例のフラグ「ps1[j].flag[k] = 0」も落として当たり判定処理完了です。

ps1[j].flag[k]

それではここで例のフラグ「ps1[j].flag[k] = 0;」についての説明をします。

プレイヤーショットを作った時の事を思い出して頂きたいのですが、プレイヤーショットを飛ばしてから一定時間後にその飛ばしたプレイヤーショットを消去するのに

ps1[i].move_flag = 0;
ps1[i].init_flag = 0;

という2つのフラグを落としたのを覚えておりますでしょうか?

この2つのフラグを落とす事により再び「プレイヤーショットの入れ物」を未使用の状態にして再び使いまわせるようにするワケです。

ではプレイヤーショットが敵に当たった時にプレイヤーショットを消滅させたいとして同じようにすればいいかと言うと、ここが「2way」・「5way」ショットを飛ばす場合は少し工夫が必要になるというワケなんですね。

プレイヤーショットの構造体をあらためてご覧ください。

struct PLAYER_SHOT{
	double x[5];
	double y[5];
	double draw_x[5];
	double draw_y[5];
	int init_flag;
	int move_flag;
	int move_type;
	int flag[5];
	double angle[5];
	int max_bullet;
	int gamecount_point;
	double range;
};

「double x[5];」・「double y[5];」のように座標を5つもっていますよね。

この状態で

ps1[i].move_flag = 0;
ps1[i].init_flag = 0;

こちらのフラグでプレイヤーショットを消去しようとするとなんと!5つの座標分まるごと消えてしまうという事になるのです!

例えば「5way」ショットを打っていたとしたら敵と当たっていない関係ないトコロまで一緒に消えてしまうのです!

その為その座標一つ一つにフラグを設定してそのフラグだけを落とす事により見た目の上では当たったショットだけ消えるという事を実現しているというワケなんですね!

敵の消去

それでは話を戻しまして、続いてはダメージを受けて体力が「0」になった敵の処理を説明します。

void my_check_enemy_power(){
	for(int i = 0;i < DISP_ENEMY_MAX;i++){
		if(enemy[i].move_flag == 1 && enemy[i].init_flag == 1){
			if(enemy[i].power <= 0){
				enemy[i].move_flag = 0;
				enemy[i].init_flag = 0;
			}
		}	
	}
}

単純に全ての「敵の入れ物」の中から今動いている敵を探してその中から体力「enemy[i].power」が「0」の敵がいた場合は

if(enemy[i].power <= 0){
	enemy[i].move_flag = 0;
	enemy[i].init_flag = 0;
}

とフラグを落として消去しているだけですね。

当たり判定をまとめる

あとはこれらの当たり判定を並べるだけです。

void my_collision_detection(){
	my_collision_player_enemy();
	my_collision_player_shot_enemy();
	my_collision_player_enemy_shot();
	my_check_enemy_power();
}

当たり判定をまとめた「my_collision_detection()」になります。

あとあとここに他の当たり判定などを同じように加えていきます。

これでここまでの当たり判定は完了です。

プレイヤーの死亡処理に関してはステージ遷移の時に一緒に説明しますので少々お待ちください。

画像(cdxs_8_1)

当たり判定完了!

ピタゴラスの定理

知っている方も知らない方も。

ではここからはなぜ上の方法で当たり判定がわかるのか?をお話します。

ピタゴラスの定理

例えば次の場合のプレイヤーと敵との直線距離

画像(cdxs_8_2)

これを知りたい時はピタゴラスの定理(三平方の定理)を使います。

これは次の直角三角形があったとして

画像(cdxs_8_3)

それぞれの長さの関係は

「底辺の2乗 + 高さの2乗 = 斜辺の2乗」

こんな関係になりますよってのが「ピタゴラスの定理」ってものになります。

そういえば、これどこかで見た気がしますよね!

それぞれ長さを次のように置き換えて

画像(cdxs_8_4)

プログラム式に書き換えてみると

「x * x + r * r = range * range」

正しいプログラムではないですが、今までやっていた計算みたいになりました!

この事から今までやっていた計算はまさしくお互いの直線距離を表すものだったのです。

それでいて例えば今から会いに行きたい友達が100メートル離れた場所にいたとして、その友達が半径100メートルの少し太った方だともうすぐに会えますよね。

つまりはその二人の当たり判定範囲分を合わせた分

画像(cdxs_8_5)

これが実際の二人の距離を超えちゃった場合

if (x * x + y * y < range * range)

この場合が衝突してるという事になるんですね!

そもそもピタゴラスの定理がわからない!という方は近くの数学に詳しい方に聞いてください。

ここまでの中間ソースになります。

中間ソース7

次回は爆発エフェクトを作ります。

次回

九日目 爆発エフェクト

□ページの先頭へ□

□目次へ戻る□

□HOME□

広告

↓2017年06月16日発売↓

やさしいC 第5版 (「やさしい」シリーズ)

新品価格
¥2,700から
(2017/5/1 13:05時点)

↓2014年08月09日発売↓

新・明解C言語 入門編 (明解シリーズ)

新品価格
¥2,484から
(2017/5/1 13:08時点)

↓2016年02月20日発売↓

新・解きながら学ぶC言語

新品価格
¥2,160から
(2017/5/1 13:10時点)

↓2017年02月11日発売↓

C言語プログラミング基本例題88 88

新品価格
¥3,024から
(2017/5/1 13:12時点)

↓2016年12月15日発売↓

Cの絵本 第2版 C言語が好きになる新しい9つの扉

新品価格
¥1,490から
(2017/5/1 13:13時点)

↓2017年02月08日発売↓

新・明解C言語で学ぶアルゴリズムとデータ構造 (明解シリーズ)

新品価格
¥2,700から
(2017/5/1 13:15時点)