横スクロールアクションの衝突判定

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら

横スクロールアクションの衝突判定

#1

投稿記事 by » 15年前

はじめまして、宮といいます。
このサイトのおかげで作り方の分からなかったゲームプログラミングに着手することができました。
ありがとうございます ^^

現在マリオ風横スクロールアクションのサンプルを作っているのですが、
プレイヤーとブロックの衝突判定がうまくいかずに困っています。

挙動がおかしいのは2箇所です。
①ブロックの斜め上から衝突すると横から進入しているのにブロックの上に乗ってしまう。
②ブロックを下から叩くとブロックのないところ(横一列に並ぶブロックの一番右側)まで移動させられる。
( 説明がヘタクソなのでアップローダーに実行ファイルとソースを添付しました。(汗) )
【key:act】
http://www1.axfc.net/uploader/He/so/252845

これらを修正するためにはどうすれば良いのでしょうか?
お力添えをお願いします。

以下が衝突判定のソースです。

================================================================================
--- Map.h ---
#define CHIP_SIZE	32	//マップチップのサイズ(キャラクター画像も32*32)

--- Charactor.h ---
#include "Map.h"

#define ACCEL_X		0.2	//X加速値
#define ACCEL_X_MAX	8.0	//X最大加速値
#define GRAVITY		0.2	//重力加速度
#define GRAVITY_MAX	10.0	//落下最大スピード
#define JAMP		-6.0	//ジャンプによるY加速値

enum eDIR{//向き
	eDIR_DOWN, eDIR_UP, eDIR_RIGHT, eDIR_LEFT,
};

struct rect_t{//矩形
	int x,y,w,h;
};

//クラス定義
class Charactor{
	int handle[16];			//画像ハンドル
	int cnt;			//カウンタ
	double cx, cy;			//座標
	double vx, vy;			//加速値
	int dir;			//向き
	bool jump;			//ジャンプフラグ
	rect_t rect;			//当たり範囲(例:cx + rect.x, cx + rect.w)
public:
	void init();			//初期化
	void input();			//入力処理
	void collision();		//衝突処理
	bool out(rect_t, int, int);	//衝突判定
};

================================================================================
--- Charactor.cpp ---

//概要:初期化
void Charactor::init()
{
	//キャラクターの判定領域(cx, cyから加算する値)
	rect.x = 4;
	rect.y = 2;
	rect.w = 28;
	rect.h = 32;
}

//概要:入力処理
void Charactor::input()
{
	//キー入力あれこれ~~

	//移動
	vy += GRAVITY;
	if(vy > GRAVITY_MAX) vy = GRAVITY_MAX;
	cx += vx;
	cy += vy;

	collision();						//衝突処理
}

//概要:衝突処理
void Charactor::collision()
{
	int x = ((int)cx + rect.x) / CHIP_SIZE;
	int y = ((int)cy + rect.y) / CHIP_SIZE;

	//座標取得
	rect_t box = rect;
	box.x += (int)cx;		box.w += (int)cx;
	box.y += (int)cy;		box.h += (int)cy;

	//位置補正(左右)
	if(vx > 0.0){//右へ移動中
		if(out(box, x+1, y)){				//壁との衝突
			cx = x * CHIP_SIZE + rect.x;		//位置調整
			vx *= 0.0/*-0.8*/;
		}
	}
	else{//左へ移動中
		if(out(box, x, y)){				//壁との衝突
			cx = (x+1) * CHIP_SIZE - rect.x;	//位置調整
			vx *= 0.0/*-0.8*/;
		}
	}

	x = ((int)cx + rect.x) / CHIP_SIZE;
	y = ((int)cy + rect.y) / CHIP_SIZE;
	box = rect;
	box.x += (int)cx;		box.w += (int)cx;
	box.y += (int)cy;		box.h += (int)cy;

	//位置補正(上下)
	jump = true;
	if(vy > 0.0){//下降中
		if((out(box, x, y+1)) || (out(box, x+1, y+1))){	//地面と衝突
			cy = (y-1) * CHIP_SIZE + rect.h;	//位置調整
			vy = 0.0;
			jump = false;
		}
	}
	else{//上昇中
		if((out(box, x, y)) || (out(box, x+1, y))){	//天井と衝突
			cy = (y+1) * CHIP_SIZE - rect.y;	//位置調整
		}
	}
}

