#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

トップ   差分 履歴 リロード   一覧 検索 最終更新   ヘルプ   最終更新のRSS