- 追加された行はこの色です。
- 削除された行はこの色です。
- Ex.02 へ行く。
#author("2023-05-18T21:58:52+09:00","","")
#author("2023-05-20T10:56:54+09:00","","")
* 相手のいる方向を求める [#q7eddc22]
前回の続きです。前回の円運動の実現によって、アーチ状に弾が出せるようになりました。次は自分と相手の位置関係を調べ、相手のいる方向を求めるルーチンが欲しいところです。このルーチンを使用すると、STGの基本と呼ばれる「狙い弾」が実現できます。さっそくやっていくことにしましょう。
** 縦横比テーブルの作成 [#k984ffb1]
目的は、自機の位置と敵の位置の関係から「相手のいる方向を求める」ことです。ちょっと考えてみると、「以下の図のように、ココとここの長さの比を見ることで、もしかしたら何か向きを得るヒントが得られるのではないか」ということが分かるはずです。このアプローチで攻めてゆくことにしましょう。
#ref(1.png,nolink)
まずは方向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と見なす」ようにして、計算を容易にするためです。
- atandata.txt
, 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 */
まさに「これはこういうものだ」として使ってかまわないと思います。値のどっかを少しずついじって実行すれば、それなりに値も推移することでしょう。そういう感覚で覚えたほうが、やっぱり気楽。「ゼロの発見」ではないのですが。
** 方向を求める関数の作成 [#i05b9e7a]
次に、関数の本体部分を作成します。
/*=============================================================================
/ 方向ゲット(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;
}
*** XとYの長さの比を求める [#i0c2d0e8]
関数の中では、まず座標の差(dx,dy) の比を求めています。テーブル作成の際に、予め「256:1を真の1:1と見なす」ようにしているので、ここでもdx を256倍しています。それからdx とdy で除算すれば、比は求まります。 ただしdx が0の場合は、比は無限大(0による除算)になってしまうため、ここではz に32767を入れることで対処しています。比は絶対値に置き換えます。
*** 64方向分サーチ [#bf87406f]
次は、XY比テーブルをもとに、64方向分を順にサーチしてゆきます。高速化は考慮していません。比の値zをもとに、求まった結果(方向)をi にセットしています。これによって、方向0~63(右下方向、つまりはX,Y ともに+方向に移動するとき)までは大体求まります。
*** ブロックによって方向を調整 [#h4eaa08e]
最後に、dx とdy それぞれの+-状態(要は自機と敵の上下左右の向き関係)によって、方向を調整してやります。方向さえ求まれば、円運動の実現で用いた方法によって、それをx,y の増分値に置き換えることも容易でしょう。
- 方向を示す値の対応表
y-
│
│
│
│
128~191 │ 192~255
│
│
x- ─────────敵─────────x+
│
│
│
│
64~127 │ 0~63
│
│
y+
- 関数の使用法
事前に目標との差を求め、それを引数dx,dyに入れるようにします。
-- (自機x - 敵x) → dx
-- (自機y - 敵y) → dy
-- 戻り値に、方向を示す値が返ってきます。
** ルーチンの改良 [#z93a93fe]
*** 高速化その1 サーチ回数の節約 [#yb25f68d]
今まで(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;
}
サーチ回数が最大約半分になり、高速になりました。
*** 高速化その2 絶対値に着目 [#k45b0f0f]
一番初めに、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;
}
*** 高速化その3 方向比データの改善 [#n53557e2]
それでも速度を遅くさせる部分が残っているように思えます。それは方向比データをサーチしてゆく部分でしょう。ならば、着眼点を変えてみて改善していきます。上記の高速化により、方向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を呼び出しています。
** 出典元 [#cb50d9db]
- M-KAIさんの「Witchシューティングゲーム制作記帳」
** 履歴 [#pf59abfb]
- 2023/05/18
- 2008/07/02