//概要:キャラとマップチップの衝突判定
bool Charactor::out(rect_t A, int _x, int _y)
{
	if(Map::getMapChip(0, _x, _y) == 0) return(false);	//マップチップが無ければ

	if(Map::getMapChip(0, _x, _y) == 1){			//マップチップが壁なら
		//マップチップ座標取得
		rect_t B;
		B.x = _x * CHIP_SIZE;		B.y = _y * CHIP_SIZE;
		B.w = B.x + CHIP_SIZE;		B.h = B.y + CHIP_SIZE;

		//矩形同士の当たり判定
		if((A.x < B.w) && (B.x < A.w) && (A.y < B.h) && (B.y < A.h)){
			return(true);
		}
	}
	return(false);
}

softya

Re:横スクロールアクションの衝突判定

#2

投稿記事 by softya » 15年前

とりあえず衝突判定で左右と上下を別々に補正しているのが問題だと思います。

こんな感じで処理してみてください。
(1)将来の位置を上下左右のベクトル計算後の仮座標で衝突判定。衝突しなかった仮座標は正式な座標として、衝突判定を抜ける。衝突したら仮座標は捨てる。
(2)左右の移動量と上下の移動量のどちらが大きいか判定。左右なら(3)(4)と処理。上下なら(4)(3)と処理
(3)左右のマップチップの境界で補正した将来の位置の仮座標で衝突判定。衝突しなかった仮座標は正式な座標として、衝突判定を抜ける。衝突したら仮座標は捨てる。
※ ようは、左右の壁に衝突すると仮定して、衝突時上下はどの位置に移動しているかを計算して、その位置で壁に衝突しているか調べる。
(4)今度は上下で(3)と同様な処理を行う。

これで大丈夫なはずですが検証していないので、間違っていたらまた考えます。

Re:横スクロールアクションの衝突判定

#3

投稿記事 by » 15年前

softyaさんの意見を参考に②の問題は解決することができました。
ありがとうございます。

(3)(4)は、例えば左右の移動量の方が上下の移動量より多ければ横の座標を修正した後に縦をチェックする。
上下の方が多ければ縦座標を修正した後に横をチェックするという解釈でいいでしょうか?
何となくそんな感じかな~とcollision()関数を修正してみました。

あとは①の問題と、もう一つおかしな所を見つけました。

■がブロック、●がプレイヤーだとします。

マップがこんな感じだとして


■  ●
■■■■

矢印に向かって跳ぶと、
■←

■  ●
■■■■

ブロックをすり抜けてしまいます。


 ■←
 ■
 ■
 ■■■■

こちらが変更したプログラムです。
================================================================================
--- Charactor.h ---

class Charactor{
	bool collisionLR();		//衝突処理(左右)
	bool collisionUD();		//衝突処理(上下)
	rect_t addRect(int _x, int _y);	//rect_t構造体の加算処理
};

================================================================================
--- Charactor.cpp ---

//衝突処理(左右)
bool Charactor::collisionLR()
{
	int next_cx = cx + vx;
	rect_t box = addRect(next_cx, cy);
	int _x = (int)(cx + rect.x + vx) / CHIP_SIZE;
	int _y = (int)(cy + rect.y)      / CHIP_SIZE;

	if(vx > 0.0){//右へ移動中
		if(out(box, _x+1, _y)){//壁との衝突
			cx = _x * CHIP_SIZE + rect.x;//位置調整
			vx *= 0.0;
			return(true);
		}
	}
	else{//左へ移動中
		if(out(box, _x, _y)){//壁との衝突
			cx = (_x+1) * CHIP_SIZE - rect.x;//位置調整
			vx *= 0.0;
			return(true);
		}
	}
	return(false);
}

