前回の続きです。前回の円運動の実現によって、アーチ状に弾が出せるようになりました。次は自分と相手の位置関係を調べ、相手のいる方向を求めるルーチンが欲しいところです。このルーチンを使用すると、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には割り算ができないので、_UDiv関数でBIOSを呼び出しています。