前回の続きです。前回の円運動の実現によって、アーチ状に弾が出せるようになりました。次は自分と相手の位置関係を調べ、相手のいる方向を求めるルーチンが欲しいところです。このルーチンを使用すると、STGの基本と呼ばれる「狙い弾」が実現できます。さっそくやっていくことにしましょう。
目的は、自機の位置と敵の位置の関係から「相手のいる方向を求める」ことです。ちょっと考えてみると、「以下の図のように、ココとここの長さの比を見ることで、もしかしたら何か向きを得るヒントが得られるのではないか」ということが分かるはずです。このアプローチで攻めてゆくことにしましょう。
まずは方向0~63の縦横比の一つ一つを、テーブルにしてテキストファイルに書き出すプログラムを作成します。
#include <stdio.h> #include <math.h> #define PI (3.1415926536) int main(int argc, char* argv[]) { FILE* fp; double x, y, a; double i; fp = fopen("atandata.txt","w"); if(fp == NULL) { return -1; } for(i=0; i<64; i++) { // 算出 x = cos(((63 - i + 0.5) / 128) * PI); y = sin(((63 - i + 0.5) / 128) * PI); a = (x / y) * 256; // テキストファイルにC形式で出力 fprintf(fp, "\t, %5d\t\t/* no.%2d */\n", (short)a, (short)i); } fclose(fp); return 0; }
例によって、sin関数とcos関数で、円の動きを作っています。求まったX,Yの値を除算することで、比を求めています。方向を示すループカウンタi に、わざわざ+0.5している理由は、境目を中間に持ってゆくためです。また、結果を256倍しているのは、またしても「256:1を真の1:1と見なす」ようにして、計算を容易にするためです。
, 3 /* no. 0 */ , 9 /* no. 1 */ , 15 /* no. 2 */ , 22 /* no. 3 */ , 28 /* no. 4 */ , 34 /* no. 5 */ , 41 /* no. 6 */ , 47 /* no. 7 */ , 54 /* no. 8 */ , 60 /* no. 9 */ , 67 /* no.10 */ , 74 /* no.11 */ , 81 /* no.12 */ , 88 /* no.13 */ , 95 /* no.14 */ , 102 /* no.15 */ , 109 /* no.16 */ , 117 /* no.17 */ , 124 /* no.18 */ , 132 /* no.19 */ , 140 /* no.20 */ , 149 /* no.21 */ , 157 /* no.22 */ , 166 /* no.23 */ , 175 /* no.24 */ , 185 /* no.25 */ , 194 /* no.26 */ , 204 /* no.27 */ , 215 /* no.28 */ , 226 /* no.29 */ , 237 /* no.30 */ , 249 /* no.31 */ , 262 /* no.32 */ , 275 /* no.33 */ , 289 /* no.34 */ , 304 /* no.35 */ , 319 /* no.36 */ , 336 /* no.37 */ , 354 /* no.38 */ , 373 /* no.39 */ , 393 /* no.40 */ , 415 /* no.41 */ , 439 /* no.42 */ , 465 /* no.43 */ , 493 /* no.44 */ , 524 /* no.45 */ , 558 /* no.46 */ , 597 /* no.47 */ , 640 /* no.48 */ , 688 /* no.49 */ , 744 /* no.50 */ , 808 /* no.51 */ , 882 /* no.52 */ , 971 /* no.53 */ , 1077 /* no.54 */ , 1209 /* no.55 */ , 1374 /* no.56 */ , 1591 /* no.57 */ , 1884 /* no.58 */ , 2308 /* no.59 */ , 2972 /* no.60 */ , 4166 /* no.61 */ , 6950 /* no.62 */ , 20859 /* no.63 */
まさに「これはこういうものだ」として使ってかまわないと思います。値のどっかを少しずついじって実行すれば、それなりに値も推移することでしょう。そういう感覚で覚えたほうが、やっぱり気楽。「ゼロの発見」ではないのですが。
次に、関数の本体部分を作成します。
/*============================================================================= / 方向ゲット(256精度) /------------------------------------------------------------------------------ / Param / dx, dy = 中心からの距離(0~255) / Return / GetAngle = 0 ~ ff / 192 / 128 + 0 / 64 /============================================================================*/ IWRAM_CODE u8 GetAngle(s16 dx, s16 dy) { // XとYの長さの比を求める s16 z; if(dx == 0) { z = 32767; } else { z = (s16)((dy * 256) / dx); // 絶対値に置き換えます if(z < 0) { z = -z; } } // 64方向分サーチ u16 i; for(i=0; i<64; i++) { if(z < AtnTbl[i]) { break; } } // ブロックによって方向を調整 u8 ret; if(dx < 0) ret = 64 - i + 64; if(dy < 0) ret = 64 - i + 192; return ret; }
関数の中では、まず座標の差(dx,dy) の比を求めています。テーブル作成の際に、予め「256:1を真の1:1と見なす」ようにしているので、ここでもdx を256倍しています。それからdx とdy で除算すれば、比は求まります。 ただしdx が0の場合は、比は無限大(0による除算)になってしまうため、ここではz に32767を入れることで対処しています。比は絶対値に置き換えます。
次は、XY比テーブルをもとに、64方向分を順にサーチしてゆきます。高速化は考慮していません。比の値zをもとに、求まった結果(方向)をi にセットしています。これによって、方向0~63(右下方向、つまりはX,Y ともに+方向に移動するとき)までは大体求まります。
最後に、dx とdy それぞれの+-状態(要は自機と敵の上下左右の向き関係)によって、方向を調整してやります。方向さえ求まれば、円運動の実現で用いた方法によって、それをx,y の増分値に置き換えることも容易でしょう。
y- │ │ │ │ 128~191 │ 192~255 │ │ x- ─────────敵─────────x+ │ │ │ │ 64~127 │ 0~63 │ │ y+
今まで(dx * 256 / dy)としていたものを「dx > dyならdxとdyを交換して比を求める」というようにすることで、32~63のテーブルが必要なくなります。つまりは、dx がdy よりも大きい場合は、dxとdyの除数と被除数の関係を交換して計算を行い、方向aの結果も[64-a]という計算で出すようにします。ちなみに、dxとdyが同じ値のときには計算を省くような、特別な処理も必要になります。
IWRAM_CODE u8 GetAngle(s16 dx, s16 dy) { // 同じ位置かチェックをします if(dx==0 && dy==0) { // 直下の方向で戻します return 64; } u8 a; s16 z; // XとYの大小比較 if(dx >= dy) { // XとYの比を求めます z = ((dy*256) / dx); // 絶対値に置き換えます if(z < 0) { z = -z; } // 33方向分サーチ for(a=0; a<=32; a++) { if(z < m_Atntbl[a]) { break; } } } else { // YとXの比を求めます z = ((dx*256) / dy); // 絶対値に置き換えます if(z < 0) { z = -z; } // 33方向分サーチ for(a=64; a>=32; a--) { if(z > m_Atntbl[64-a]) { break; } } } // ブロックによって方向を調整 if(dx < 0) a = 128 - a; if(dy < 0) a = 256 - a; return a; }
サーチ回数が最大約半分になり、高速になりました。
一番初めに、dxとdyの絶対値を求めてしまいましょう。ただし、dxとdyの正負関係は重要なので、これはこのまま残します。dx,dyの絶対値は、adx,adyにセットすることにしました。adxとadyの値は必ず正になるので、「dx*256」は安心してシフト演算子「(dx<<8)」に置き換えることが出来、その分高速化を図ることが出来ます。当然ですが、zの値も0~256の正になります。
IWRAM_CODE u8 GetAngle(s16 dx, s16 dy) { u8 a; s16 z; u16 adx, ady; // 同じ位置かチェックをします if(dx==0 && dy==0) { // 直下の方向で戻します return 64; } // 絶対値を求めます u16 adx = dx; u16 ady = dy; if(dx < 0) adx = -adx; if(dy < 0) ady = -ady; // XとYの大小比較 if(adx >= ady) { // XとYの比を求めます z = ((ady<<8) / adx); // 33方向分サーチ for(a=0; a<=32; a++) { if(z < m_Atntbl[a]) { break; } } } else { // YとXの比を求めます z = ((adx<<8) / ady); // 33方向分サーチ for(a=64; a>=32; a--) { if(z > m_Atntbl[64-a]) { break; } } } // ブロックによって方向を調整 if(dx < 0) a = 128 - a; if(dy < 0) a = 256 - a; return a; }
それでも速度を遅くさせる部分が残っているように思えます。それは方向比データをサーチしてゆく部分でしょう。ならば、着眼点を変えてみて改善していきます。上記の高速化により、方向32~63のデータがむしろ不要であることが分かりました。それと、zに返ってくる比の結果は必ず0~256までになるはずです。ということは、計算手順を変えて、返ってくる比の結果(0~256)からテーブルを導き出すことで、方向をテーブルから取得することが可能にならないか、ということでここからが今回の本題です。
dxとdyがどんなケースの場合でも zは257通り(0:1~256:1で257)の結果しか返さなくなっているので、「差→方向」の変換はテーブル管理で何とかなりそうです。XY比0~256をテーブルのインデックスとし、実際の方向をテーブル内に書くようにしないといけません。前回作成したソースコードをインクルードして、テーブル作成プログラムの内部でGetAngle関数を呼びます。xの距離を予め256に設定しておき、yを0~256まで変化させながらGetAngleを呼んでゆけば、縦横比0~256から、対応する方向番号0~32が出るわけです。
・テーブル作成
#include <stdio.h> #include "gamemath.c" int main(void) { FILE *fp; unsigned short atntbl[257]; short i, x=256, y=0; // 値を算出します for(y=0; y<=256; y++) { atntbl[y] = GetAngle(x-0, y-0); } // 結果を書き出します fp = fopen("atantbl2.txt","w"); if(fp == NULL) { return -1 } for(i=0; i<=256; i++) { if((i%16) == 0) { fprintf(fp,"\t"); } if(i==0) fprintf(fp," %3d",atntbl[i], i); else fprintf(fp,",%3d",atntbl[i], i); if((i%16)==15) { fprintf(fp,"\t/* No. %3d~%3d */\n",(i/16)*16,i); } } fclose(fp); return 0; }
・生成結果
0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3 /* No. 0~ 15 */ , 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5 /* No. 16~ 31 */ , 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8 /* No. 32~ 47 */ , 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10 /* No. 48~ 63 */ , 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12 /* No. 64~ 79 */ , 12, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 15 /* No. 80~ 95 */ , 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17 /* No. 96~111 */ , 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19 /* No. 112~127 */ , 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21 /* No. 128~143 */ , 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23 /* No. 144~159 */ , 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25 /* No. 160~175 */ , 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26 /* No. 176~191 */ , 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28 /* No. 192~207 */ , 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29 /* No. 208~223 */ , 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31 /* No. 224~239 */ , 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32 /* No. 240~255 */ , 32
・使用関数
/*============================================================================= / 方向ゲット(256精度) /------------------------------------------------------------------------------ / Param / dx, dy = 中心からの距離(0~255) / Return / GetAngle = 0 ~ 255 / 192 / 128 + 0 / 64 /============================================================================*/ IWRAM_CODE u8 GetAngle(s16 dx, s16 dy) { u8 a; s16 z; u16 adx, ady; // 同じ位置かチェックをします if(dx==0 && dy==0) { // 直下の方向で戻します return 64; } // 絶対値を求めます u16 adx = dx; u16 ady = dy; if(dx < 0) adx = -adx; if(dy < 0) ady = -ady; // XとYの大小比較 if(adx >= ady) { // XとYの比を求めます z = _UDiv((ady<<8), adx); a = m_Atntbl[z]; } else { // YとXの比を求めます z = _UDiv((adx<<8), ady); a = 64 - m_Atntbl[z]; } // ブロックによって方向を調整 if(dx < 0) a = 128 - a; if(dy < 0) a = 256 - a; return a; }
サーチによる取得法からテーブルによる取得法に変えたので、気持ちの悪かった部分は解消したと思います。GBAにはCPUの割り算ができないので、_UDiv関数でBIOSを呼び出しています。