//衝突処理(上下)
bool Charactor::collisionUD()
{
	int next_cy = cy + vy;
	rect_t box = addRect(cx, next_cy);
	int _x = (int)(cx + rect.x)      / CHIP_SIZE;
	int _y = (int)(cy + rect.y + vy) / CHIP_SIZE;

	//位置補正(上下)
	if(vy > 0.0){//下降中
		if((out(box, _x, _y+1)) || (out(box, _x+1, _y+1))){//地面と衝突
			cy = (_y-1) * CHIP_SIZE + rect.h;//位置調整
			vy = 0.0;
			jump = false;
			return(true);
		}
	}
	else{//上昇中
		if((out(box, _x, _y)) || (out(box, _x+1, _y))){//天井と衝突
			cy = (_y+1) * CHIP_SIZE - rect.y;//位置調整
			return(true);
		}
	}
	return(false);
}

void Charactor::collision()
{
	//変数定義
	int next_cx, next_cy;		//次の座標
	rect_t box;			//プレイヤーキャラの左上, 右下座標
	bool fmove = false;		//移動フラグ

	next_cx = cx + vx;			//次に移動する座標
	next_cy = cy + vy;
	box = addRect(next_cx, next_cy);	//次のフレームの当たり判定

	//衝突していたら
	jump = true;
	if(vx*vx > vy*vy){//横の移動量の方が大きい
		collisionLR();		//左右の衝突チェック
		fmove = collisionUD();	//上下の衝突チェック
	}
	else{//縦の移動量の方が大きい
		fmove = collisionUD();	//上下の衝突チェック
		collisionLR();		//左右の衝突チェック
	}

	//移動
	cx += vx;
	if(!fmove) cy += vy;
}

//rect_t構造体の加算処理
rect_t Charactor::addRect(int _x, int _y)
{
	rect_t box = rect;	//当たり判定を取得
	box.x += (int)_x;		box.w += (int)_x;	//キャラの左座標, 右座標を取得
	box.y += (int)_y;		box.h += (int)_y;	//キャラの上座標, 下座標を取得
	return(box);
}

softya

Re:横スクロールアクションの衝突判定

#4

投稿記事 by softya » 15年前

残る問題はすり抜けだけですかね?
だとすると、簡単な方法は最初に移動ベクトルの半分の値でまず当たり判定を行うと良いと思います。
イメージ的には、AからCに移る移動処理で、途中のBポイントで当たり判定をしてみるって感じです。本当は1ドット毎にやりたいんですが処理時間が掛かるので中間点だけ調べるので代用します。
これでだいぶすり抜ける事は無くなると思います。

修正版です。(0)を追加しました。

(0)上下左右のベクトルの半分で計算した仮座標で衝突判定。衝突したら(2)へ。しなかったら(1)へ。
(1)将来の位置を上下左右のベクトル計算後の仮座標で衝突判定。衝突しなかった仮座標は正式な座標として、衝突判定を抜ける。衝突したら仮座標は捨てる。
(2)左右の移動量と上下の移動量のどちらが大きいか判定。左右なら(3)(4)と処理。上下なら(4)(3)と処理
(3)左右のマップチップの境界で補正した将来の位置の仮座標で衝突判定。衝突しなかった仮座標は正式な座標として、衝突判定を抜ける。衝突したら仮座標は捨てる。
※ ようは、左右の壁に衝突すると仮定して、衝突時上下はどの位置に移動しているかを計算して、その位置で壁に衝突しているか調べる。
(4)今度は上下で(3)と同様な処理を行う。

Re:横スクロールアクションの衝突判定

#5

投稿記事 by » 15年前

なるほどです。
ただ実装してみたのですが、変化がありませんでした。

(0)で横を修正した後、縦を修正。
衝突していなかったら(2)の処理へ。
bool hit = false;				//接触フラグ
if(collisionLR(vx/2, 0)){			//左右の衝突チェック
	fmove = collisionUD(0, vy/2);	//上下の衝突チェック
	hit = true;
}

