相手のいる方向を求める

前回の続きです。前回の円運動の実現によって、アーチ状に弾が出せるようになりました。次は自分と相手の位置関係を調べ、相手のいる方向を求めるルーチンが欲しいところです。このルーチンを使用すると、STGの基本と呼ばれる「狙い弾」が実現できます。さっそくやっていくことにしましょう。

縦横比テーブルの作成

目的は、自機の位置と敵の位置の関係から「相手のいる方向を求める」ことです。ちょっと考えてみると、「以下の図のように、ココとここの長さの比を見ることで、もしかしたら何か向きを得るヒントが得られるのではないか」ということが分かるはずです。このアプローチで攻めてゆくことにしましょう。

1.png

まずは方向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と見なす」ようにして、計算を容易にするためです。

まさに「これはこういうものだ」として使ってかまわないと思います。値のどっかを少しずついじって実行すれば、それなりに値も推移することでしょう。そういう感覚で覚えたほうが、やっぱり気楽。「ゼロの発見」ではないのですが。

方向を求める関数の作成

次に、関数の本体部分を作成します。

/*=============================================================================
/    方向ゲット(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の長さの比を求める

関数の中では、まず座標の差(dx,dy) の比を求めています。テーブル作成の際に、予め「256:1を真の1:1と見なす」ようにしているので、ここでもdx を256倍しています。それからdx とdy で除算すれば、比は求まります。 ただしdx が0の場合は、比は無限大(0による除算)になってしまうため、ここではz に32767を入れることで対処しています。比は絶対値に置き換えます。

64方向分サーチ

次は、XY比テーブルをもとに、64方向分を順にサーチしてゆきます。高速化は考慮していません。比の値zをもとに、求まった結果(方向)をi にセットしています。これによって、方向0~63(右下方向、つまりはX,Y ともに+方向に移動するとき)までは大体求まります。

ブロックによって方向を調整

最後に、dx とdy それぞれの+-状態(要は自機と敵の上下左右の向き関係)によって、方向を調整してやります。方向さえ求まれば、円運動の実現で用いた方法によって、それをx,y の増分値に置き換えることも容易でしょう。

ルーチンの改良

高速化その1 サーチ回数の節約

今まで(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 絶対値に着目

一番初めに、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 方向比データの改善

それでも速度を遅くさせる部分が残っているように思えます。それは方向比データをサーチしてゆく部分でしょう。ならば、着眼点を変えてみて改善していきます。上記の高速化により、方向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を呼び出しています。

出典元

履歴


トップ   一覧 検索 最終更新   ヘルプ   最終更新のRSS