ゲーム制作講座1−6

6.敵の当たり判定をつけよう


 前回までに自機が移動、攻撃の一通りの動作が出来るようになりました。
 今回は折角自機が攻撃できるようになったので、敵に弾が当たるようにしたいと思います。

 さて、敵に弾が当たったかを調べるにはどうすればいいでしょうか?
 現在取得できる情報は敵の存在する領域の左上座標、弾の存在する領域の左上座標、あと
敵と弾それぞれのグラフィックサイズです。なんとかこの情報だけで弾と敵が接触しているか
判定してみましょう。

 まず以下の定義をします。

	int ShotX ShotY ShotW ShotH     SikakuX SikakuY SikakuW SikakuH 

	ShotX , ShotY : 弾の存在矩形左上頂点座標のX成分とY成分
	ShotW , ShotH : 弾のグラフィックの幅と高さ

	SikakuX , SikakuY : 敵の存在矩形左上頂点座標のX成分とY成分
	SikakuW , SikakuH : 敵のグラフィックの幅と高さ

 上記の定義のもとに弾と敵が重なるとき成り立つ条件式は以下のものになります。
複雑な概念はありませんので御託を並べるより、式を見て考えた方が理解し易いと思います。

	if( ( ( ShotX > SikakuX && ShotX < SikakuX + SikakuW ) ||
		( SikakuX > ShotX && SikakuX < ShotX + ShotW ) ) &&
		( ( ShotY > SikakuY && ShotY < SikakuY + SikakuH ) ||
		( SikakuY > ShotY && SikakuY < ShotY + ShotH ) ) )
	{
		// 接触している
	}
	else
	{
		// 接触していない
	}

 ごちゃごちゃしていますね…やはり少し解説します。
 まず重なっている条件として、X成分だけで見てもY成分だけで見てもお互いの存在領域が
重なっている
必要があります。

 そして、先程の式の

	( ( ShotX > SikakuX && ShotX < SikakuX + SikakuW ) ||
	( SikakuX > ShotX && SikakuX < ShotX + ShotW ) )

 がX成分の存在領域が重なっているかを判定している式で、

	( ( ShotY > SikakuY && ShotY < SikakuY + SikakuH ) ||
	( SikakuY > ShotY && SikakuY < ShotY + ShotH ) ) 

 がY成分の存在領域が重なっているかを判定している式です。
 どちらの式も成り立たなければならないので、X成分、Y成分の2つの判定式の繋ぎ目には
&&』演算子があります。
 各成分ごとに重なっているかを判定している式は、どういう仕組みか考えてみてください。
大小比較だけでこれといった演算はしていないので落ちついて考えれば容易にイメージできる
と思います。


 さて、これで当たり判定自体の処理は出来るようになりました。
 とりあえずこの条件式を使って敵に弾が当たるようにしたいと思います。

 で、出来たのが次のプログラムです。
 弾の数だけ当たり判定をしなくてはいけないことに注意してください。

 
#include "DxLib.h"

#define SHOT 20