if(!hit){
	collisionLR(vx, 0);		//左右の衝突チェック
	fmove = collisionUD(0, vy);		//上下の衝突チェック
}
こんな感じにしてみたのですが・・・

softya

Re:横スクロールアクションの衝突判定

#6

投稿記事 by softya » 15年前

うまく伝わっていなかったと思うので、プログラムを直してみました。
ただし、未コンパイル&未デバッグです。
そのまま使うのではなく、アルゴリズムの参考として使ってください。
大丈夫だとは思うのですが、アクションゲームのアルゴリズムを書いたのは相当久しぶりなので少々自信がありません。そちらの関数の使い方を勘違いしている可能性もあります。

Re:横スクロールアクションの衝突判定

#7

投稿記事 by » 15年前

遅くなってしまってすみません。

プログラム見させてもらいました。
参考になります。

x座標かy座標の片方だけずらして位置を補正するのではなく、
両方をずらし、当たり判定を取ってから壁際に移動させれば良かったのですね。

昨日は立て込んでしまい時間が取れませんでしたが、
また都合の良い時に修正案を試させていただきます!

きちんと動かすために少し手間取ってしまいそうなので、
ガッツリ考え込むかもしれません。

解決するか、煮詰まってしまったら再びコメントさせていただきます ^^

Re:横スクロールアクションの衝突判定

#8

投稿記事 by » 15年前

粘りましたがアカンでした。
やればやるほどこんがらがってしまいました。
う~ん、理解が足りてなかったようです。

申し訳ないのですが
実行可能なソースを添付しましたので、再度ご確認いただけるとありがたいです。
プログラムの問題点や解決案など、ご指摘お願いします。

けんたろうちゃん

Re:横スクロールアクションの衝突判定

#9

投稿記事 by けんたろうちゃん » 15年前

私は同じように2Dアクションを作るとき、以下のサイトを参考にしました
http://javagame.skr.jp/index.php?%A5%D6 ... E%D7%C6%CD

Re:横スクロールアクションの衝突判定

#10

投稿記事 by » 15年前

解決しました。
けんたろうちゃんさんに紹介していただいたサイトの方法でうまくいきました。
今までは
縦よりも横のほうが移動量が大きい⇒ 横、縦の順に修正。
そうでなければ			⇒ 縦、横の順に修正。
というやり方で進めてきましたが、
何が問題だったのかようやくつきとめることができました。

□●←右側から □ に衝突する


■■■■

□ と衝突して一度はその右隣に修正はされるものの、
次にcollision()関数を通るときには縦の移動量(vy)の方が横移動量(vx)よりも大きくなってしまう。
(衝突時に(vx)が0に修正されるため)

すると次に □ にぶつかったときに
縦、横の順で座標修正されるので

●←この位置に来てしまう。



■■■■

というとこでした。
紹介サイトのやり方を試したら上記の問題もすり抜け問題も一気に解決しました。
けんたろうちゃんさん、ありがとうございます!

それと、softyaさん。
何度も手助けしていただき、感謝です!
処理の手順や関数の使い方など、かなり参考になりました。
ありがとうございます!

softya

Re:横スクロールアクションの衝突判定

#11

投稿記事 by softya » 15年前

とりあえずうまく行って良かったです。
けんたろうちゃんさん紹介のサイトの方法の方がシンプルで良いかと思います。
気になっているのは、落下速度の上限を決めておかないと変なすり抜けを起こすんじゃないかって事なんですが、落下距離次第なんで一度確認してみてください。ではでは。

けんたろうちゃん

Re:横スクロールアクションの衝突判定

#12

投稿記事 by けんたろうちゃん » 15年前

いえいえどういたしまして~
今度2Dアクションの坂で効率的な方法思いついたら教えてくださいなw

閉鎖

“C言語何でも質問掲示板” へ戻る