弾のすり抜け対策がうまくいきません

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
もるも
記事: 52
登録日時: 4年前
連絡を取る:

弾のすり抜け対策がうまくいきません

#1

投稿記事 by もるも » 3年前

お世話になっています。
当たり判定のサンプルを作っているのですが、
前フレームの弾と現在の位置の弾の間を計算するところがうまく作れなくて困っています。
弾同士の距離の間を少しづつループで進めて計算して、
弾の間に判定を詰め込んで隙間をなくしたいです。

コード:

//main.cpp
#include "DxLib.h"
#include <cmath>
#define BULLET_MAX 10
char key[256];//キー入力用
struct vector2D {
	float X;
	float Y;

};
vector2D A, B, P;//座標用
vector2D AB, AP, BP,AO,BO;//ベクトル用
struct Bullet {
	double X;
	double Y;
	double preX;
	double preY;
	double angle;
	bool exist;
	int speed;
	int size;
	int wait;
	bool hit;
};
Bullet bullet[BULLET_MAX];

int rapid;
float R;		//半径
float GetVector2D_Dot(vector2D A, vector2D B);
float GetVector2D_Cross(vector2D A, vector2D B);
float GetVector2D_Length(vector2D A, vector2D B);
bool LineCircleCollision(float r, vector2D A, vector2D B, vector2D P);
void Shot();
void DrawBullet();
void BulletUpdate();
void BulletInit();
void BulletCollision();
class Fps {
	int mStartTime;         //測定開始時刻
	int mCount;             //カウンタ
	float mFps;             //fps
	static const int N = 60;//平均を取るサンプル数
	static const int FPS = 60;	//設定したFPS

public:
	Fps() {
		mStartTime = 0;
		mCount = 0;
		mFps = 0;
	}

	bool Update() {
		if (mCount == 0) { //1フレーム目なら時刻を記憶
			mStartTime = GetNowCount();
		}
		if (mCount == N) { //60フレーム目なら平均を計算する
			int t = GetNowCount();
			mFps = 1000.f / ((t - mStartTime) / (float)N);
			mCount = 0;
			mStartTime = t;
		}
		mCount++;
		return true;
	}

	void Draw() {
		DrawFormatString(600, 460, GetColor(255, 255, 255), "%.1f", mFps);
	}

	void Wait() {
		int tookTime = GetNowCount() - mStartTime;	//かかった時間
		int waitTime = mCount * 1000 / FPS - tookTime;	//待つべき時間
		if (waitTime > 0) {
			Sleep(waitTime);	//待機
		}
	}
};
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
	ChangeWindowMode(TRUE), DxLib_Init(), SetDrawScreen(DX_SCREEN_BACK); //ウィンドウモード変更と初期化と裏画面設定
	//初期化
	R = 20;
	P.X = 200;
	P.Y = 200;
	rapid = 0;
	Fps fps;
	BulletInit();
	// while(裏画面を表画面に反映, メッセージ処理, 画面クリア)
	while (ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0) {
		fps.Update();	//更新
		fps.Draw();		//描画

		fps.Wait();		//待機
		A.X = 100;
		A.Y = 300;
		B.X = 400;
		B.Y = 300;
	
		GetHitKeyStateAll(key);
		if (key[KEY_INPUT_RIGHT] != 0) { P.X++; }
		if (key[KEY_INPUT_LEFT] != 0) { P.X--; }
		if (key[KEY_INPUT_UP] != 0) { P.Y--; }
		if (key[KEY_INPUT_DOWN] != 0) { P.Y++; }
		if (key[KEY_INPUT_Z] !=0) {
			rapid++;
			rapid %= 10;
			if (rapid == 1) {
				Shot();
			}
		}
		BulletCollision();
		DrawBullet();
		//円を描画
		DrawCircle(P.X, P.Y, R, GetColor(255, 255, 0));
		//線を描画
		DrawLine(A.X, A.Y, B.X, B.Y, GetColor(255, 0, 0));

		//当たり判定
		if (LineCircleCollision(R, A, B, P));
	}
	DxLib_End(); // DXライブラリ終了処理
	return 0;
}
// キーの入力状態を更新する
int gpUpdateKey() {
	char tmpKey[256]; // 現在のキーの入力状態を格納する
	GetHitKeyStateAll(tmpKey); // 全てのキーの入力状態を得る
	for (int i = 0; i < 256; i++) {
		if (tmpKey[i] != 0) { // i番のキーコードに対応するキーが押されていたら
			key[i]++;     // 加算
		}
		else {              // 押されていなければ
			key[i] = 0;   // 0にする
		}
	}
	return 0;
}
//内積
float GetVector2D_Dot(vector2D A, vector2D B) {
	float Dot = A.X*B.X + A.Y*B.Y;
	return Dot;
}
//外積
float GetVector2D_Cross(vector2D A, vector2D B) {
	float Cross = A.X*B.Y - A.Y*B.X;
	return Cross;
}
//大きさ
float GetVector2D_Length(vector2D A, vector2D B) {
	float X = fabsf(B.X - A.X);
	float Y = fabsf(B.Y - A.Y);
	return pow(X * X + Y * Y, 0.5);

}

