ページ 1 / 1
ボールとブロックの当たり判定
Posted: 2013年4月11日(木) 20:08
by ヨシタケ
以前にも同じ質問をしてある程度妥協していたのですが、
やはり気になってしまうのでもう一度質問します。
以前までは縦の辺と横の辺に同時に接触した場合を追加したことで少しはまともになりました。
しかし、無敵ブロックにヒットしたときそのままはまってしまうという不具合がありました。
それを防ぐためbool hitflagを追加しブロックにヒットしたとき一定時間ヒットの判定が起きないようにしたのですが、
こんどはまれにブロックをすり抜けるようになりました。
以下がブロックとボールの当たり判定とブロックの当たり判定のコードになります。
コード:
//-------------------------------------
// 関数名: bool HitBallandBlock(BLOCK block1,Line2D pt[4],bool *flag)
// 処理: ボールとブロックのあたり判定(ブロックのデータ,各4辺,ニードルボールフラグ)
//-------------------------------------
bool HitBallandBlock(BLOCK block1,Line2D pt[4],bool *flag){
if(hitflag == FALSE){
if((HitLineAndCircle(pt[0],ball.hit) == TRUE && HitLineAndCircle(pt[1],ball.hit) == TRUE)||
(HitLineAndCircle(pt[0],ball.hit) == TRUE && HitLineAndCircle(pt[2],ball.hit) == TRUE))
{
//上と左or右と同時に当たった時
if(block1.life <= 1 && ball.type == 5){
ball.dy*= -1;
}
}else if((HitLineAndCircle(pt[3],ball.hit) == TRUE && HitLineAndCircle(pt[1],ball.hit) == TRUE)||
(HitLineAndCircle(pt[3],ball.hit) == TRUE && HitLineAndCircle(pt[2],ball.hit) == TRUE))
{
//下と左or右と同時に当たった時
if(block1.life != 1 || ball.type != 5)
ball.dy*= -1;
}else{
//上、下、左、右に当たった時
if(HitLineAndCircle(pt[0],ball.hit) == TRUE){
if(block1.life != 1 || ball.type != 5)
ball.dy*= -1;
}
if(HitLineAndCircle(pt[3],ball.hit) == TRUE){
if(block1.life != 1 || ball.type != 5)
ball.dy*= -1;
}
if(HitLineAndCircle(pt[1],ball.hit) == TRUE){
if(block1.life != 1 || ball.type != 5)
ball.dx*= -1;
}
if(HitLineAndCircle(pt[2],ball.hit) == TRUE){
if(block1.life != 1 || ball.type != 5)
ball.dx*= -1;
}
}
//ブロックの各辺とボールとの当たり判定
if(HitLineAndCircle(pt[0],ball.hit) == TRUE ||
HitLineAndCircle(pt[1],ball.hit) == TRUE ||
HitLineAndCircle(pt[2],ball.hit) == TRUE ||
HitLineAndCircle(pt[3],ball.hit) == TRUE){
hitflag = TRUE;
if(ball.type == Needle)
*flag = TRUE;
else
*flag = FALSE;
return TRUE;
}
}
return FALSE;
}
コード:
//-------------------------------------
// 関数名: BlockHit()
// 処理: ブロックの当たり判定
//-------------------------------------
void BlockHit(BLOCK block1[13][9],int *blocknumber){
int i,j;
//ボールとブロックのあたり判定
for(i = 0; i < 13 ; i++){
for(j = 0; j < 9; j++){
//バーの各4辺(上、左、右、下)
Line2D pt[4] ={{{block1[i][j].hit.lt.x,block1[i][j].hit.lt.y},{block1[i][j].hit.lt.x + block1[i][j].hit.width,block1[i][j].hit.lt.y}},
{{block1[i][j].hit.lt.x,block1[i][j].hit.lt.y},{block1[i][j].hit.lt.x,block1[i][j].hit.lt.y + block1[i][j].hit.height}},
{{block1[i][j].hit.lt.x +block1[i][j] .hit.width,block1[i][j].hit.lt.y},{block1[i][j].hit.rb.x,block1[i][j].hit.rb.y}},
{{block1[i][j].hit.lt.x,block1[i][j].hit.lt.y + block1[i][j].hit.height},{block1[i][j].hit.rb.x,block1[i][j].hit.rb.y}}};
//ブロックがまだ消えていないときのブロックとボールの当たり判定
if(block1[i][j].flag == 0){
if(HitBallandBlock(block1[i][j],pt,&needleflag) == TRUE ){
if(block1[i][j].life >= 1){
if(needleflag == TRUE){
if(block1[i][j].life == 1){
block1[i][j].life--;
}else{
block1[i][j].life -= 2;
}
}else{
block1[i][j].life--;
}
}
if(block1[i][j].life == 0){
block1[i][j].flag = 1;
*blocknumber +=1;
PointPlus(2);
ItemAppearance(block1[i][j].hit);
}
}
}
}
}
}
無敵ブロックではまってしまうのはおそらく隣り合う二つのブロックにボールが当たって判定されてしまうのが問題だと
思います。通常のブロックでもたまに反射していないと思われる場合がありますし。
なにか当たり判定のいい方法がありましたら教えてください。お願いします。
Re: ボールとブロックの当たり判定
Posted: 2013年4月12日(金) 07:39
by dic
こちらには、再現する方法がないので、
ブロックと、ボールのあたり判定はどのように判定しているのでしょうか?
できたらコードで教えてください。
Re: ボールとブロックの当たり判定
Posted: 2013年4月12日(金) 10:31
by ヨシタケ
ボールとブロックの当たり判定はブロックの各4辺に当たった場合の判定としているため
線と円の当たり判定を使っています。
上の線と左右の線、下の線と左右の線、各4辺にあたった場合の判定となっています。
円と線の当たり判定はある参考書の内容そのまま使い以下のコードになります。
コード:
/*ベクトルの引き算を行う関数
座標からベクトルを求める場合にも使える*/
Vector2 SubVector(Vector2 v1, Vector2 v2){
Vector2 result;
result.x = v1.x - v2.x;
result.y = v1.y - v2.y;
return result;
}
/*ベクトルの足し算を行う関数*/
Vector2 AddVector(Vector2 v1, Vector2 v2){
Vector2 result;
result.x = v1.x + v2.x;
result.y = v1.y + v2.y;
return result;
}
//内積を求める関数
double DotProduct(Vector2 p,Vector2 q){
return p.x * q.x + p.y * q.y;
}
/*ベクトルの長さの二乗を求める関数
長さが必要なときはsqrtで平方根を求める*/
float VectorLengthSquare(Vector2 in){
return in.x * in.x + in.y * in.y;
}
/*ベクトルを正規化する関数*/
Vector2 Normalize(Vector2 in){
Vector2 result;
float l = sqrt( in.x * in.x + in.y * in.y );
result.x = in.x / l;
result.y = in.y / l;
return result;
}
//線と円の当たりを判定する
bool HitLineAndCircle(Line2D linein, Ball2D ballin){
Vector2 avec, bvec;
avec = SubVector(ballin.pos, linein.startpos);
bvec = SubVector(linein.endpos, linein.startpos);
float dot = DotProduct(avec, bvec); //内積を求める
float bl = VectorLengthSquare(bvec); //ベクトルbの長さの二乗を求める
if ( (dot > -ZEROVALUE) && (dot < bl) ) { //衝突の可能性有り
//円と線の距離を求める
//ボールの中心から垂直におろした線の交点を求める
Vector2 bnvec = Normalize(bvec); //ベクトルbの正規化
float dot2 = DotProduct(avec, bnvec);
bnvec.x *= dot2;
bnvec.y *= dot2;
Point2D kouten = AddVector(linein.startpos, bnvec);
Vector2 dist = SubVector(ballin.pos, kouten);
float al = VectorLengthSquare(dist);
if ( al < (ballin.hankei * ballin.hankei) ){
return TRUE;
}
}
return FALSE;
}
Re: ボールとブロックの当たり判定
Posted: 2013年4月12日(金) 10:41
by dic
コード:
bool HitBallandBlock(BLOCK block1,Line2D pt[4],bool *flag){
と関数が定義されていますが、Line2D pt[4] とあります。
Line2D pt, とベクトルひとつでいいのではないでしょうか?
HitBallandBlock( ... を呼び出しているコードはどうなっているのか教えてください。
Re: ボールとブロックの当たり判定
Posted: 2013年4月12日(金) 12:01
by ヨシタケ
このコードのことですか?
コード:
//-------------------------------------
// 関数名: BlockHit(BLOCK block1[13][9],int *blocknumber)
// 処理: ブロックの当たり判定(ブロックのデータ、壊したブロックの数)
//-------------------------------------
void BlockHit(BLOCK block1[13][9],int *blocknumber){
int i,j;
//ボールとブロックのあたり判定
for(i = 0; i < 13 ; i++){
for(j = 0; j < 9; j++){
//バーの各4辺(上、左、右、下)
Line2D pt[4] ={{{block1[i][j].hit.lt.x,block1[i][j].hit.lt.y},{block1[i][j].hit.lt.x + block1[i][j].hit.width,block1[i][j].hit.lt.y}},
{{block1[i][j].hit.lt.x,block1[i][j].hit.lt.y},{block1[i][j].hit.lt.x,block1[i][j].hit.lt.y + block1[i][j].hit.height}},
{{block1[i][j].hit.lt.x +block1[i][j] .hit.width,block1[i][j].hit.lt.y},{block1[i][j].hit.rb.x,block1[i][j].hit.rb.y}},
{{block1[i][j].hit.lt.x,block1[i][j].hit.lt.y + block1[i][j].hit.height},{block1[i][j].hit.rb.x,block1[i][j].hit.rb.y}}};
//ブロックがまだ消えていないときのブロックとボールの当たり判定
if(block1[i][j].flag == 0){
if(HitBallandBlock(block1[i][j],pt,&needleflag) == TRUE ){//ここでブロックとボールの判定
if(block1[i][j].life >= 1){
if(needleflag == TRUE){
if(block1[i][j].life == 1){
block1[i][j].life--;
}else{
block1[i][j].life -= 2;
}
}else{
block1[i][j].life--;
}
}
if(block1[i][j].life == 0){
block1[i][j].flag = 1;
*blocknumber +=1;
PointUpData(2);
ItemAppearance(block1[i][j].hit);
}
}
}
}
}
}
Re: ボールとブロックの当たり判定
Posted: 2013年4月12日(金) 16:38
by dic
いくつかバグが潜んでいる気がしますが、気が付いたひとつ
最初のコードの
//-------------------------------------
// 関数名: bool HitBallandBlock(BLOCK block1,Line2D pt[4],bool *flag)
// 処理: ボールとブロックのあたり判定(ブロックのデータ,各4辺,ニードルボールフラグ)
//-------------------------------------
bool HitBallandBlock(BLOCK block1,Line2D pt[4],bool *flag){
の21行目からしたのところの
ブロックの下にあたっていたら y *= -1 と
ブロックの上にあたっていたら y *= -1 で
これが同時に成立する場合、ボールがつきぬけているときに y = -1 x -1 = 1 となり、
貫通します。
ひとまず、ひとつだけ見つけました。
// === 追加 ===
if( ...ball.hit == TRUE && ... )
とありますが、このball.hit にはどこで値が代入されているのでしょうか?
そして、どこで初期化されているのでしょうか?
もしかしたら、透明のブロックになって存在しているかもしれません。
Re: ボールとブロックの当たり判定
Posted: 2013年4月12日(金) 17:39
by ISLe
当たっているかどうかという判定といっしょに、ボールがブロックに向かって移動しているかどうかという判定も加えると良いかもしれません。
ボールの移動量が一気にブロックを抜けるくらい大きいとダメですが、その場合ボールを少しずつ動かすように分けて処理すると使えます。
Re: ボールとブロックの当たり判定
Posted: 2013年4月12日(金) 21:03
by ヨシタケ
上と下同時に成立しないように
コード:
//上、下、左、右に当たった時
if(HitLineAndCircle(pt[0],ball.hit) == TRUE && ball.dy > 0){
if(block1.life != 1 || ball.type != 5)
ball.dy*= -1;
}
if(HitLineAndCircle(pt[3],ball.hit) == TRUE && ball.dy < 0){
if(block1.life != 1 || ball.type != 5)
ball.dy*= -1;
}
というようにしてボールの進む向きによって判定するように変えました。あと、ボールとブロックがあったたとき一定時間
判定されないようにするためのhitflagは当たり方によってはブロックをすり抜けてしまうので消しました。
ボールの計算は以下になります。
コード:
void BallUpData(BAR bar){
if(func_state == GAMEPLAY){
//ボール発射
if(CheckStatePad(5) == 1){
ball.flag = TRUE;
if( startshootflag == FALSE){
ball.dx = 2;
ball.dy = -5;
}
startshootflag = TRUE;
shootflag = TRUE;
}
//ボールの大きさ
if(ball.type == Normal){
ball.hit.hankei = 9;
}else if(ball.type == Small){
ball.hit.hankei = 5;
}else{
ball.hit.hankei = 7;
}
if(ball.flag == FALSE){
if(startshootflag == FALSE){
ball.hit.pos.x = bar.x - 23 + 5*bar.longth;
ball.hit.pos.y = bar.y - 22;
}
if(startshootflag == TRUE){
ball.hit.pos.x = bar.x + bardifball.x ;
ball.hit.pos.y = bar.y + bardifball.y;
}
}
//ボールの移動
if(ball.flag == TRUE){
//移動量を加算
ball.hit.pos.x+= ball.dx;
ball.hit.pos.y+= ball.dy;
}
}
//バーの各4辺(上、左、右、下)
Line2D pt[4] ={{{bar.hit.lt.x,bar.hit.lt.y},{bar.hit.lt.x + bar.hit.width,bar.hit.lt.y}},
{{bar.hit.lt.x,bar.hit.lt.y},{bar.hit.lt.x,bar.hit.lt.y + bar.hit.height}},
{{bar.hit.lt.x + bar.hit.width,bar.hit.lt.y},{bar.hit.rb.x,bar.hit.rb.y}},
{{bar.hit.lt.x,bar.hit.lt.y + bar.hit.height},{bar.hit.rb.x,bar.hit.rb.y}}};
if(barhitflag == FALSE){
//バーにあたったときのボールの移動
if((HitLineAndCircle(pt[0],ball.hit) == TRUE && HitLineAndCircle(pt[1],ball.hit) == TRUE)||
(HitLineAndCircle(pt[0],ball.hit) == TRUE && HitLineAndCircle(pt[2],ball.hit) == TRUE))
{
if(ball.hit.pos.x < bar.x){
ball.dx = -4;
ball.dy = -2;
}else{
ball.dx = 4;
ball.dy = -2;
}
}else if((HitLineAndCircle(pt[3],ball.hit) == TRUE && HitLineAndCircle(pt[1],ball.hit) == TRUE)||
(HitLineAndCircle(pt[3],ball.hit) == TRUE && HitLineAndCircle(pt[2],ball.hit) == TRUE))
{
if(ball.hit.pos.x < bar.x){
ball.dx = -4;
ball.dy = 2;
}else{
ball.dx = 4;
ball.dy = 2;
}
}else{
if(HitLineAndCircle(pt[0],ball.hit) == TRUE){
if(ball.hit.pos.x < bar.hit.lt.x + bar.hit.width/14){
ball.dx = -4;
ball.dy = -2;
}
if(ball.hit.pos.x > bar.hit.rb.x - bar.hit.width/14){
ball.dx = 4;
ball.dy = -2;
}
if(ball.hit.pos.x >= bar.hit.lt.x + bar.hit.width/14 && ball.hit.pos.x < bar.hit.lt.x + 5*bar.hit.width/14){
ball.dx = -2;
ball.dy = -5;
}
if(ball.hit.pos.x <= bar.hit.rb.x - bar.hit.width/14 && ball.hit.pos.x > bar.hit.rb.x - 5*bar.hit.width/14){
ball.dx = 2;
ball.dy = -5;
}
if(ball.hit.pos.x >= bar.hit.lt.x + 5*bar.hit.width/14 && ball.hit.pos.x < bar.hit.lt.x + 7*bar.hit.width/14){
ball.dx = -1;
ball.dy = -6;
}
if(ball.hit.pos.x <= bar.hit.rb.x - 5*bar.hit.width/14 && ball.hit.pos.x >= bar.hit.rb.x - 7*bar.hit.width/14){
ball.dx = 1;
ball.dy = -6;
}
}
if(HitLineAndCircle(pt[3],ball.hit) == TRUE){
ball.hit.pos.y = bar.hit.rb.y + 7;
ball.dy*= -1;
}
if(HitLineAndCircle(pt[1],ball.hit) == TRUE){
ball.dx = -6;
ball.dy = -2;
}
if(HitLineAndCircle(pt[2],ball.hit) == TRUE){
ball.dx = 6;
ball.dy = -2;
}
}
if(HitLineAndCircle(pt[0],ball.hit) == TRUE ||
HitLineAndCircle(pt[1],ball.hit) == TRUE ||
HitLineAndCircle(pt[2],ball.hit) == TRUE ||
HitLineAndCircle(pt[3],ball.hit) == TRUE){
barhitflag = TRUE;
if(ball.type == Sucker){
if(shootflag == TRUE){
bardifball.x = ball.hit.pos.x - bar.x;
bardifball.y = ball.hit.pos.y - bar.y;
}
shootflag = FALSE;
ball.flag = FALSE;
}
PointUpData(1);
}
}
}
プレイヤーの計算(バー、ボール、必殺技)はPlayerUpData()にまとめてあります。
そして、以下がゲームプレイ時の計算になります。
コード:
//-------------------------------------
// 関数名: void GameMainUpData()
// 処理: ゲーム画面での計算
//-------------------------------------
void GameMainUpData(){
if(gamestate ==Start || gamestate == Standby)
PlayerUpData();
StageMainUPData(stagenumber);
}
StageMainUPData(stagenumber);の中にBlockHit関数が入っています。
実際はもう少し長いですが今回の質問ではひつようなさそうだったので短くしました。
あと、ボールの初期化BallIniがあるのですが,
コード:
//-------------------------------------
// 関数名: void BallIni()
// 処理: ボールの初期化
//-------------------------------------
void BallIni(){
if(func_state == FIRSTINIT || func_state == GAMEOVER){
LoadDivGraph("Media/img/ball2.bmp",6,3,2,60,60,img_ball);
ball.life = 3;
ball.flag = FALSE;
}
if(func_state == GAMESTARTINIT){
nball = ball.type;
}
if(func_state == GAMEINIT){
startshootflag = FALSE;shootflag = FALSE;
barhitflag = FALSE;
barhitcnt = 0;
hitflag = FALSE;
hitcnt = 0;
ball.flag = FALSE;
}
}
このとおり、この中でball.hitの初期化はおこなっていません。今までなんともなかったので別に問題ないとおもっていたのですが、ちゃんと値を入れるべきでしょうか?
Re: ボールとブロックの当たり判定
Posted: 2013年4月13日(土) 05:43
by dic
ballの構造体の定義も教えてください。
ヨシタケ さんが書きました:このとおり、この中でball.hitの初期化はおこなっていません。今までなんともなかったので別に問題ないとおもっていたのですが、ちゃんと値を入れるべきでしょうか?
偶然としかいいようがないです。
値は入れるべきです。
Re: ボールとブロックの当たり判定
Posted: 2013年4月13日(土) 10:50
by ヨシタケ
ボールの構造体は
コード:
//ボールに関する構造体
typedef struct BALL{
double dx,dy; //速さ
bool flag; //状態(固定or発射)
int life; //残り弾数
int type; //タイプ
Ball2D hit; //当たり判定
}BALL;
というようになります。
あと、構造体の中身をちゃんと初期化するようにしました。
コード:
//-------------------------------------
// 関数名: void BallIni()
// 処理: ボールの初期化
//-------------------------------------
void BallIni(){
if(func_state == FIRSTINIT || func_state == GAMEOVER){
LoadDivGraph("Media/img/ball2.bmp",6,3,2,60,60,img_ball);
ball.life = 3;
ball.flag = FALSE;
}
if(func_state == GAMESTARTINIT){
nball = ball.type;
}
if(func_state == GAMEINIT){
startshootflag = FALSE;shootflag = FALSE;
barhitflag = FALSE;
barhitcnt = 0;
hitflag = FALSE;
hitcnt = 0;
ball.hit.pos.x = 0; ball.hit.pos.y = 0;
ball.dx = 2;ball.dy = -5;
ball.flag = FALSE;
}
}
ball.typeは最初に選択したキャラクターで変わるのでプレイヤーのソース(player.cpp)の初期化で
ChangeBallType(int 変更するボールの種類)として初期化してあります。
Re: ボールとブロックの当たり判定
Posted: 2013年4月13日(土) 16:08
by dic
申し訳ありません、多少私がトチっているところもありました。
まったくどこらへんが悪いのか見当がつかないので、
メールにてソースコード一式送ってくれるか、ソースコード一式公開するか
お願いできるでしょうか?
いやだったらかまいません。
Re: ボールとブロックの当たり判定
Posted: 2013年4月14日(日) 00:03
by ヨシタケ
下記のサイトにソースをアップしました。パスワードは「YT2013」です。
http://www1.axfc.net/uploader/so/2867804
一応あれからボールがおかしな動きをするのは2つのブロックに同時にあたって判定されているのが
いけないのかとおもったためブロックとボールがあたったときの判定を
コード:
if(HitBallandBlock(block1[i][j],pt,&needleflag) == TRUE ||
(HitBallandBlock(block1[i][j],pt,&needleflag) == TRUE && HitBallandBlock(block1[i-1][j],pt,&needleflag) == TRUE) ||
(HitBallandBlock(block1[i][j],pt,&needleflag) == TRUE && HitBallandBlock(block1[i+1][j],pt,&needleflag) == TRUE) ||
(HitBallandBlock(block1[i][j],pt,&needleflag) == TRUE && HitBallandBlock(block1[i][j-1],pt,&needleflag) == TRUE) ||
(HitBallandBlock(block1[i][j],pt,&needleflag) == TRUE && HitBallandBlock(block1[i][j+1],pt,&needleflag) == TRUE)){
}
というように変えてみました。HitBallandBlockでボールの移動向きの変更を行っているのであまり意味はありませんでした。
アップした内容に動画も入れておきました。動画内で気になる点は
(1) 青いブロックが右上にあるときの判定時のボールの動きがおかしい(上記の変更が問題かなと思っています)
(2) 無敵ブロック(ねずみ色)をボールがすり抜ける。
の2つです。
Re: ボールとブロックの当たり判定
Posted: 2013年4月14日(日) 18:32
by dic
すいません、ソースコードまでアップしてもらったのですが、
仕事の都合上、あまり時間をとれませんので、ほかの方に解決を手伝ってもらってもいいでしょうか?
ここまでつきあわせておいてすいません
Re: ボールとブロックの当たり判定
Posted: 2013年4月14日(日) 22:32
by ヨシタケ
別に構いません。
アドバイス色々と役に立ったのでありがとうございました。
Re: ボールとブロックの当たり判定
Posted: 2013年4月15日(月) 18:58
by softya(ソフト屋)
「DXライブラリ質問掲示板」とマルチポストに成っていますので、フォーラムルール違反となります。相互リンクをお願いします。
【補足】ISLe さんの回答をスルーしているようですが、一番ポイントを付いた内容だと思います。
根本的な問題を抜きにして解決はないと思いますが。
Re: ボールとブロックの当たり判定
Posted: 2013年4月15日(月) 20:11
by ヨシタケ
申し訳ございません。以後気をつけます。
相互リンクとはどのようにすればよりしいのでしょうか?
インターネットで調べたのですが、自分のサイトの場合しか見つからなかったため、
さすがに勝手に行うのも心配なので質問させていただきました。
Re: ボールとブロックの当たり判定
Posted: 2013年4月15日(月) 20:16
by softya(ソフト屋)
ヨシタケ さんが書きました:申し訳ございません。以後気をつけます。
相互リンクとはどのようにすればよりしいのでしょうか?
インターネットで調べたのですが、自分のサイトの場合しか見つからなかったため、
さすがに勝手に行うのも心配なので質問させていただきました。
あちらからこちらの質問へのリンク、こちらからあちらの質問へのリンクを貼れば相互リンクです。
どちらの掲示板の回答者も、相互に質問や回答が参照可能な状態にしてください。
Re: ボールとブロックの当たり判定
Posted: 2013年4月15日(月) 20:22
by ヨシタケ
Re: ボールとブロックの当たり判定
Posted: 2013年4月15日(月) 21:00
by softya(ソフト屋)
ボールの当たり判定ですが、ゲームの中の衝突というのは、時間が断片的に流れてしまうゲームの特性を上手く制御してやらないといけません。
どういう事かと言うと現実のボールは時間に従って連続的に移動しますが、ゲームの中のボールは小ワープを繰り返すように断片的な時間の中を空間位置をスキップしながら移動します。
[移動のイメージ]

- ball.png (3.39 KiB) 閲覧数: 11235 回
極端な場合は反対側で衝突してしまいます。

- ball2.png (2.72 KiB) 閲覧数: 11235 回
これが断片的な時間でボールが空間位置をスキップしていると言うことです。
これは絶対に起こりますので完全には防ぐことはできませんが、ISLe さんの回答にある通り少しずつ動かすことで擬似的に解決出来ます。

- ball3.png (2.01 KiB) 閲覧数: 11235 回
ポイント1からポイント2へ1フレームで移動してしまうなら、間の空間を1ピクセルづつ移動するようにforループで回して表示なしで移動と当たり判定だけを行うのです。
描画は前フレームのポイント1と今フレームのポイント2(当たったら反射するので違う方向へ行くので実際はポイント2で無い)だけです。
Re: ボールとブロックの当たり判定
Posted: 2013年4月16日(火) 00:15
by ヨシタケ
一応、当たり判定が少しずつ変化するようにしようと思い
コード:
for(int i = 0;i<10;i++){
ball.hit.pos.x += ball.dx/10;
ball.hit.pos.y += ball.dy/10;
}
というように変えてみましたが特に変化はありませんでした。
当たり判定の設定をfor文の中で行っていないからでしょうか?
Re: ボールとブロックの当たり判定
Posted: 2013年4月16日(火) 00:24
by softya(ソフト屋)
はい。当たり判定をしていないので意味は無いですね。
当たった後の処理も必要です。
表示が無いだけで他の処理は全部必要だと思ってください。
10回ループ中に2回衝突する可能性もあります。
Re: ボールとブロックの当たり判定
Posted: 2013年4月16日(火) 17:36
by はるかぜ
ちょっと動かして試してみましたが、無敵ブロックの横の部分にぶつかった場合の反射後に無敵ブロックをすり抜けてしまうようです。
ブロックの角にぶつかった時の処理を追加してみたらマシになるかもしれません。
Re: ボールとブロックの当たり判定
Posted: 2013年4月16日(火) 17:44
by softya(ソフト屋)
はるかぜ さんが書きました:ちょっと動かして試してみましたが、無敵ブロックの横の部分にぶつかった場合の反射後に無敵ブロックをすり抜けてしまうようです。
ブロックの角にぶつかった時の処理を追加してみたらマシになるかもしれません。
現状の問題を私の図の様に書いてもらえるとイメージが共通化出来ると思います。
>無敵ブロックの横の部分にぶつかった場合の反射後に無敵ブロックをすり抜けてしまうようです。
すり抜けるパターンを図式化できますでしょうか?
Re: ボールとブロックの当たり判定
Posted: 2013年4月17日(水) 12:03
by ヨシタケ
はるかぜ さんが書きました:ちょっと動かして試してみましたが、無敵ブロックの横の部分にぶつかった場合の反射後に無敵ブロックをすり抜けてしまうようです。
ブロックの角にぶつかった時の処理を追加してみたらマシになるかもしれません。
一応、角にあたったときの判定として上の辺と左右の辺に当たった場合、下の辺と左右の辺に当たった場合の判定を
行っています。
for文の中に当たり判定を行うということですが、ball.cppではボールの当たり判定の移動計算とボールとブロックの当たり判定を別の関数内で行ます。いっそのことblock.cppでボールとブロックの当たり判定の関数を書き直し、その関数をfor文の中にいれようと考えたのですが、block変数自体block.cpp内で定義されたものを使っているわけではないため無理でした。(block変数はstage.cppで定義)。
また。block.cpp内でblock変数を使うようにblockの初期化、描画、計算を行おうとも考えたのですが、初期化と描画が
ステージ毎に異なるためこの方法も無理でした。
これら以外の方法は思い浮かびませんでした。なにか良い方法がありましたらどうか教えてください。お願いします。
Re: ボールとブロックの当たり判定
Posted: 2013年4月17日(水) 12:12
by softya(ソフト屋)
すいません。コードの関数関係が深くてややこしいです。
ちゃんと把握していないですが、GameMainUpData();からの呼び出しそのものを改めることはできませんか?
ちなみに、これを簡単に直せないと言うことは関数間のつながりの設計に問題が有る事になります。
【補足】
素直に作ると バーの移動 → ボールの移動 → ボールとバーやブロックの当たり判定
となりますが、ファイル分割/モジュール化を優先しすぎてこの形に成っていないものと思われます。
これは、可読性・メンテナンス性上げるためのモジュール分割として本末転倒です。
【追記】
こういう形にはできませんでしょうか?
コード:
for( ループ10回 ) {
バーの移動()
ボールの移動()
if( ボールとバーの当たり判定() ) {
ボールの反射()
} else if( ボールとブロックの当たり判定() ) {
ブロックの消滅()
ボールの反射()
}
}
Re: ボールとブロックの当たり判定
Posted: 2013年4月18日(木) 01:18
by ヨシタケ
【追記】の内容ですが、これはGameMainUpdata()の中に書けばよろしいのでしょうか?
この場合、バー、ボール、ブロックの構造体をGame.cpp内で定義する必要があると考えています。
例えば、今はボールの初期化ですと
コード:
//-------------------------------------
// 関数名: void BallIni()
// 処理: ボールの初期化
//-------------------------------------
void BallIni(int state,BALL *ball){
if(func_state == FIRSTINIT || func_state == GAMEOVER){
LoadDivGraph("Media/img/ball2.bmp",6,3,2,60,60,img_ball);
ball->life = 3;
ball->flag = FALSE;
}
if(func_state == GAMESTARTINIT){
if(state == 0)
ball->type = Normal;
if(state == 1)
ball->type = Small;
if(state == 2)
ball->type = Needle;
nball = ball->type;
}
if(func_state == GAMEINIT){
startshootflag = FALSE;shootflag = FALSE;
barhitflag = FALSE;
barhitcnt = 0;
hitflag = FALSE;
hitcnt = 0;
ball->x = 0;ball->y = 0;
ball->hit.pos.x = 0; ball->hit.pos.y = 0;
ball->dx = 2;ball->dy = -5;
ball->flag = FALSE;
}
}
というように変えて、Game.cpp内のボール構造体の中身を変更する必要があると思っています。
ただ、これだと今はまだ変えてないのですが画像の読み込みに使っているimg_ballなどのstatic変数も
Game.cpp内に定義する必要があり、とても長いソースになってしまうのではと思っているのですが、どうですか。
それともこのやり方は違うのでしょうか?
Re: ボールとブロックの当たり判定
Posted: 2013年4月18日(木) 11:25
by usao
ちゃんとコードとか読んでないので 的外れだったり 全く参考にならない 可能性もありますが…
自分だったら衝突判定は 「円」vs「ブロックの4辺」 ではなくて
「円の中心座標(点)」vs「円の中心がここにはいったらブロックに衝突になる 領域」みたいな捉え方で考えるかなぁ?とか.
後者の領域境界線は,ブロックの外周を,ボール半径分だけ外方向に膨らませたもので,角の部分が丸い四角形.
あと,ボールの速度がブロックサイズに対して早いときに「1Pixelずつ動くループで処理」とかじゃなくて,
ボール中心の移動前位置と移動先(予定)位置とを結ぶ線分が,上記の領域境界線と交わるかどうかを計算すれば良い.
(領域境界線は{4つの線分と4つの円}パーツにわけて処理するのかな.
その場合,複数のパーツで交点が求まるときは,ボールの移動前座標にもっとも近い場所を選べばよいかな?)
あとは,交点位置での法線方向ベクトル(領域境界線に垂直でブロックから見て外向きな方向のベクトル)とボールの移動ベクトルの内積から,
ボールが侵入してくる状態なのか,出ていく状態なのか をチェックして,後者の場合は衝突を無視するとかすればよさそうに思う.
#ブロックの角にあたった場合にどのように跳ね返るべきなのかよくわからないけれど,
例えば,前記衝突位置における法線ベクトル を跳ね返り方向とすれば
角への当たり具合によって 跳ね返る方向が滑らかに変化するようにできるね.
横から失礼しました.
何かちょっとでも参考になる点があれば幸いです.
Re: ボールとブロックの当たり判定
Posted: 2013年4月18日(木) 14:38
by softya(ソフト屋)
>あと,ボールの速度がブロックサイズに対して早いときに「1Pixelずつ動くループで処理」とかじゃなくて,
>ボール中心の移動前位置と移動先(予定)位置とを結ぶ線分が,上記の領域境界線と交わるかどうかを計算すれば良い.
線で当たり判定するときの問題は、1フレーム内に2回の反射がある前提だとコードがややこしくなるんでは?と言う問題があると思います。
それでも実装できるのなら問題ないのですが。
>この場合、バー、ボール、ブロックの構造体をGame.cpp内で定義する必要があると考えています。
Game.cppで構造体の中身を操作しないなら、構造体を保持する必要は無いと思います。
ただ、現状はプログラムが上手く動かないほうが問題ですので、とりあえず動くもの作った方が私は良いと思います。
綺麗なモジュール分割は一度忘れて動くものを作るという目標で作られたらどうでしょうか?
つまり、あまり良いとはいえないけど動くもの優先で。ヨシタケ さんの分かりやす形で作成する。
Re: ボールとブロックの当たり判定
Posted: 2013年4月18日(木) 16:37
by usao
>線で当たり判定するときの問題は、1フレーム内に2回の反射がある前提だとコードがややこしくなるんでは?と言う問題があると思います。
複数回の反射への対応のややこしさは
時間をなんらかの一定の細かさでサンプリングしても同じ問題に直面するように思いますが,違うのでしょうか??
でも,そもそもそこまでまじめにやらなくてもいいんじゃないか?
すなわち,各フレームでは 時刻的に一番最初にぶつかる相手 との衝突だけを扱う とかでもわりとうまくいくんじゃないか?
とか思ってしまいますが,それじゃダメなのかなぁ.
(まじめ:1フレーム内で2個の衝突を解決 → 不真面目:2フレームかけて2個の衝突を解決
各フレーム間で扱う"時間"が均一でなくなってしまうけども,見た目わからないんじゃ?っていう.)
本当にまじめにフレーム間の時間を複数に区分して,複数の衝突を扱うのであれば,
「1/10フレームごと」とか「1Pixel動く時間ごと」とかではなく,「衝突時刻」で区切るべきなのかな?
(私が書いたアイデアで言えば…交点座標が ボールの移動軌跡線分上のどこにあるか から衝突時刻がわかる.
例えば,0.6フレーム後に衝突するならば,時計をそこまで進めて=ボールの位置と速度を然るべく更新して,
残り0.4フレーム分をそこから同じように計算する,という具合.
で,不真面目にやる場合は,例えば,残りの0.4フレーム分をやらないでその場で処理を終えてしまう.)
Re: ボールとブロックの当たり判定
Posted: 2013年4月18日(木) 16:47
by softya(ソフト屋)
usao さんが書きました:
でも,そもそもそこまでまじめにやらなくてもいいんじゃないか?
すなわち,各フレームでは 時刻的に一番最初にぶつかる相手 との衝突だけを扱う とかでもわりとうまくいくんじゃないか?
とか思ってしまいますが,それじゃダメなのかなぁ.
(まじめ:1フレーム内で2個の衝突を解決 → 不真面目:2フレームかけて2個の衝突を解決
各フレーム間で扱う"時間"が均一でなくなってしまうけども,見た目わからないんじゃ?っていう.)
経験則ですが、ボールのような単純な動きでフレーム毎の移動距離が変わると速度が変化する事になるので変な挙動に見えると思います。
usao さんが書きました:
本当にまじめにフレーム間の時間を複数に区分して,複数の衝突を扱うのであれば,
「1/10フレームごと」とか「1Pixel動く時間ごと」とかではなく,「衝突時刻」で区切るべきなのかな?
(私が書いたアイデアで言えば…交点座標が ボールの移動軌跡線分上のどこにあるか から衝突時刻がわかる.
例えば,0.6フレーム後に衝突するならば,時計をそこまで進めて=ボールの位置と速度を然るべく更新して,
残り0.4フレーム分をそこから同じように計算する,という具合.
で,不真面目にやる場合は,例えば,残りの0.4フレーム分をやらないでその場で処理を終えてしまう.)
それ(線での当たり判定)の問題は実装がヨシタケさんの負担になると言う点だけです。
たしかに正確にはなりますが、そこまでしなくてもそれらしく見えるという経験則での答えになりますがよろしいでしょうか。
【補足】
選ぶのはヨシタケさんですので、私に強要する権利はありません。
なので経験則として私はこう思いますよと言う話だけさせて頂きました。
Re: ボールとブロックの当たり判定
Posted: 2013年4月18日(木) 17:33
by ISLe
横に並んだ2つのブロックの境目に斜めにボールが突っ込んだ場合、反射後のボールは下に向かうことになるはずですが…
フレームを跨いでブロックひとつずつ判定する場合、斜めに反射しながらそのまま上昇してしまうことがあります。
Re: ボールとブロックの当たり判定
Posted: 2013年4月18日(木) 17:40
by usao
こちらは経験則ですらない思いつきを 単に 少しでも参考になる部分があればいいな 程度で言ってるのだけなので
そんな雰囲気でとらえてください.
で,また的外れかもしれませんが,ブロックとの当たり判定ができているのであれば,
コード:
Vector2D RefNormalVec = { 0,0 } //ボールの運動を反射させる方向:ボールの速度ベクトルのうち,このベクトル方向成分を反転する
for( all blocks )
{
if( ブロックとボールがHitしているか? )
{
Vector2D += 運動反射方向(接触点の法線ベクトル)
}
}
for( all 壁 )
{
同上
}
if( ||RefNormalVec||>0 )
{
RefNormalVec方向について,ボールの速度を反転
}
みたくすれば複数ブロックとかブロックと壁に同時に当たってもうまく跳ね返りませんか?
Re: ボールとブロックの当たり判定
Posted: 2013年4月18日(木) 17:49
by softya(ソフト屋)
usao さんが書きました:こちらは経験則ですらない思いつきを 単に 少しでも参考になる部分があればいいな 程度で言ってるのだけなので
そんな雰囲気でとらえてください.
で,また的外れかもしれませんが,ブロックとの当たり判定ができているのであれば,
コード:
Vector2D RefNormalVec = { 0,0 } //ボールの運動を反射させる方向:ボールの速度ベクトルのうち,このベクトル方向成分を反転する
for( all blocks )
{
if( ブロックとボールがHitしているか? )
{
Vector2D += 運動反射方向(接触点の法線ベクトル)
}
}
for( all 壁 )
{
同上
}
if( ||RefNormalVec||>0 )
{
RefNormalVec方向について,ボールの速度を反転
}
みたくすれば複数ブロックとかブロックと壁に同時に当たってもうまく跳ね返りませんか?
いくつかのパターンを図に書いてみたほうが良いんじゃないででしょうか。
すごく条件式が単純すぎるように思います。
「ブロックとボールがHitしているか?」で複雑な処理をしているなら別ですが。
Re: ボールとブロックの当たり判定
Posted: 2013年4月19日(金) 10:36
by usao
すみません,記述不足でしたでしょうか.
えーと,まず,やろうとしていることは
(1)ボールvsブロックの接触判定 (ブロックの他にも自機?とかステージ端の壁とかその他オブジェクトとの判定もあるけど)
(2)接触時のボールの運動変更
で,現状コードでは 以下のような処理の流れになっているのだと思うんですが…
コード:
for( 例えば10回ループ ) //フレーム間時間を,ボール移動量が十分小さくなる短い時間にわけて処理する
{
オブジェクト群の移動(ボールや自機等の位置を,考えている短い時間に動く分だけ 更新)
//ボールvsブロックの処理
for( int i=0; i<nBlocks; i++ ) //すべてのブロックについてループ
{
if( ボールとi番目のブロックがHitしているか? ) //ここではこれは既存コードで判定できているのだとみなして話をしています
{
//ボールの跳ね返り処理
ボールの速度ベクトルのうち
Hitした箇所(現状,{4辺,4角}の8パターンに分けて考えられている)に応じた成分だけを反転する
}
}
//ボールvs自機 とか ボールvs壁 とかも同様の処理を行う(略)
}
つまり,同一の"短い時間"内に複数の衝突がある場合に,
個々の衝突における跳ね返り処理の総和 によって 正しく見える方向への跳ね返り結果 を得よう,という方式.
で,動画を拝見した感じだと,これ(複数に接触した際の総和結果)がうまくいっていないように見受けられました.
これに対して,各"短い時間"内では
「複数回の跳ね返り処理」を行うのではなくて,
「正しく見える方向への」1回の跳ね返り処理 を行う方式 に変更したら問題が改善しませんか?という提案を申し上げました.
例えば,ボールがステージ左上角に達した際,上端の壁と左端の壁の2か所に「同時に」衝突する形になります.
↑に書いた疑似コードのような処理ではこれを,上壁による跳ね返り(VY反転)+左壁による跳ね返り(VX反転) として処理しますが,
これは,2つの壁の法線の合成ベクトル(右下45度な斜め方向)を,衝突面の法線 と見なして跳ね返り処理を1回だけ行うことと等価です.
別の例で,隣接する2つのブロック [ブロック][ブロック] に同時に衝突する際はどうでしょうか.
現在,これを単純に行うと,VY反転が2回発生してしまうので その結果跳ね返らずに通り抜ける ような結果が起きてしまい
その対策に苦心されているのだと見受けますが,
今回提案した方式であれば,このパターンも前記壁の例と同じように扱うことができるのではないか,と…
説明が下手で&長文すみません.
Re: ボールとブロックの当たり判定
Posted: 2013年4月19日(金) 10:55
by softya(ソフト屋)
ヨシタケさんの現状のコードは10回ループ改造中で完了しておりません。
フレーム毎に10ピクセル近く移動しているコードが最後に動いているコードとなります。
なので問題が起きているは、過去の時点のものであり1ピクセル移動では無いと言うことです。
前にも書きましたが、同時衝突だけ上手く処理できても意味が無いのは理解されていますよね?
こういうのに対処するには、usaoさんの提案だけだと不十分だと思うわけです。

- 反射.png (1.87 KiB) 閲覧数: 10833 回
【補足】
usaoさんの方式に無理があると言っているのではなくて、色々となパターンに対処できるように書けば問題なく動作します。
ただ、それはやはりそれなりの複雑なものとなります。現状(usaoさんの擬似コード)のようには単純には済まないと言うことです。
その場合、今の処理を捨てても良いほど効果的かというと問題が残るわけです。
Re: ボールとブロックの当たり判定
Posted: 2013年4月19日(金) 11:27
by usao
(1)提示いただいた図のパターンに対して何が不十分となるのかわかりません というか
そういう状況を こうすれば簡単に取り扱えるんじゃない? という旨の提案なので,
どうしてダメなのか を明示いただけると助かります.
(2)複雑…でしょうか?
・法線の加算処理→ブロックの4辺について衝突していたら(現コードあり),{1,0},{-1,0},{0,-1},{0,1}のいずれかを加算するだけ
(角に当たった場合は2辺にHitしているから2つを加算することになる)
・最終的な1回の跳ね返り処理
if( 法線.x !=0 )ボールのVX *= -1;
if( 法線.y !=0 )ボールのVY *= -1;
程度のことを言っているだけなのですが.
(ベクトルの「成分」という書き方には語弊がありました.VX,VY反転で跳ね返すのだから↑のような単純処理ですね)
(3)それはそれとして,質問者様不在でやりとりだけ行うのは邪魔っぽいので これ以上は控えます.
ほんの一部分でも現状打破の助けになる部分があったら良いのですが,かえって邪魔だったかもですね.
Re: ボールとブロックの当たり判定
Posted: 2013年4月19日(金) 17:49
by ISLe
usao さんが書きました: ・法線の加算処理→ブロックの4辺について衝突していたら(現コードあり),{1,0},{-1,0},{0,-1},{0,1}のいずれかを加算するだけ
(角に当たった場合は2辺にHitしているから2つを加算することになる)
・最終的な1回の跳ね返り処理
if( 法線.x !=0 )ボールのVX *= -1;
if( 法線.y !=0 )ボールのVY *= -1;
横に並んだ2つのブロックの境目に斜めにボールが突っ込む場合、仮に左下から右上に向かうボールは右のブロックに衝突しますが、左のブロックには衝突しません。
#というふうに処理します。
左のブロックに衝突するためには、右のブロックに衝突して向きを変えなければならないので、同時に判定できません。
同時に判定できるように、左下から右上に向かうボールが左のブロックに衝突するものとすると、今度は左下から右上に向かうボールがブロックの右下をかすめるだけで引き返すように反射してしまうようになります。
これはいわゆる壁ハマりを引き起こすので最優先で避ける必要があります。
想像ではなく経験を元に事実を述べているということをご理解ください。
いたずらに否定しようというのではありません。
Re: ボールとブロックの当たり判定
Posted: 2013年4月19日(金) 23:31
by ヨシタケ
いくつものためになるアドバイスありがとうございます。
今回は当たり判定の移動を分割して行う方法でやってみようと思っています。
実際アドバイス頂いた通りGameUpData関数内に
コード:
for(int i = 0;i<10; i++){
BarUpData(&bar); //バーの移動
BallUpData(&ball,bar); //ボールの移動
HitBallandBar(&ball,bar); //ボールとバーの当たり判定
HitBlockandBall(block,&ball,&blocknumber); //ブロックとボールの当たり判定
}
という文を加えました。(当たり判定をそのまま書くと長くなってしまうため、それそれ関数にしました。)
それでもやはり無敵ブロックに当たった場合ボールがはまってしまいました。
一応それぞれの関数は
BarUpData
コード:
void BarUpData(BAR *bar){
int i,sayu_flag=0,joge_flag=0;
double x,y,mx,my,naname=1;
double move_x[4] ={-bar->speed,bar->speed,0,0},move_y[4]={0,0,bar->speed,-bar->speed};//{左,右,下,上}のスピード
int inputpad[4];
inputpad[0]=CheckStatePad(configpad.left); inputpad[1]=CheckStatePad(configpad.right);
inputpad[2]=CheckStatePad(configpad.down); inputpad[3]=CheckStatePad(configpad.up);
//キャラクター選択
if(func_state == SELECTCHAR){
if(CheckStatePad(configpad.right)%30==1){ // 右キーが押された瞬間だけ処理
if(bar->state == 2){
bar->state = 0;
}else{
bar->state =bar->state + 1;
}
}
if(CheckStatePad(configpad.left)%30==1){ // 左キーが押された瞬間だけ処理
if(bar->state == 0){
bar->state = 2;
}else{
bar->state -=1;
}
}
}
if(func_state == GAMEPLAY){
if(bar->life > 0){
//バーの移動制御
for(i=0;i<2;i++)//左右分
if(inputpad[i]>0)//左右どちらかの入力があれば
sayu_flag=1;//左右入力フラグを立てる
for(i=2;i<4;i++)//上下分
if(inputpad[i]>0)//上下どちらかの入力があれば
joge_flag=1;//上下入力フラグを立てる
if(sayu_flag==1 && joge_flag==1)//左右、上下両方の入力があれば斜めだと言う事
naname=sqrt(2.0);//移動スピードを1/ルート2に
for(int i=0;i<4;i++){//4方向分ループ
if(inputpad[i]>0){//i方向のキーボード、パッドどちらかの入力があれば
x=bar->x , y=bar->y;//今の座標をとりあえずx,yに格納
mx=move_x[i]/10; my=move_y[i]/10;//移動分をmx,myに代入
x+=mx/naname , y+=my/naname;//今の座標と移動分を足す
if(!(x< field.x|| x>fieldmax.x - 9.5*bar->longth || y<field.y || y>fieldmax.y)){//計算結果移動可能範囲内なら
bar->x=x , bar->y=y;//実際に移動させる
}
}
}
}
//ダメージ時の無敵解除条件
if(bar->damegeflag == TRUE){
bar->cnt++;
if(bar->cnt>180){
if(bar->life <=0)
bar->life = lifemax;
bar->damegeflag = FALSE;
bar->cnt = 0;
}
}
}
if(bar->state == 1){
//キャラクターB
bar->hit.lt.x = bar->x - 45; bar->hit.lt.y = bar->y - 15;
bar->hit.rb.x = bar->x + 9 * bar->longth; bar->hit.rb.y = bar->y + 3;
bar->hit.height= bar->hit.rb.y - bar->hit.lt.y;
bar->hit.width = bar->hit.rb.x - bar->hit.lt.x;
}else{
bar->hit.lt.x = bar->x - 46; bar->hit.lt.y = bar->y - 15;
bar->hit.rb.x = bar->x + 9.75 * bar->longth; bar->hit.rb.y = bar->y + 3;
bar->hit.height= bar->hit.rb.y - bar->hit.lt.y;
bar->hit.width = bar->hit.rb.x - bar->hit.lt.x;
}
}
BallUpData
コード:
void BallUpData(BALL *ball,BAR bar){
if(func_state == OPTION){
if(CheckStatePad(configpad.left)%30 == 1 && ball->life >1)
ball->life--;
if(CheckStatePad(configpad.right)%30 == 1 && ball->life <5)
ball->life++;
}
if(func_state == GAMEPLAY){
//ボール発射
if(CheckStatePad(5) == 1){
ball->flag = TRUE;
if( startshootflag == FALSE){
ball->dx = 2;
ball->dy = -5;
}
startshootflag = TRUE;
shootflag = TRUE;
}
//ボールの大きさ
if(ball->type == Normal){
ball->hit.hankei = 9;
}else if(ball->type == Small){
ball->hit.hankei = 5;
}else{
ball->hit.hankei = 7;
}
if(ball->flag == FALSE){
if(startshootflag == FALSE){
ball->x = bar.x - 23 + 5*bar.longth;
ball->y = bar.y - 22;
ball->hit.pos.x = bar.x - 23 + 5*bar.longth;
ball->hit.pos.y = bar.y - 22;
}
if(startshootflag == TRUE){
ball->x = bar.x + bardifball.x;
ball->y = bar.y + bardifball.y;
ball->hit.pos.x = bar.x + bardifball.x;
ball->hit.pos.y = bar.y + bardifball.y;
}
}
//ボールの移動
if(ball->flag == TRUE){
//移動量を加算
ball->x+= ball->dx/10;
ball->y+= ball->dy/10;
ball->hit.pos.x += ball->dx/10;
ball->hit.pos.y += ball->dy/10;
//左の壁にヒット
if(ball->hit.pos.x < leftwall){
//PlaySoundMem(seall[5],DX_PLAYTYPE_BACK);
//ball.hit.pos.x = leftwall;
ball->dx*=-1;
}
//右の壁にヒット
if(ball->hit.pos.x > rightwall){
//ball.hit.pos.x = rightwall;
ball->dx*= -1;
//PlaySoundMem(seall[5],DX_PLAYTYPE_BACK);
}
//上の壁にヒット
if(ball->hit.pos.y < upwall){
//ball.hit.pos.y = 20;
ball->dy*=-1;
//PlaySoundMem(seall[5],DX_PLAYTYPE_BACK);
}
//ボールが下に落ちたとき
if(ball->hit.pos.y > autoline){
ball->life--;
ball->type = nball;
ball->hit.pos.x = bar.x;
ball->hit.pos.y = bar.y -20;
ball->flag = FALSE;
startshootflag = FALSE;
//ゲームオーバー
if(ball->life == 0)
ChangeGameState(Gameover);
}
}
}
}
HitBallandBar
コード:
void HitBallandBar(BALL *ball,BAR bar){
//バーの各4辺(上、左、右、下)
Line2D pt[4] ={{{bar.hit.lt.x,bar.hit.lt.y},{bar.hit.lt.x + bar.hit.width,bar.hit.lt.y}},
{{bar.hit.lt.x,bar.hit.lt.y},{bar.hit.lt.x,bar.hit.lt.y + bar.hit.height}},
{{bar.hit.lt.x + bar.hit.width,bar.hit.lt.y},{bar.hit.rb.x,bar.hit.rb.y}},
{{bar.hit.lt.x,bar.hit.lt.y + bar.hit.height},{bar.hit.rb.x,bar.hit.rb.y}}};
//バーにあたったときのボールの移動
if((HitLineAndCircle(pt[0],ball->hit) == TRUE && HitLineAndCircle(pt[1],ball->hit) == TRUE)||
(HitLineAndCircle(pt[0],ball->hit) == TRUE && HitLineAndCircle(pt[2],ball->hit) == TRUE))
{
if(ball->hit.pos.x < bar.x){
ball->dx = -4;
ball->dy = -2;
}else{
ball->dx = 4;
ball->dy = -2;
}
}else if((HitLineAndCircle(pt[3],ball->hit) == TRUE && HitLineAndCircle(pt[1],ball->hit) == TRUE)||
(HitLineAndCircle(pt[3],ball->hit) == TRUE && HitLineAndCircle(pt[2],ball->hit) == TRUE))
{
if(ball->hit.pos.x < bar.x){
ball->dx = -4;
ball->dy = 2;
}else{
ball->dx = 4;
ball->dy = 2;
}
}else{
if(HitLineAndCircle(pt[0],ball->hit) == TRUE ){
if(ball->hit.pos.x < bar.hit.lt.x + bar.hit.width/14){
ball->dx = -4;
ball->dy = -2;
}
if(ball->hit.pos.x > bar.hit.rb.x - bar.hit.width/14){
ball->dx = 4;
ball->dy = -2;
}
if(ball->hit.pos.x >= bar.hit.lt.x + bar.hit.width/14 && ball->hit.pos.x < bar.hit.lt.x + 5*bar.hit.width/14){
ball->dx = -2;
ball->dy = -5;
}
if(ball->hit.pos.x <= bar.hit.rb.x - bar.hit.width/14 && ball->hit.pos.x > bar.hit.rb.x - 5*bar.hit.width/14){
ball->dx = 2;
ball->dy = -5;
}
if(ball->hit.pos.x >= bar.hit.lt.x + 5*bar.hit.width/14 && ball->hit.pos.x < bar.hit.lt.x + 7*bar.hit.width/14){
ball->dx = -1;
ball->dy = -6;
}
if(ball->hit.pos.x <= bar.hit.rb.x - 5*bar.hit.width/14 && ball->hit.pos.x >= bar.hit.rb.x - 7*bar.hit.width/14){
ball->dx = 1;
ball->dy = -6;
}
}
if(HitLineAndCircle(pt[3],ball->hit) == TRUE){
//ball->hit.pos.y = bar.hit.rb.y + 7;
ball->dy*= -1;
}
if(HitLineAndCircle(pt[1],ball->hit) == TRUE){
ball->dx = -6;
ball->dy = -2;
}
if(HitLineAndCircle(pt[2],ball->hit) == TRUE){
ball->dx = 6;
ball->dy = -2;
}
}
if(HitLineAndCircle(pt[0],ball->hit) == TRUE ||
HitLineAndCircle(pt[1],ball->hit) == TRUE ||
HitLineAndCircle(pt[2],ball->hit) == TRUE ||
HitLineAndCircle(pt[3],ball->hit) == TRUE){
//barhitflag = TRUE;
if(ball->type == Sucker){
if(shootflag == TRUE){
bardifball.x = ball->hit.pos.x - bar.x;
bardifball.y = ball->hit.pos.y - bar.y;
}
shootflag = FALSE;
ball->flag = FALSE;
}
}
}
HitBlockandBall
コード:
void HitBlockandBall(BLOCK block[3][13][9],BALL *ball,int *blocknumber){
//ボールとブロックのあたり判定
for(int k=0;k<3;k++){
for(int i = 0; i < 13 ; i++){
for(int j = 0; j < 9; j++){
//バーの各4辺(上、左、右、下)
Line2D pt[4] ={{{block[k][i][j].hit.lt.x,block[k][i][j].hit.lt.y},{block[k][i][j].hit.lt.x + block[k][i][j].hit.width,block[k][i][j].hit.lt.y}},
{{block[k][i][j].hit.lt.x,block[k][i][j].hit.lt.y},{block[k][i][j].hit.lt.x,block[k][i][j].hit.lt.y + block[k][i][j].hit.height}},
{{block[k][i][j].hit.lt.x +block[k][i][j].hit.width,block[k][i][j].hit.lt.y},{block[k][i][j].hit.rb.x,block[k][i][j].hit.rb.y}},
{{block[k][i][j].hit.lt.x,block[k][i][j].hit.lt.y + block[k][i][j].hit.height},{block[k][i][j].hit.rb.x,block[k][i][j].hit.rb.y}}};
//ブロックがまだ消えていないときのブロックとボールの当たり判定
if(block[k][i][j].flag == 0){
//ボールの反射
if((HitLineAndCircle(pt[0],ball->hit) == TRUE && HitLineAndCircle(pt[1],ball->hit) == TRUE)||
(HitLineAndCircle(pt[0],ball->hit) == TRUE && HitLineAndCircle(pt[2],ball->hit) == TRUE))
{
//上と左or右と同時に当たった時
if(block[k][i][j].life <= 1 && ball->type == 5){
ball->dy*= -1;
}
}else if((HitLineAndCircle(pt[3],ball->hit) == TRUE && HitLineAndCircle(pt[1],ball->hit) == TRUE)||
(HitLineAndCircle(pt[3],ball->hit) == TRUE && HitLineAndCircle(pt[2],ball->hit) == TRUE))
{
//下と左or右と同時に当たった時
if(block[k][i][j].life != 1 || ball->type != 5)
ball->dx*= -1;
}else{
//上、下、左、右に当たった時
if(HitLineAndCircle(pt[0],ball->hit) == TRUE){
if(block[k][i][j].life != 1 || ball->type != 5)
ball->dx*= -1;
}
if(HitLineAndCircle(pt[3],ball->hit) == TRUE){
if(block[k][i][j].life != 1 || ball->type != 5)
ball->dy*= -1;
}
if(HitLineAndCircle(pt[1],ball->hit) == TRUE){
if(block[k][i][j].life != 1 || ball->type != 5)
ball->dx*= -1;
}
if(HitLineAndCircle(pt[2],ball->hit) == TRUE){
if(block[k][i][j].life != 1 || ball->type != 5)
ball->dx*= -1;
}
}
//ブロックにダメージ
if(HitLineAndCircle(pt[0],ball->hit) == TRUE ||
HitLineAndCircle(pt[1],ball->hit) == TRUE ||
HitLineAndCircle(pt[2],ball->hit) == TRUE ||
HitLineAndCircle(pt[3],ball->hit) == TRUE){
if(block[k][i][j].life >= 1){
if(ball->type == Needle){
if(block[k][i][j].life == 1){
block[k][i][j].life--;
}else{
block[k][i][j].life -= 2;
}
}else{
block[k][i][j].life--;
}
}
if(block[k][i][j].life == 0){
block[k][i][j].flag = 1;
*blocknumber +=1;
}
}
}
}
}
}
}
というようになります。
一応、データをアップしておきます。上がソースと実際ボールがどのような挙動をするのかという動画、下が動画のみとなります。パスワードは「YT2013」です。
http://www1.axfc.net/uploader/so/2874245
http://www1.axfc.net/uploader/so/2874249
ちなみに動画は無印が上(または下)と左右の辺が当たった時ボールの移動方向をball.dy*=-1とした場合、
2がball.dx*=-1とした場合になります。
Re: ボールとブロックの当たり判定
Posted: 2013年4月19日(金) 23:47
by ヨシタケ
すみません。よく見たら
上と左右の辺と間違えて、上の辺に当たった場合をball.dx*=-1;と変えたました。
一応直して確認したのですが、結局、無敵ブロックに当たるとはまってしまいます。
いっそのこと、角に当たった場合はif文を使って元のボールの移動方向()ball.dx,ball.dyによって反応を変えるようにしたほうがいいでしょうか。
Re: ボールとブロックの当たり判定
Posted: 2013年4月19日(金) 23:49
by softya(ソフト屋)
分割することで1ピクセル以下の単位で動く事を前提にしたブロック崩し(実際には崩さないのでブロック反射)のサンプルを作成したので御覧ください。
ブロックはわざと意地悪に配置しています。
デバッグ機能として一部の部分を擬似拡大描画しています。
それと当たりの大きさが1pixelになっていますが、これでも問題ないと思います。
このぐらいの長さのコードで動作確認した上で、本当のコードを変更するほうが合理的なので、そう言う面でも参考にしてください。
コード:
#include "DxLib.h"
// 当たり結果
typedef struct {
int x;
int y;
} hit_t;
// ブロック構造体
typedef struct {
float x;
float y;
} block_t;
// ブロックの定義
#define BLOCK_NUMS (3)
#define BLOCK_SIZEX (50)
#define BLOCK_SIZEY (20)
static block_t blockData[BLOCK_NUMS] = {
{ 1,1 },
{ BLOCK_SIZEX+2,5 },
{ BLOCK_SIZEX*2+3,0 },
};
// スクリーン
#define SCREEN_X 640
#define SCREEN_Y 480
// 壁のサイズ
#define KABE_X 171
#define KABE_Y 60
//-------------------------------------------------
// 壁当たり判定
//-------------------------------------------------
hit_t HitKabe(float x,float y)
{
hit_t hit = {0,0};//初期化
if( x<0 || x>=KABE_X ) {
hit.x = 1;
}
if( y<0 || y>=KABE_Y ) {
hit.y = 1;
}
return hit;
}
//-------------------------------------------------
// ブロック当たり判定
//-------------------------------------------------
hit_t HitBlock(float bx,float by,float vx,float vy)
{
hit_t hit = {0,0};
int ballx = (int)bx;
int bally = (int)by;
for( int b=0 ; b<BLOCK_NUMS ; b++ ) {
int posLeft = (int)blockData[b].x;
int posRight = posLeft+BLOCK_SIZEX-1;
int posTop = (int)blockData[b].y;
int posBottom = posTop+BLOCK_SIZEY-1;
// ブロック端に衝突しているか?
float deepx = 0;
float deepy = 0;
if( (posTop<=bally) && (bally<=posBottom) && (posLeft==ballx) && vx>0 ) { // 左から
hit.x = 1;
deepx = bx - (float)posLeft;
}
if( (posTop<=bally) && (bally<=posBottom) && (posRight==ballx) && vx<0 ) { // 右から
hit.x = 1;
deepx = (float)posRight - bx;
}
if( (posLeft<=ballx) && (ballx<=posRight) && (posTop==bally) && vy>0 ) { // 上から
hit.y = 1;
deepy = by - (float)posTop;
}
if( (posLeft<=ballx) && (ballx<=posRight) && (posBottom==bally) && vy<0 ) { // 下から
hit.y = 1;
deepy = (float)posBottom - by;
}
// 衝突が有った
if( hit.x || hit.y ) {
// 両方衝突?
if( hit.x == hit.y ) {
// 浅い方に当たったとみなす
if( deepx < deepy ) {
hit.x = 1;
hit.y = 0;
} else {
hit.x = 0;
hit.y = 1;
}
return hit;
} else {
return hit;
}
}
}
return hit;
}
//-------------------------------------------------
// main
//-------------------------------------------------
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) {
ChangeWindowMode( TRUE );
if( DxLib_Init() != 0 ) return 0;
SetDrawScreen( DX_SCREEN_BACK );
float ballx=31.0;
float bally=51.0;
float vx=-1.0;
float vy=-1.0;
#define LOOP_MAX 10
// ボールの軌跡記録
struct {
float x;
float y;
float vx;
float vy;
} ballRec[LOOP_MAX];
while ( ProcessMessage() == 0 && ScreenFlip() == 0 && ClearDrawScreen() == 0 ) {
hit_t hit;
// 小さく移動
for( int loop=0 ; loop<LOOP_MAX ; loop++ ) {
// 移動
ballx += vx / (float)LOOP_MAX;
bally += vy / (float)LOOP_MAX;
// ボールの軌跡
ballRec[loop].x = ballx;
ballRec[loop].y = bally;
ballRec[loop].vx = vx;
ballRec[loop].vy = vy;
// ブロック当たり判定
hit = HitBlock(ballx,bally,vx,vy);
if( hit.x==0 && hit.y==0 ) {
// 壁当たり判定
hit = HitKabe(ballx,bally);
}
// 反射
if( hit.x ) { vx = -vx; }
if( hit.y ) { vy = -vy; }
}
// ボールを描く
DrawCircle( (int)ballx, (int)bally, 3, GetColor(255,0,0) ,TRUE );
// ブロックを描く
for( int b=0 ; b<BLOCK_NUMS ; b++ ) {
int posx = (int)blockData[b].x;
int posy = (int)blockData[b].y;
DrawBox( posx, posy, posx+BLOCK_SIZEX, posy+BLOCK_SIZEY, GetColor(0,255,0) ,TRUE );
}
// 壁を描く
DrawBox( -1, -1, KABE_X, KABE_Y, GetColor(255,255,255) ,FALSE );
//--------------------------------
// debug ボールの軌跡を拡大表示
//--------------------------------
// ボールの軌跡
#define FLAME_X 200
#define FLAME_Y 200
#define FLAME_THICK 2
#define SCALE (40.0)
for( int loop=0 ; loop<LOOP_MAX ; loop++ ) {
int index = LOOP_MAX - loop - 1;
// ボールの軌跡を描く
DrawCircle( (int)(ballRec[index].x*SCALE)+FLAME_X, (int)(ballRec[index].y*SCALE)+FLAME_Y, 3, GetColor(0,255-loop,255-loop) ,TRUE );
}
// 枠
DrawBox( FLAME_X,FLAME_Y-FLAME_THICK,SCREEN_X,FLAME_Y, GetColor(255,255,255), TRUE );//X軸
DrawBox( FLAME_X-FLAME_THICK,FLAME_Y-FLAME_THICK,FLAME_X,SCREEN_Y, GetColor(255,255,255), TRUE );//Y軸
// 座標の表示
for( int loop=0 ; loop<LOOP_MAX ; loop++ ) {
DrawFormatString( FLAME_X, 0+loop*16, GetColor(255,255,255), "(%f,%f)-(%f,%f)"
, ballRec[loop].x,ballRec[loop].y
, ballRec[loop].vx,ballRec[loop].vy );
}
// ブロック
for( int b=0 ; b<BLOCK_NUMS ; b++ ) {
int posx = (int)(blockData[b].x*SCALE)+FLAME_X;
int posy = (int)(blockData[b].y*SCALE)+FLAME_Y;
DrawBox( posx, posy, posx+BLOCK_SIZEX*SCALE, posy+BLOCK_SIZEY*SCALE, GetColor(0,255,0) ,FALSE );
DrawBox( posx, posy, posx+1*SCALE, posy+1*SCALE, GetColor(0,255,0) ,FALSE );//1dot
}
}
DxLib_End();
return 0;
}
Re: ボールとブロックの当たり判定
Posted: 2013年4月20日(土) 16:26
by ISLe
ボールの向きを考慮するというのは、ボールがブロックの角に当たったとき…
水平に近い方向から当たったのなら、上や下には『当たっていない』とみなす
垂直に近い方向から当たったのなら、右や左には『当たっていない』とみなす
ということです。
それを踏まえてsoftyaさんのコードを読むと良いと思います。
Re: ボールとブロックの当たり判定
Posted: 2013年4月21日(日) 11:43
by 七篠真名
自分も過去にブロック崩しを作ってみたことがあります。
動画「ブロック崩し」の13秒~をみると接触している間はずっと横に動いているのに、離れると過去の移動方向を思い出して斜めに移動していますね。
つまり速度としては斜めの方向を維持しているのに、短い間に何度も方向転換をして横に移動しているように見えるのだと思います。
今後のことを考えるとあまり良くないかもしれませんが、とりあえずの解決策として反射後の速度を現在の速度に -1 かけたものにするのではなく、
速度の絶対値に符号をかけてあげてはどうでしょうか?
つまり、下のように書き換えてみてはどうでしょうか?
コード:
//ボールの反射
if((HitLineAndCircle(pt[0],ball->hit) == TRUE && HitLineAndCircle(pt[1],ball->hit) == TRUE)||
(HitLineAndCircle(pt[0],ball->hit) == TRUE && HitLineAndCircle(pt[2],ball->hit) == TRUE))
{
//上と左or右と同時に当たった時
if(block[k][i][j].life <= 1 && ball->type == 5)
ball->dy = -abs( ball->dy );
// ball->dy *= -1;
}else if((HitLineAndCircle(pt[3],ball->hit) == TRUE && HitLineAndCircle(pt[1],ball->hit) == TRUE)||
(HitLineAndCircle(pt[3],ball->hit) == TRUE && HitLineAndCircle(pt[2],ball->hit) == TRUE))
{
//下と左or右と同時に当たった時
if(block[k][i][j].life != 1 || ball->type != 5)
ball->dy = abs( ball->dy );
// ball->dy *= -1;
}else{
// 以下略
それから、意図的に上のコードに含ませたのですが、上と左or右と同時に当たった時だけ反射する条件式が他のものと違うみたいですね。
これはそういうつもりでやったのでしょうか?これのせいで上からボールが当たった時にだけ変な動きをしていたみたいです。
とりあえずこの2点を修正すればほぼそれっぽい動きをしていたと思います。
ここからは+αです。
まず、直線とボールの接触判定について。
直線とボールの接触判定は見た目としてはボールの中心が図1の緑の部分と青の部分のどちらかに入っていれば接触していると考えられます。
しかし、HitLineAndCircle関数では青の部分にあるときのみ TRUE を返し、緑の部分では FALSE を返しているようです。
これは想定通りの動きでしょうか?違う場合は修正した方がいいかもしれません。
それから上の方でも何度か語られていますが、角の接触後の反射について。
現状では問答無用で上の辺に接触したら上に反射して、下の辺に接触したら下に反射していると思います。
そうすると、図2のような場合長い間接触し続けてしまうことになります。
この間に何度もボールの速度を反転させると、ジグザグまたは真横にボールが移動してしまいます。(これもボールが無敵ブロックにくっつくように動いていた理由の一つ)
上のようにコードを書きなおせば反射した後の動き自体は問題なくなりますが、接触の判定はずっとしているので、
何度も音が鳴ったり、ライフが3あるブロックが一気に削れたりすると思います。(たぶん)
そして、そもそも反射する向きが明らかにおかしく感じると思います。
(現状だと、接触判定の関係で本当に同時に接触している判断されている場合はほとんどないようです)
そこで出てくるのがボールの位置や速度を考慮して反射する方向を決めようという話なのです。
自分の場合は図3のようにボールの中心と接触した角とを結んだ直線(オレンジの線)を法線とする面に接触したとして反射する様にしましたが、
ソフト屋さんの書いた浅い方に当たったとみなすプログラムの方が分かりやすくていいと思います。
これを変えれば見た目的にも良くなりますし、もしかしたら最初に書いた速度の反転方法をもとに戻してもうまくいくかもしれません。
これをさらに複数のブロックに同時に当たった時におかしな挙動を示さないように工夫しなければいけないわけですが、
これは自分もてきとうにやったので、とりあえず他の方の意見を参考にしてください。
# 慣れない作業(画像の作成)は大変だ。ソフト屋さんの画像はわかりやすくていいですね。
Re: ボールとブロックの当たり判定
Posted: 2013年4月21日(日) 13:44
by ヨシタケ
ブロックの当たり判定についてはソフト屋さんのやり方を参考にさせていただきました。
バーについての当たり判定は当たる位置によって反射する角度を変えているため一応今はそのままにしてあります。
皆様、ためになるアドバイス本当にありがとうございました。