// WinMain関数
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
					 LPSTR lpCmdLine, int nCmdShow )
{
	int BallX , BallY , BallGraph ;
	int SikakuX , SikakuY , SikakuMuki , SikakuGraph ;
	int ShotX[SHOT] , ShotY[SHOT] , ShotFlag[SHOT] , ShotGraph ;
	int SikakuW , SikakuH , ShotW , ShotH ;
	int ShotBFlag ;
	int i ;

	// 画面モードの設定
	SetGraphMode( 640 , 480 , 16 ) ;

	// DXライブラリ初期化処理
	if( DxLib_Init() == -1 ) return -1;

	// グラフィックの描画先を裏画面にセット
	SetDrawScreen( DX_SCREEN_BACK ) ;

	// ボール君のグラフィックをメモリにロード&表示座標をセット
	BallGraph = LoadGraph( "Ball.png" ) ;
	BallX = 288 ; BallY = 400 ;

	// 四角君のグラフィックをメモリにロード&表示座標をセット
	SikakuGraph = LoadGraph( "Sikaku.png" ) ;
	SikakuX = 0 ; SikakuY = 50 ;

	// 弾のグラフィックをメモリにロード
	ShotGraph = LoadGraph( "Shot.png" ) ;

	// 弾が画面上に存在しているか保持する変数に『存在していない』を意味する0を代入しておく
	for( i = 0 ; i < SHOT ; i ++ )
	{
		ShotFlag[i] = 0 ;
	}

	// ショットボタンが前のフレームで押されたかどうかを保存する変数に0(押されいない)を代入
	ShotBFlag = 0 ;

	// 四角君の移動方向をセット
	SikakuMuki = 1 ;

	// 弾のグラフィックのサイズをえる
	GetGraphSize( ShotGraph , &ShotW , &ShotH ) ;

	// 四角君のグラフィックのサイズを得る
	GetGraphSize( SikakuGraph , &SikakuW , &SikakuH ) ;

	// 移動ルーチン
	while( 1 )
	{
		// 画面を初期化(真っ黒にする)
		ClearDrawScreen() ;

		// ボール君の操作ルーチン
		{
			// ↑キーを押していたらボール君を上に移動させる
			if( CheckHitKey( KEY_INPUT_UP ) == 1 ) BallY -= 3 ;

			// ↓キーを押していたらボール君を下に移動させる
			if( CheckHitKey( KEY_INPUT_DOWN ) == 1 ) BallY += 3 ;

			// ←キーを押していたらボール君を左に移動させる
			if( CheckHitKey( KEY_INPUT_LEFT ) == 1 ) BallX -= 3 ;

			// →キーを押していたらボール君を右に移動させる
			if( CheckHitKey( KEY_INPUT_RIGHT ) == 1 ) BallX += 3 ;

			// スペースキーを押した場合は処理を分岐
			if( CheckHitKey( KEY_INPUT_SPACE ) )
			{
				// 前フレームでショットボタンを押したかが保存されている変数が0だったら弾を発射
				if( ShotBFlag == 0 )
				{
					// 画面上にでていない弾があるか、弾の数だけ繰り返して調べる
					for( i = 0 ; i < SHOT ; i ++ )
					{
						// 弾iが画面上にでていない場合はその弾を画面に出す
						if( ShotFlag[i] == 0 )
						{
							int Bw, Bh, Sw, Sh ;

							// ボール君と弾の画像のサイズを得る
							GetGraphSize( BallGraph , &Bw , &Bh ) ;
							GetGraphSize( ShotGraph , &Sw , &Sh ) ;

							// 弾iの位置をセット、位置はボール君の中心にする
							ShotX[i] = ( Bw - Sw ) / 2 + BallX ;
							ShotY[i] = ( Bh - Sh ) / 2 + BallY ;

							// 弾iは現時点を持って存在するので、存在状態を保持する変数に1を代入する
							ShotFlag[i] = 1 ;

							// 一つ弾を出したので弾を出すループから抜けます
							break ;
						}
					}
				}

				// 前フレームでショットボタンを押されていたかを保存する変数に1(おされていた)を代入
				ShotBFlag = 1 ;
			}
			else
			{
				// ショットボタンが押されていなかった場合は
				// 前フレームでショットボタンが押されていたかを保存する変数に0(おされていない)を代入
				ShotBFlag = 0 ;
			}

			// ボール君が画面左端からはみ出そうになっていたら画面内の座標に戻してあげる
			if( BallX < 0 ) BallX = 0 ;

			// ボール君が画面右端からはみ出そうになっていたら画面内の座標に戻してあげる
			if( BallX > 640 - 64 ) BallX = 640 - 64  ;

			// ボール君が画面上端からはみ出そうになっていたら画面内の座標に戻してあげる
			if( BallY < 0 ) BallY = 0 ;

			// ボール君が画面下端からはみ出そうになっていたら画面内の座標に戻してあげる
			if( BallY > 480 - 64 ) BallY = 480 - 64 ;

			// ボール君を描画
			DrawGraph( BallX , BallY , BallGraph , FALSE ) ;
		}

		// 弾の数だけ弾を動かす処理を繰り返す
		for( i = 0 ; i < SHOT ; i ++ )
		{
			// 自機の弾iの移動ルーチン( 存在状態を保持している変数の内容が1(存在する)の場合のみ行う )
			if( ShotFlag[ i ] == 1 )
			{
				// 弾iを16ドット上に移動させる
				ShotY[ i ] -= 16 ;

				// 画面外に出てしまった場合は存在状態を保持している変数に0(存在しない)を代入する
				if( ShotY[ i ] < -80 )
				{
					ShotFlag[ i ] = 0 ;
				}

				// 画面に弾iを描画する
				DrawGraph( ShotX[ i ] , ShotY[ i ] , ShotGraph , FALSE ) ;
			}
		}

		// 四角君の移動ルーチン
		{
			// 四角君の座標を移動している方向に移動する
			if( SikakuMuki == 1 ) SikakuX += 3 ;
			if( SikakuMuki == 0 ) SikakuX -= 3 ;

			// 四角君が画面右端からでそうになっていたら画面内の座標に戻してあげ、移動する方向も反転する
			if( SikakuX > 576 )
			{
				SikakuX = 576 ;
				SikakuMuki = 0 ;
			}

			// 四角君が画面左端からでそうになっていたら画面内の座標に戻してあげ、移動する方向も反転する
			if( SikakuX < 0 )
			{
				SikakuX = 0 ;
				SikakuMuki = 1 ;
			}

			// 四角君を描画
			DrawGraph( SikakuX , SikakuY , SikakuGraph , FALSE ) ;
		}

		// 弾と敵の当たり判定、弾の数だけ繰り返す
		for( i = 0 ; i < SHOT ; i ++ )
		{
			// 弾iが存在している場合のみ次の処理に映る
			if( ShotFlag[ i ] == 1 )
			{
				// 四角君との当たり判定
				if( ( ( ShotX[i] > SikakuX && ShotX[i] < SikakuX + SikakuW ) ||
					( SikakuX > ShotX[i] && SikakuX < ShotX[i] + ShotW ) ) &&
					( ( ShotY[i] > SikakuY && ShotY[i] < SikakuY + SikakuH ) ||
					( SikakuY > ShotY[i] && SikakuY < ShotY[i] + ShotH ) ) )
				{
					// 接触している場合は当たった弾の存在を消す
					ShotFlag[ i ] = 0 ;
				}
			}
		}

		// 裏画面の内容を表画面にコピーする
		ScreenFlip() ;

		// Windows 特有の面倒な処理をDXライブラリにやらせる
		// -1 が返ってきたらループを抜ける
		if( ProcessMessage() < 0 ) break ;

		// もしESCキーが押されていたらループから抜ける
		if( CheckHitKey( KEY_INPUT_ESCAPE ) ) break ;
	}

	// DXライブラリ使用の終了処理
	DxLib_End() ;

	// ソフトの終了
	return 0 ;
}

<実行図>

 グラフィックのサイズの取得には GetGraphSize 関数を使っています。
 実行してみて弾を四角君に当てると確かに弾が消え、きちんと弾の当たり判定が
行われていることが確認できると思います。

 さて、今回はとりあえず弾があたるようになっただけですので、次は四角君に弾が
当たった時のリアクションをつけてみたいと思います。

戻る