bool LineCircleCollision(float r, vector2D A, vector2D B, vector2D P) {
	//座標からベクトルを得る
	AB.X = B.X - A.X;
	AB.Y = B.Y - A.Y;
	AP.X = P.X - A.X;
	AP.Y = P.Y - A.Y;
	BP.X = P.X - B.X;
	BP.Y = P.Y - B.Y;

	//円と線分の距離を求める
	//ベクトルAB、APの外積の絶対値が平行四辺形Dの面積になる
	double D = abs(GetVector2D_Cross(AB, AP));
	double L = GetVector2D_Length(A, B);	//AB間の距離
	double H = D / L;

	DrawFormatString(0, 0, GetColor(255, 255, 255), "H%d", (int)H);
	DrawFormatString(0, 20, GetColor(255, 255, 255), "D%d", (int)D);
	DrawFormatString(0, 40, GetColor(255, 255, 255), "L%d", (int)L);
	float dot1 = GetVector2D_Dot(AP, AB);
	float dot2 = GetVector2D_Dot(BP, AB);
	DrawFormatString(0, 60, GetColor(255, 255, 255), "dot%d", (int)dot1);
	DrawFormatString(0, 80, GetColor(255, 255, 255), "dot%d", (int)dot2);

	//線と円の中心の距離が半径より小さい
	if (H <= r) {

		if (dot1*dot2 <= 0) {//角度が90度以下なので円の中心が線分の両端の内側にある
			DrawString(0, 460, "HIT!!!", GetColor(0, 255, 255));
			return true;

		}
		//鈍角になるスペシャルケースの場合
		float AP = GetVector2D_Length(A, P);
		float BP = GetVector2D_Length(B, P);
		DrawFormatString(0, 100, GetColor(255, 255, 255), "AP%d", (int)AP);
		DrawFormatString(0, 120, GetColor(255, 255, 255), "BP%d", (int)BP);
		if (AP < R || BP < R) {
			DrawString(0, 460, "HIT!!!", GetColor(255, 0, 255));
			return true;
		}

	}
}
void BulletInit(){
	for (int i = 0; i < BULLET_MAX;i++) {
		bullet[i].X = 0;
		bullet[i].Y = 0;
		bullet[i].preX = 0;
		bullet[i].preY = 0;
		bullet[i].angle = 0.75;
		bullet[i].exist = false;
		bullet[i].speed =8;
		bullet[i].size = 2;
		bullet[i].hit = false;
		bullet[i].wait = 0;
	}
}
void DrawBullet(){
	BulletUpdate();
	for (int i = 0; i < BULLET_MAX; i++) {
		if (bullet[i].exist) {
			DrawCircle(bullet[i].X,bullet[i].Y,bullet[i].size,GetColor(0,255,0));
		}
	}
}
void BulletUpdate() {
	for (int i = 0; i < BULLET_MAX; i++) {
		if (bullet[i].exist) {
			bullet[i].preX = bullet[i].X;
			bullet[i].preY = bullet[i].Y;

			bullet[i].X = bullet[i].X + cos(bullet[i].angle)*bullet[i].speed;
			bullet[i].Y = bullet[i].Y + sin(bullet[i].angle)*bullet[i].speed;
		}
	
		//画面外だと消える
		if (bullet[i].X < 0 || bullet[i].X>640) {
			if (bullet[i].Y < 0 || bullet[i].Y>480) {
				bullet[i].exist = false;
				bullet[i].angle = 0.75;
			}
		}
		//判定が重ならないようにウェイト
		if (bullet[i].wait == 3) {
			bullet[i].hit = false;
			bullet[i].wait = 0;
		}
		if (bullet[i].hit) {
			bullet[i].wait++;
		}
	
	}

}
void Shot() {
	
			for (int i = 0; i < BULLET_MAX; i++) {
			if (!bullet[i].exist) {
				bullet[i].X = P.X;
				bullet[i].Y = P.Y;
				bullet[i].exist = true;
				return;
			}
		
	}
}
void BulletCollision() {
	for (int i = 0; i < BULLET_MAX; i++) {
		//当たり判定
		vector2D O;
		O.X = bullet[i].X;
		O.Y = bullet[i].Y;
		if (bullet[i].exist&&!(bullet[i].hit)) {
			if (LineCircleCollision(bullet[i].size, A, B, O)) {
				bullet[i].hit = true;
				bullet[i].angle = bullet[i].angle*(-1);
				return;
		}
			//弾と弾のすきま用
			vector2D Q;
				//前フレームと現在の弾の距離を求める
				double x = abs(bullet[i].X - bullet[i].preX);
				double y = abs(bullet[i].Y - bullet[i].preY);
				double length = sqrt(x*x + y*y);
				//弾何個分か調べる
				//(弾同士の距離-それぞれの弾の半径)÷弾の直径
				double b_num = (length - (bullet[i].size*2)) / (bullet[i].size*2);
				
				DrawFormatString(200, 200, GetColor(255, 255, 255), "b_num%d", (int)b_num);
			for (int j = 0; j <= b_num; j++) {
		
						double b_x = bullet[i].preX;
						double b_y = bullet[i].preY;
						Q.X = b_x;
						Q.Y = b_y;
						if (LineCircleCollision(bullet[i].size, A, B, Q)) {
							bullet[i].hit = true;
							bullet[i].angle = bullet[i].angle*(-1);
							return;
						}
						b_x += cos(bullet[i].angle)*bullet[i].size*2;
						b_y += sin(bullet[i].angle)*bullet[i].size*2;
						DrawCircle(b_x, b_y, bullet[i].size, GetColor(255, 0, 100));
		
			}
		}
	}
}


