2Dゲーム制作Ⅰ⑥(2025/05/26)

今回の目標
・HitCheck()関数で当たり判定を関数化
・爆発をアニメーションで表現
・敵が球を打ったら逃げる行動に変化
・弾と敵を配列で複数化

HitCheck()関数
・当たり判定の処理を関数にまとめる。(分離・再利用可能にする)。

bool CollisionCheck(int x1,int y1,int r1,int x2,int y2,int r2){
 int dx = x1 - x2;
 int dy = y1 - y2;
 int dist = dx * dx + dy * dy;
 int radius = r1 + r2;

 if(dist < radius * radius){
    return true;//当たっている
  }
 return false;//当たっていない
}

上記をWinMain関数の下に書くため、プロトタイプ宣言を行っておく。

// プロトタイプ宣言
bool CollisionCheck(int x1, int y1, int r1, int x2, int y2, int r2);

加えてプレイヤーのサイズや敵のサイズ、弾のサイズをマクロ化(#define)。
上記を踏まえて、衝突判定をCollisionCheck()に差し替え。
【今日のコード①】

#include<stdlib.h>
#include<time.h>
#include<DxLib.h>

#define SCREEN_W 480
#define SCREEN_H 880

#define CENTER_X (SCREEN_W / 2)
#define CENTER_Y (SCREEN_H / 2)

#define DISTANCE_R (250*250) //距離の二乗
#define PLAYER_DEL_PERIOD (30)
#define PLAYER_MAX (1)
#define GAME_START_PERIOD (60)//GAME START表示時間

#define PLAYER_W 128 //プレイヤーの横幅
#define PLAYER_H 141 //プレイヤーの高さ
#define PLAYER_R (PLAYER_W/2)//プレイヤーの半径
#define SHOT_R (5)//弾の半径
#define ENEMY_R (250)
#define ENEMY_SHOT_R (5)
#define FIRE_PERIOD (5)

bool playerFlag = true; //自機の表示フラグ
int playerX = CENTER_X;
int playerY = CENTER_Y;
int scrollY = 0;
int shotX = 0, shotY = 0, shotFlag = 0;
int shotX2 = 0, shotY2 = 0, shotFlag2 = 0;
int shotX3 = 0, shotY3 = 0, shotFlag3 = 0;// shotFlag…0:表示しない 1:表示
int shotTimer = 0;
int enemyFlag = 1, enemyX = 0, enemyY = 100, enemyTimer = 0, enemyVX = 0, enemyVY = 1; //enemy画像の幅の半分をずらして、真ん中付近に表示
int playerTimer = 0;
int playerLife;
int gameTimer = 0;
int n = 0;

// プロトタイプ宣言
bool CollisionCheck(int x1, int y1, int r1, int x2, int y2, int r2);

void DrawGraphCenter(int x, int y, int handle, bool flag);

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
	ChangeWindowMode(TRUE); // ウィンドウモード
	SetGraphMode(SCREEN_W, SCREEN_H, 32);

	DxLib_Init(); // DXライブラリ初期化
	SetMainWindowText("BlackPiyo VS Mike");
	int handleShot = LoadGraph("chara/egg.png");
	int handleEnemy = LoadGraph("chara/mike.png");
	int handleBg = LoadGraph("bg.png");
	int handle = LoadGraph("chara/blackpiyo-s.png");
	srand((unsigned)time(NULL) * 123);

	playerLife = PLAYER_MAX
		;

	while (ProcessMessage() == 0) {
		SetDrawScreen(DX_SCREEN_BACK); // 裏画面に描画
		ClearDrawScreen();

		//playerを動かす処理
		if (CheckHitKey(KEY_INPUT_RIGHT))
		{
			playerX += 2;
		}
		if (CheckHitKey(KEY_INPUT_LEFT))
		{
			playerX -= 2;
		}
		if (CheckHitKey(KEY_INPUT_DOWN))
		{
			playerY += 2;
		}
		if (CheckHitKey(KEY_INPUT_UP))
		{
			playerY -= 2;
		}
		if (CheckHitKey(KEY_INPUT_SPACE) && (shotTimer == 0))
		{
			if (shotFlag == 0)
			{
				shotX = playerX;
				shotY = playerY;
				shotFlag = 1;
				shotTimer = 20;
			}
			else if (shotFlag2 == 0)
			{
				shotX2 = playerX;
				shotY2 = playerY;
				shotFlag2 = 1;
				shotTimer = 20;
			}
			else if (shotFlag3 == 0)
			{
				shotX3 = playerX;
				shotY3 = playerY;
				shotFlag3 = 1;
				shotTimer = 20;
			}
		}

		//プレーヤーの移動制限
		if (playerX > SCREEN_W - PLAYER_W / 2)playerX = SCREEN_W - PLAYER_W / 2;
		if (playerX < PLAYER_W / 2)playerX = PLAYER_W/ 2;
		if (playerY > SCREEN_H - PLAYER_H / 2)playerY = SCREEN_H - PLAYER_H / 2;
		if (playerY < PLAYER_H / 2)playerY = PLAYER_H / 2;

		//プレイヤーの弾の処理
		if (shotTimer > 0)
		{
			shotTimer--;
		}
		int dx = 0;
		if (shotFlag == 1 && enemyFlag == 1)
		{
			if (CollisionCheck(shotX,shotY,SHOT_R,enemyX,enemyY,ENEMY_R))
			{
				
				enemyFlag = 0;
				shotFlag = 0;
				enemyTimer = (rand() % 100);
				enemyX = (rand() % 360);
			}
		}
		if (shotFlag2 == 1 && enemyFlag == 1)
		{
			if (CollisionCheck(shotX2,shotY2,SHOT_R,enemyX,enemyY,ENEMY_R))
			{
				enemyFlag = 0;
				shotFlag2 = 0;
				enemyTimer = (rand() % 100);
				enemyX = (rand() % 360);
			}
		}
		if (shotFlag3 == 1 && enemyFlag == 1)
		{
			if (CollisionCheck(shotX3, shotY3, SHOT_R, enemyX, enemyY, ENEMY_R))
			{
				enemyFlag = 0;
				shotFlag3 = 0;
				enemyTimer = (rand() % 100);
				enemyX = (rand() % 360);
			}
		}
		scrollY += 8;
		if (scrollY > SCREEN_H)
		{
			scrollY = 0;
		}

		if (shotFlag == 1)
		{
			shotY -= 4;
			if (shotY < 0)
			{
				shotFlag = 0;
			}
		}
		if (shotFlag2 == 1)
		{
			shotY2 -= 4;
			if (shotY2 < 0)
			{
				shotFlag2 = 0;
			}
		}

		if (shotFlag3 == 1)
		{
			shotY3 -= 4;
			if (shotY3 < 0)
			{
				shotFlag3 = 0;
			}
		}
		//敵の移動処理
		if (enemyFlag == 1) {
			enemyX += enemyVX; // X方向の移動
			enemyY += enemyVY; // Y方向の移動

				if (CollisionCheck(playerX,playerY,PLAYER_R,enemyX,enemyY,ENEMY_R))
				{
					playerFlag = FALSE; //プレイヤー消滅
					enemyFlag = FALSE; //敵消滅
				}
				//プレイヤーが一定時間消えた後、復活する
				if ((playerFlag == false) && (playerLife >= 0) )
				{
					if (playerTimer++ > PLAYER_DEL_PERIOD) {
						playerFlag = TRUE;
						playerTimer = 0;
						playerLife--;
					}
				}
			// 範囲外判定:敵が画面外に出たらフラグをオフにし再出現を準備
			if (enemyX < -300 || enemyX -300 > SCREEN_W) {
				enemyFlag = 0;
				enemyTimer = 40;
			}
			if (enemyY  < -300 || enemyY -300 > SCREEN_H) {
				enemyFlag = 0;
				enemyTimer = 40;
			}
		}
		else {
			// 敵の再出現処理
			if (enemyTimer-- < 0) {
				enemyFlag = 1;
				enemyX = rand() % SCREEN_W;
				enemyY = -300;
				enemyVX = rand() % 5 - 1; // -1~1のランダム速度
				enemyVY = rand() % 5 - 2; // -2~2のランダム速度
				enemyTimer = 50;
			}
		}
		DrawGraph(0, scrollY, handleBg, TRUE);
		DrawGraph(0, scrollY - SCREEN_H, handleBg, TRUE);
		DrawGraphCenter(playerX, playerY, handle, playerFlag);
		DrawGraphCenter(shotX, shotY, handleShot, shotFlag == 1);
		DrawGraphCenter(shotX2, shotY2, handleShot, shotFlag2 == 1);
		DrawGraphCenter(shotX3, shotY3, handleShot, shotFlag3 == 1);
		DrawGraphCenter(enemyX, enemyY, handleEnemy, TRUE);

		//残機表示
		DrawFormatString(0, 0, GetColor(255, 255, 255), "残機:%d", playerLife+n);
		
		//ゲームオーバー表示
		if (playerLife == -1)
		{
			if(n==0)n++;
			DrawString(70, 240, "GAME OVER", GetColor(255, 255, 255));
		}

		//ゲームスタート表示
		if (gameTimer++ < GAME_START_PERIOD)
		{
			DrawString(120, CENTER_Y, "GAME START", GetColor(255, 255, 255));
		}

		ScreenFlip();
	}
	DxLib_End();
}
//画像の中心位置基準で表示
//input x, y 表示位置座標
//handle 画像ID
//flag 表示/非表示
void DrawGraphCenter(int playerX, int playerY, int handle, bool flag)
{
	int width, height;

	if (flag)
	{
		GetGraphSize(handle, &width, &height);
		DrawGraph(playerX - (width / 2), playerY -( height / 2), handle, TRUE);
	}
}
//衝突チェック
//input x1, y1	キャラ#1	X,Y位置
//		r2		キャラ#1	半径
//		x2, y2	キャラ#2	X,Y位置
//		r2		キャラ#2	半径
//output flag	当たった/当たってない

bool CollisionCheck(int x1, int y1, int r1, int x2, int y2, int r2) {
	int dx = x1 - x2;
	int dy = y1 - y2;
	int dist = dx * dx + dy * dy;
	int radius = r1 + r2;

	if (dist < radius * radius) {
		return true;//当たっている
	}
	return false;//当たっていない
}

※if分の条件式は、結果が真=1だと実行、偽=0だと実行されない。
このため、bool型の戻り値を返す関数をそのまま条件式に書いておくと条件分岐が成立する。
例えば、if(1){}だと必ず実行され、if(0){}だと絶対に実行されない、ということになる。

爆発をアニメーションで表現

・爆発画像(複数コマ)を準備し、爆発中はカウンタでコマ送りする。
↓今回のアセット

アニメを表示するために必要なパラメーター
・表示/非表示フラグ
・表示X,Y位置
・何枚目のパターンを表示中かをカウントするアニメーションのタイマー
下記で宣言。

bool fire_flag = false;//爆発アニメを表示するフラグ
int fire_x;
int fire_y;
int fire_pat_index = 0;//何枚目の絵を表示中?
int fire_pat_timer = 0;//アニメーションのタイマー

ここで時間切れ。続きは明日。

コメント

タイトルとURLをコピーしました