◆PROCESSING 逆引きリファレンス
カテゴリー:ゲーム作成
追尾ミサイルを打つには
【解説】
打った弾が敵を追いかけて撃墜する。シューティングゲームでは良くみかけるシーンですよね?。自動追尾(ホーミング)ミサイルというやつです。
あれはどういった仕組みで実現しているのでしょうか?。
いろいろなやり方があるかとは思いますが、1つの方法として以下のようなものがあります。
放ったミサイルの位置が移動する毎に、その時のミサイルと敵機を結ぶ直線を計算し、その直線上の点に向けてミサイルを移動する方法です。
(画像URL:deviantart.com 様、illust-AC 様:kaeru-yaさん)
ミサイルと敵機を結ぶ直線上の点を計算するだけなら lerp() 命令で簡単に実現可能です。難しいのは、ミサイルを常に一定の速度で飛ばす事です。
またミサイルが敵を貫通して追い越してしまい、「当たった」と判定されない事にも注意が必要です。
(画像URL:deviantart.com 様、illust-AC 様:kaeru-yaさん)
点uはUFOの座標、点pは1つ前のミサイル座標、点bが現在のミサイル座標です。追尾1から追尾2の状態に遷移するとして、ミサイルの速度(n)は一定である必要があります。
1つ前のミサイル座標(p)と現在のミサイル座標(b)の距離(n)が、1つ前のミサイル座標(p)とUFO座標(u)の距離(t)を追い越していたら、弾が貫通してしまったので「当たり」と判定します。
この処理を実現するための大まかな流れは、以下のようになります。
- 点u と点p の距離(t)を計算
- 点u と点p の角度(θ)を計算
- nを元に内積・外積で点b の座標を計算
- n < t なら当たっていないと判断
- n > t なら点u = 点b とし、当たりと判断
- 自機、敵機、ミサイル描画
- 当たりならUFO爆発エフェクト描画
2点間の距離(1の処理)は dist() 命令で簡単に計算できます。
ポイントとなるのは、角度を求める処理(2の処理)と、点bの座標を計算する処理(3の処理)ですよね。
下図をみてください。
(画像URL:deviantart.com 様、illust-AC 様:kaeru-yaさん)
ある2点(pとu)の角度(θ)は、atan2() 命令で求めることが可能です(左側の図)。
また角度(θ)がわかれば、距離( n =ミサイルの速度)をもとに内積と外積の計算を使って、現在のミサイル座標(=点b)を計算することが可能です。
数学が苦手な私は(汗)、内積と外積について上手に説明することができませんが、「円と線分の当たり判定を行うには」の記事が参考になるかもしれません。よろしければ参照してみて下さい。
【構文】
float rad= atan2( float x, float y ) ;
【パラメータ】
rad : 角度(ラジアン)
x : 直角三角形の底辺の長さ
y : 直角三角形の高さ
【注意】
atan2() はタンジェントの逆関数です。
直角三角形の高さと底辺の長さから、2辺を結ぶ斜辺の角度(θ)を求めます。
求まる角度は度(°)ではなくラジアンです。またPROCESSINGは「時計回り」の角度を基準とします。私達が数学でよく使う「反時計回り」の角度ではないことに注意してください。
【関連記事】
サンプルプログラム
角度を計算する例:
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 |
PImage ufo, plane; //各画像 int ufox, ufoy; //UFO座標 int plnx, plny; //戦闘機座標 void setup(){ size(400,400); //画像読み込み imageMode( CENTER ); ufo = loadImage( "ufo.png" ); plane= loadImage( "plane.png" ); //表示位置指定 //戦闘機は画面中央 plnx = width/2; plny = height/2; //UFOは右斜め上 ufox = width/4 * 3; ufoy = height/4; frameRate( 60 ); } void draw(){ background( 0 ); //画面中央に十字を描く strokeWeight( 2 ); stroke( color(255,100,100)); line( width/2, 0, width/2,height ); line( 0, height/2, width, height/2 ); //戦闘機とUFOを表示 image( plane, plnx, plny ); image( ufo, ufox, ufoy ); //戦闘機とUFOを線で結ぶ stroke( color(100,100,255)); line( plnx, plny, ufox, ufoy ); //戦闘機とUFOの角度を計算する float rad = atan2( ufoy - plny, ufox - plnx ); fill(255); text( "ラジアン:" + rad + " (" + degrees( rad ) + ")" , 0, 16 ); } void mouseDragged(){ //UFOがドラッグされていたらUFOの位置を変更する if( ufox - ufo.width/2 < mouseX && ufox + ufo.width/2 > mouseX && ufoy - ufo.height/2 < mouseY && ufoy + ufo.height/2 > mouseY){ ufox = mouseX; ufoy = mouseY; } } |
画面中央に戦闘機、戦闘機の斜め上にUFOを描きます。
その後、戦闘機とUFOが作る角度を計算します。
内部で atan2() を利用して角度を計算しています。UFOをマウスで移動させると、角度の表示がかわる事がわかると思います。
<出力サンプル>
(画像URL:deviantart.com 様、illust-AC 様:kaeru-yaさん)
追尾ミサイルを打つ例:
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 |
PImage ufo, plane, ball, back; //各画像 int ufox, ufoy; //UFO座標 int nxufox, nxufoy; //次のUFO座標 int plnx, plny; //戦闘機座標 int balx, baly; //弾座標 int bfbalx, bfbaly; //1つ前の弾座標 int balSpeed = 16; //弾速度 int ufoSpeed = 3; //ufo速度 int gameTime = 0; //ゲーム時間 boolean isShoot; //弾発射中FLG boolean isHit; //命中FLG void setup(){ size(400,400); //画像読み込み imageMode( CENTER ); back = loadImage( "cosmos.png" ); ufo = loadImage( "ufo.png" ); plane= loadImage( "plane.png" ); ball = loadImage( "ballet.png" ); //表示位置指定 //戦闘機と弾の位置は画面下中央 plnx = width/2; plny = height - 48; //弾とUFOの座標設定 initLocation(); isShoot = false; //弾は未発射 isHit = false; //まだ命中していない frameRate( 60 ); } //弾とUFOの座標初期化処理 void initLocation(){ //弾は戦闘機と同じ位置 balx = plnx; baly = plny; bfbalx = balx; bfbaly = baly; //UFO位置はランダム ufox = (int)random( 0 + ufo.width, width - ufo.width ); ufoy = (int)random( 0 + ufo.height, height - 180 ); //UFOの次の位置(移動先)も設定 nxufox = (int)random( 0 + ufo.width, width - ufo.width ); nxufoy = (int)random( 0 + ufo.height, height - 180 ); } //UFO移動処理 void ufoMove( ){ //移動する if( nxufox < ufox ){ ufox = ufox - ufoSpeed; } else { ufox = ufox + ufoSpeed; } if( nxufoy < ufoy ){ ufoy = ufoy - ufoSpeed; } else { ufoy = ufoy + ufoSpeed; } } void draw(){ background( back ); gameTime++; //ゲーム時間加算 if( isShoot ){ //弾発射中なら //UFOと弾の距離を計算する float t = dist( ufox, ufoy, bfbalx, bfbaly ); //弾とUFOの角度を計算する float rad = atan2( ufoy - bfbaly ,ufox - bfbalx ); //次の弾座標を計算する int x = (int)(cos( rad ) * balSpeed + bfbalx); int y = (int)(sin( rad ) * balSpeed + bfbaly); if( balSpeed < t ){ //また当たりじゃないので、弾の位置を更新 bfbalx = balx; bfbaly = baly; balx = x; baly = y; } else { //当たり isHit = true; balx = ufox; baly = ufoy; } //弾を表示 image( ball, balx, baly ); } //キャラ表示 image( plane, plnx, plny ); if( isHit == false ){ //当たりじゃないので、UFO表示 image( ufo, ufox, ufoy ); } else { //当たり isShoot = false; //弾を非表示=未発射に戻す //2.5秒後にUFOを再度出現させる if( gameTime % 150 == 0 ){ isHit = false; gameTime = 0; //弾、UFO、次のUFO座標を初期化する initLocation(); } } //UFOを移動する //現在のUFO位置と次のUFO位置の距離を計算する float m = dist( ufox, ufoy, nxufox, nxufoy ); if( ufoSpeed < m ){ //次のUFO座標に到達していないので移動 ufoMove(); } else { //目標位置にUFOが到達したので、次の目標位置を再設定 ufox = nxufox; ufoy = nxufoy; nxufox = (int)random( 0 + ufo.width, width - ufo.width ); nxufoy = (int)random( 0 + ufo.height, height - 180 ); } } void mouseClicked(){ //未命中で未発射なら、弾を発射する if( isHit == false && isShoot == false ){ isShoot = true; } } |
角度を計算するサンプルをベースに、少しだけ処理を加えました。
内部で atan2() を利用して角度を計算しています。また内積・外積を利用して弾の座標を計算しています。
実行すると、画面上にフラフラと動く怪しいUFOが出現します。マウスをクリックすると戦闘機から弾が発射されます。
弾は自動追尾ミサイルですので、百発百中となります(笑)。
<出力サンプル>
(画像URL:deviantart.com 様、illust-AC 様:kaeru-yaさん、りなまる さん)
下記はサンプルプログラムをP5.jsで書き直したものです。動作イメージを確認できます。
本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。