アバター
みけCAT
記事: 6235
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: 弾のすり抜け対策がうまくいきません

#2

投稿記事 by みけCAT » 3年前

もるも さんが書きました:当たり判定のサンプルを作っているのですが、
前フレームの弾と現在の位置の弾の間を計算するところがうまく作れなくて困っています。
弾同士の距離の間を少しづつループで進めて計算して、
弾の間に判定を詰め込んで隙間をなくしたいです。
そうですか。では、頑張ってください。

フォーラムルールより
「うまくいきません」という質問は大抵回答に困ります。

1. 自分は今何がしたくて

2. どう取り組んで(作ったプログラムはどれで

3. どのようなエラーやトラブルで困っていて

4. 自分は何が解らないのか、知りたいのか

5. 今のCの知識はどの程度なのか

この5点をしっかりと明記して下さい。

環境に依存する場合やライブラリを使っているときは

使っているOS名・コンパイラ名・ライブラリ名も明記しましょう。

コンパイルエラーの質問時は必ず最低限のエラーメッセージも書きましょう 。
3、4、5およびその後の文章に相当する部分が無いようですね。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
Dixq (管理人)
管理人
記事: 1661
登録日時: 9年前
住所: 北海道札幌市
連絡を取る:

Re: 弾のすり抜け対策がうまくいきません

#3

投稿記事 by Dixq (管理人) » 3年前

解決したい問題が何かを確認させてください。
弾同士がすり抜けるのはダメなんですか?
シューティングゲームにおける自機と弾との当たり判定の計算とは違うものですか?

もし高速に動く弾と高速に動く弾との正確な当たり判定を計算したければ、
「片端円-長方形-片端円」が自由に回転するもの同士の当たり判定を計算することになると思います。
hit.png
hit.png (17.98 KiB) 閲覧数: 913 回
当たり判定の計算って結構難しいんですよ。
円と自由回転する長方形の当たり判定の計算方法はうちのサイトで紹介しているので参考にしてみてください。
http://dixq.net/rp/34.html

実際には長方形と長方形の当たり判定を計算する必要があります。
なお、長方形は2つの三角形から出来ていますから、正確には2つの三角形の連結したもの同士と当たり判定を計算すればよいです。
これもなかなか難しく、ウェブにはあまりいい資料はありませんでした。
唯一ヒットしたのはこのサイトです。
h ttp://marupeke296.com/COL_3D_No21_TriTri.html

また、私も持っていますが、このような時は「3Dグラフィックのための数学」という本が役に立ちます。

この本の363ページ目に三角形同士の当たり判定の計算方法が載っています。

また、ライブラリで計算する方法もあります。
DXライブラリなら以下のような関数が用意されています。


Segment_Segment_MinLength  二つの線分の最近点間の距離を得る
Segment_Triangle_MinLength  線分と三角形の最近点間の距離を得る
Segment_Point_MinLength  線分と点の一番近い距離を得る
HitCheck_Line_Triangle  三角形と線分の当たり判定

# 余談ですが、一般的に使われる名前は bool exists; です。

アバター
Dixq (管理人)
管理人
記事: 1661
登録日時: 9年前
住所: 北海道札幌市
連絡を取る:

Re: 弾のすり抜け対策がうまくいきません

#4

投稿記事 by Dixq (管理人) » 3年前

みけCAT君

必要な情報はほとんどそろっていると思います。
・解決したい課題
・現在取り組んだコード
・使用しているライブラリ名(DxLib.hと書いてあります)
が分かればおおむね回答出来ると思います。
冷たい回答をせずにもう少し親身になってみませんか?

アバター
usao
記事: 1565
登録日時: 6年前

Re: 弾のすり抜け対策がうまくいきません

#5

投稿記事 by usao » 3年前

No.3にて貼られている絵のような状態の判定がしたいのだとすれば,

2つの ”(長方形+円×2 で構成された)面積を持つ図形” 同士の判定を正直に(?)やらずとも

一方の弾由来の図形を他方の弾の半径分だけ太らせて,
図形 vs 線分 の形にもっていけそうに思うのですが,ダメでしょうか.
Collision.jpg
Collision.jpg (38.35 KiB) 閲覧数: 832 回

アバター
もるも
記事: 52
登録日時: 4年前
連絡を取る:

Re: 弾のすり抜け対策がうまくいきません

#6

投稿記事 by もるも » 3年前

補足させていただきます。

シューティングゲームで反射弾を作っていて、
この当たり判定は地形と弾の当たり判定に使うためのサンプルなんです。
地形チップのそれぞれの辺を線分として上下左右どの辺に当たっているかを調べるためです。

弾が小さかったりスピードが速いと線分をまたいで通り抜けてしまいます・・・。
それで通り抜けないようにしたいのですが、
龍神録プログラミングの館18章を参考にして、
ループを使ってフレーム間の弾同士の距離を少しずつ計算していくのを真似て作ってみたのですが、
上手くいきません。

コード:

void BulletCollision() {
    for (int i = 0; i < BULLET_MAX; i++) {
        //当たり判定
        vector2D O;//座標用
        O.X = bullet[i].X;  //弾の座標
        O.Y = bullet[i].Y;
        if (bullet[i].exist&&!(bullet[i].hit)) {
            if (LineCircleCollision(bullet[i].size, A, B, O)) {
                bullet[i].hit = true;  
                bullet[i].angle = bullet[i].angle*(-1); //角度を変える
                return;
        }
            //弾と弾のすきま用
            vector2D Q;//弾の座標用
                //前フレームと現在の弾の距離を求める
                double x = abs(bullet[i].X - bullet[i].preX);
                double y = abs(bullet[i].Y - bullet[i].preY);
                double length = sqrt(x*x + y*y);
                //弾何個分か調べる
                //(弾同士の距離-それぞれの弾の半径)÷弾の直径
                double b_num = (length - (bullet[i].size*2)) / (bullet[i].size*2);
                
                DrawFormatString(200, 200, GetColor(255, 255, 255), "b_num%d", (int)b_num);
            for (int j = 0; j <= b_num; j++) {
            //隙間の弾の数だけ少しづつ計算する
                        double b_x = bullet[i].preX;
                        double b_y = bullet[i].preY;
                        Q.X = b_x;
                        Q.Y = b_y;
                        if (LineCircleCollision(bullet[i].size, A, B, Q)) {
                            bullet[i].hit = true;
                            bullet[i].angle = bullet[i].angle*(-1);
                            return;
                        }
        //弾の直径分増やしていく
                        b_x += cos(bullet[i].angle)*bullet[i].size*2;
                        b_y += sin(bullet[i].angle)*bullet[i].size*2;
                        DrawCircle(b_x, b_y, bullet[i].size, GetColor(255, 0, 100));//テスト用
        
            }
        }
    }
}

アバター
みけCAT
記事: 6235
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: 弾のすり抜け対策がうまくいきません

#7

投稿記事 by みけCAT » 3年前

パッと見でわかることは、
もるも さんが書きました://弾の直径分増やしていく
というコメントに反して、b_xおよびb_yをループ中変化しない値で毎回初期化しているので、増やして「いく」という感じがしないですね。
テストしていませんが、if文に付随するブロック中のDrawFormatStringより後の部分を以下のようにすると改善するかもしれません。

コード:

            // 毎回計算すると重くなりそうな気がするので変化量を事前に計算する
            double d_x = cos(bullet[i].angle)*bullet[i].size*2;
            double d_y = sin(bullet[i].angle)*bullet[i].size*2;
            for (int j = 0; j <= b_num; j++) {
            //隙間の弾の数だけ少しづつ計算する
                        // この時点で変化を反映させる
                        double b_x = bullet[i].preX + d_x * j;
                        double b_y = bullet[i].preY + d_y * j;
                        Q.X = b_x;
                        Q.Y = b_y;
                        if (LineCircleCollision(bullet[i].size, A, B, Q)) {
                            bullet[i].hit = true;
                            bullet[i].angle = bullet[i].angle*(-1);
                            return;
                        }
        //弾の直径分増やす
                       // 従来通り一旦変数を更新してから描画してもよいが、直接描画してもよい
                        DrawCircle(b_x + d_x, b_y + d_y, bullet[i].size, GetColor(255, 0, 100));//テスト用
        
            }
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
もるも
記事: 52
登録日時: 4年前
連絡を取る:

Re: 弾のすり抜け対策がうまくいきません

#8

投稿記事 by もるも » 3年前

説明不足で皆さんを混乱させてしまってすみませんでした(><)
どういう風にしたかったのかというと、
前フレームと現在の弾の間をループで少しづつ計算する。
=可視化すると前フレームと現在の弾が隙間なく連なって見える。
という感じです。
完全ではありませんが速い弾も拾ってくれるようになりました。

今回みけCATさんのおかげで弾が連なって見えるようになって目的が達成されたので、
解決とさせていただきます。
みけCATさん、Dixq (管理人)さん、usaoさん、
いろんな方法を紹介してくださり、ありがとうございました。
当たり判定についてもっとためしてみたりして勉強したいと思います。

閉鎖

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