◆PROCESSING 逆引きリファレンス
カテゴリー:ゲーム作成
円同士の当たり判定を行うには
【解説】
多くのゲームでは、物と物が衝突したかどうかを判定する処理(当たり判定)は定番ですね。
例えばシューティングゲームでは、敵と弾が衝突したかどうかを判定し、しかるべき処理を行います。
ゲーム専用のフレームワーク(UnityやCocos2d-X)では、専用の当たり判定命令が用意されています。
残念ながらPROCESSINGには、当たり判定を行う便利な標準命令はありません。
無いものは作るしかないというわけで(笑)、ここでは画面上に登場するキャラクターを囲む円を用いて、円同士の当たり判定を行う方法について解説したいと思います。
キャラクターを囲む円とは、以下の様なものです。
(画像URL:illust-AC 様:うーさん)
青い円が「キャラクターを囲む円」です。そして、円同士の衝突は以下のようなイメージになります。
(画像URL:illust-AC 様:うーさん、kaeru-yaさん)
この場合、UFOと砲弾を囲む円が衝突しているか(触れているかどうか)を判定します。円同士が触れているかどうかは、中学校で習う三平方の定理を用いれば計算可能です。
上記式で求められる結果が、円Aと円Bの半径同士を足した長さ(距離)よりも小さければ、衝突している事になります。
【構文】
float s = sqrt ( float n ) ;
float t = sq ( float f ) ;
【パラメータ】
s : 平方根の結果
n : 平方根の対象数
t : 二乗した結果
f : 二乗する対象数
【注意】
sqrt() は平方根を求める命令です。sqrt( 2 ) なら 1.4142135 が戻されます。
sq( ) は与えられた数を二乗する命令です。 sq( 3 ) は 3 × 3 と同じ意味になります。
【関連記事】
サンプルプログラム
当たり判定例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
/****************************** ボール管理クラス *******************************/ class ball{ PImage img; //画像 int pointX; //位置(X) int pointY; //位置(Y) int speed; //移動速度 int collisionR; //衝突判定円の半径 /* コンストラクタ */ ball( int v, String s ){ //画像を読み込む img = loadImage(s); if( v == LEFT ){ //左側へ配置 pointX = 0; speed = 4; } else { //右側へ配置 pointX = width - img.width; speed = -4; } //上下は中央配置 pointY = (height - img.height)/2; //衝突判定半径は、画像に外接する四角の半分の大きさ collisionR = img.width/2; } /* 移動処理 */ void moveBall(){ pointX = pointX + speed; //壁に当たったら反転する if( pointX < 0 || pointX > (width - img.width) ){ moveReverse(); } } /* 反転処理 */ void moveReverse(){ speed = speed * -1; } /* 表示処理 */ void dispBall(){ moveBall(); image( img, pointX, pointY ); } } //グローバル変数 ball myBall[]; /* 初期化処理 */ void setup(){ size(500,500); //ボールを作成する myBall = new ball[2]; myBall[0] = new ball( LEFT, "basketball.png" ); myBall[1] = new ball( RIGHT, "soccerball.png" ); frameRate(60); } /* 描画処理 */ void draw(){ background(0); //ボールを表示する myBall[0].dispBall(); myBall[1].dispBall(); //衝突判定 if( isCollisionCircle( myBall[0], myBall[1] )){ //2つのボールが衝突したので反転する myBall[0].moveReverse(); myBall[1].moveReverse(); } } /****************************** 衝突判定関数 ------------------------------ en1, en2 : 判定する2つの円 戻り値 : true 衝突あり false 衝突なし *******************************/ boolean isCollisionCircle( ball en1, ball en2 ){ float dx,dy,dr; dx = abs(en1.pointX - en2.pointX); //水平方向の距離 dy = abs(en1.pointY - en2.pointY); //垂直方向の距離 dr = en1.collisionR + en2.collisionR; //半径の和 //三平方の定理 return ( sqrt( sq(dx) + sq(dy) ) < dr );//当たっていたらtrue } |
上記を実行すると、バスケットボールとサッカーボールが画面中央で衝突して跳ね返ります。
LEFTとRIGHTはPROCESSINGが標準で持っているグローバル定数で、左か右かを判別するのに利用しています。
記事で紹介した衝突判定の計算を行っているのは isCollisionCircle() 関数になります。sqrt( sq(dx) + sq(dy) ) と書かれた部分が今回のポイントとなる計算式です。
上記サンプルは data フォルダ配下に
- basketball.png:バスケットボール画像(64×64)
- soccerball.png:サッカーボール画像 (64×64)
が格納されている前提です。
また当たり判定部分については、双流蒼天歌 様のブログを参照させて頂きました。
<出力サンプル>
(画像URL:pixabay 様)
当たり判定例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
//グローバル変数 myChara ufo; myChara ballet; myChara cannon; //定数 final int GTOP = 0; //上端 final int GLEFT = 1; //左端 final int GCENTER = 2; //横中央 final int GBOTTOM = 3; //下端 final int GCBOTTOM= 4; //下端2 /****************************** キャラクター管理クラス *******************************/ class myChara{ PImage img; //画像 float initX; //初期位置(X) float initY; //初期位置(Y) boolean isAlive; //有効無効FLG float pointX; //現在位置(X) float pointY; //現在位置(Y) float collisionR; //衝突判定円の半径 float moveSpeed; //移動速度 int myTimer; //経過時間 /*--------------------------- コンストラクタ --------------------------- s:画像ファイル x:横位置 y:縦位置 v:速度 ---------------------------*/ myChara( String s, int x, int y, float v ){ img = loadImage( s ); //画像読み込み initX = calcLocation( x );//初期位置を計算 initY = calcLocation( y );//〃 collisionR = img.width/2; //衝突判定半径を初期化 moveSpeed = v; //移動速度設定 setAlive( false ); //無効設定 initPoint(); //現在位置を初期化 } /* 位置計算 */ int calcLocation( int s ){ int d = 0; switch( s ){ case GTOP: d = 0; //上端 break; case GLEFT: d = 0; //左端 break; case GCENTER: d = (width - img.width)/2; //横中央 break; case GBOTTOM: d = height - img.height; //下端 break; case GCBOTTOM: d = height - img.height*2; //下端2 break; } return( d ); } /* 現在位置を初期化するメソッド */ void initPoint(){ myTimner = 0; //経過時間を初期化 pointX = initX; //現在位置を初期位置に戻す pointY = initY; //〃 } /* 移動メソッド(横移動)*/ void movePointX(){ if( isAlive == false ){ //もしUFOが無効なら、1/2s後に出現させる //ゲームタイマーを加算(1/60msで+1される) myTimer++; if( myTimer > 30 ){ //30ms = 1/2秒経過したらUFO出現 setAlive( true ); myTimer = 0; } } else { //UFOが有効なので移動する pointX = pointX + moveSpeed; dispImg(); if (pointX > (width - img.width)) { //右端にきたら、座標初期化 initPoint(); } } } /* 移動メソッド(縦移動)*/ void movePointY(){ if( isAlive == false ) return; pointY = pointY - moveSpeed; dispImg(); if( pointY < 0 ) { //上端にきたら消滅させる allInit(); } } /* 単純描画 */ void dispImg(){ image( img, pointX, pointY ); } /* 全部初期化 */ void allInit(){ initPoint(); setAlive( false ); } /* 有効無効 */ void setAlive( boolean b ){ if( b == isAlive ) return; isAlive = b; } } /****************************** 衝突判定関数 ------------------------------ en1, en2 : 判定する2つの円 戻り値 : true 衝突あり false 衝突なし *******************************/ boolean isCollisionCircle( myChara en1, myChara en2 ){ float dx,dy,dr; dx = abs(en1.pointX - en2.pointX); //水平方向の距離 dy = abs(en1.pointY - en2.pointY); //垂直方向の距離 dr = en1.collisionR + en2.collisionR; //半径の和 //三平方の定理 return ( sqrt( sq(dx) + sq(dy) ) < dr );//当たっていたらtrue } //初期処理関数 public void setup() { size( 500, 500 ); //画像読み込み ufo = new myChara("ufo.png", GTOP, GLEFT, 4 ); ballet = new myChara("ballet.png", GCENTER, GCBOTTOM, 8 ); cannon = new myChara("cannon.png", GCENTER, GBOTTOM, 0 ); frameRate(60); } //描画処理関数 public void draw() { background(0); //砲台を描画 cannon.dispImg(); //UFOを移動する ufo.movePointX(); //弾を飛ばす ballet.movePointY(); if( isCollisionCircle( ufo, ballet )){ //あたりなので、弾とUFOを初期化 ufo.allInit(); ballet.allInit(); } } public void keyPressed() { //弾を発射する ballet.setAlive( true ); } |
ちょっとしたミニミニゲームですね(笑)。
上記を実行すると画面下中央に固定砲台が表示されます。左上から現れるUFOめがけて弾を発射してください。弾は何かのキーを押すと発射されます。
isCollisionCircle() が今回紹介した円同士の衝突判定を行っている関数です。弾がUFOに命中すると、UFOと弾が消失します。
上記サンプルは data フォルダ配下に
- ufo.png : UFO画像(64×64)
- cannon.png :砲台画像(64×64)
- ballet.png :砲弾画像(48×48)
が格納されている前提です。
また当たり判定部分については、双流蒼天歌 様のブログを参照させて頂きました。
<出力サンプル>
(画像URL:illust-AC 様:うーさん、kaeru-yaさん、acworks さん)
下記はサンプルプログラムをP5.jsで書き直したものです。マウスクリックすると動作イメージを確認できます。
本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。