#author("2023-04-17T22:48:29+09:00","","")
* 固定小数点と構造体ワークエリア [#q2e864fe]
アクション系ゲームのキャラクタ移動など、1ドット単位よりも細かな座標指定を擬似的に扱うにはどうすればよいのでしょうか。ストレートに考えれば、小数点を変数に持たせることが必要になると思います。そこでfloat型を使用して・・・となりますが、ちょっと待ってください。浮動小数点の取扱いはCPUの処理に時間が掛かり、整数計算の数十倍は時間が掛かるといわれています。実は整数型を使った良い方法があるのです。方法は単純で''16を1ドット分とみなす''ようにするだけです。この決まり事を事前にプログラム上の約束にすることで「座標 % 16」という結果の「0~16までの値」を小数として利用できます。
#author("2023-04-18T08:55:53+09:00","","")
* 固定小数点 [#q2e864fe]
アクション系ゲームのキャラクタ移動など、1ドット単位よりも細かな座標指定を擬似的に扱うにはどうすればよいのでしょうか。ストレートに考えれば、小数点を変数に持たせることが必要になると思います。そこでfloat型を使用して・・・となりますが、ちょっと待ってください。浮動小数点の取扱いはCPUの処理に時間が掛かり、整数計算の数十倍は時間が掛かるといわれています。実は整数型を使った良い方法があるのです。方法は単純で''16を1ドット分とみなす''ようにするだけです。この決まり事を事前にプログラム上の約束にすることで「座標 % 16」という結果の「0~15までの値」を小数として利用できます。このことは16進数でいう4bit分、つまりs16だと下位4ビット=小数点、上位12ビット=実際の表示ピクセル座標という扱いになります。

|10進数|0|RIGHT:1|RIGHT:2|RIGHT:3|RIGHT:4|RIGHT:5|RIGHT:6|RIGHT:7|RIGHT:8|RIGHT:9|RIGHT:10|RIGHT:11|RIGHT:12|RIGHT:13|RIGHT:14|RIGHT:15|RIGHT:16|RIGHT:17|RIGHT:18|
|16進数|0|0x1|0x2|0x3|0x4|0x5|0x6|0x7|0x8|0x9|0xA|0xB|0xC|0xD|0xE|0xF|0x10|0x11|0x12|
|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|RIGHT:|c
|10進数|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|COLOR(red):16|17|18|省略・・・|
|16進数|0x0|0x1|0x2|0x3|0x4|0x5|0x6|0x7|0x8|0x9|0xA|0xB|0xC|0xD|0xE|0xF|COLOR(red):0x10|0x11|0x12||
| 2進数|0|1|10|11|100|101|110|111|1000|1001|1010|1011|1100|1101|1110|1111|COLOR(red):10000|10001|10010||

内訳としては、
-上位12ビット = 実際の表示ピクセル座標~
-下位 4ビット = 小数点~
という扱いになります。


 typedef struct {
 	u16 x;
 	u16 y;
 } ST_WORD;
 s16 x, y;
 
 typedef struct {
 	u8 xl;
 	u8 xh;
 	u8 yl;
 	u8 yh;
 } ST_BYTE;
 // 座標の代入
 x = ((GBAの横サイズ240 / 2) - (キャラサイズ / 2)) << 4;
 y = ((GBAの縦サイズ160 / 2) - (キャラサイズ / 2)) << 4;
 
 typedef union {
 	ST_WORD w;
 	ST_BYTE b;
 } ST_CHR_POS;

unionを使用していることで、たとえばST_WORD にx=16384、y=32768 が格納されていたとします。次にST_BYTE のxh, yh(上位ビット) を見ることで、表示座標の64、128を求めることが可能になります。

 	ST_CHR_POS chr;
 // 座標の加算
 x = x + 1;
 
 	chr.w.x = 16384;
 	chr.w.y = 32768;
 
 	DispChr(chr.b.xh, chr.b.yh);
 // 座標の表示
 SpriteDraw(x >> 4, y >> 4);


1/256精度もあれば、大抵は実用に耐えると思います。ところが精度は半分でもいいから、画面外処理のために移動範囲を余裕に持ちたい、ということなら「128を1とみなす」ようにしても良いでしょう。さらにST_WORDやunionを使用せずu16だけを使い、表示座標がほしいときだけ「座標>>7」に変換するだけでもかまわないと思います。

 IWRAM_CODE u8 ChrGetTransPos(u16 src)
** サンプルプログラム [#t5219b78]
 int main(void)
 {
 	return src >> 7;
 }


GBAならイチオシの使い方なので、よかったら使用してみてください。ただし最近のCPUなどでは、double型を直接使用したほうが有益とされています。何故有益かというと、整数と小数の速度の差異自体、もはや気にするレベルでは無いですし、こっちの方が正確だし、組んでていちいち256を意識する必要が無いからです。


** 出典元 [#u576450d]
- M-KAIさんの「Witchシューティングゲーム制作記帳」


** 履歴 [#k0edec0a]
- 2008/06/30


* Tips.1-4 構造体ワークエリアのススメ [#zbec0e23]
** 静的変数に多種多彩な要望を [#h782e407]
シューティングゲーム製作時、敵キャラクタごとにイベント用変数を用意したものの個々に複雑化してしまい、管理が面倒になってしまった経験はないでしょうか。ここでは「多種多様な敵キャラを、1つのワークエリアに割り当てる方法」を紹介します。コードを読んでみることが理解の近道のようなので、さっそく見ていきましょう。


-1.まず、敵キャラクタの構造体と実体を用意します。
 typedef struct {
 	u16 id;			// オブジェクトID
 	u16 work[12];		// 汎用ワークエリア
 } ST_ENEMY;
 	REG_WSCNT = 0x4317;
 
 ST_ENEMY Enemy[128];
至って普通です。コメントしようがありません(^^;。


-2.次に、個別の敵キャラ構造体宣言をします。
 // 敵1(タコさん)
 typedef struct {
 	u16 x;
 	u16 y;
 	u16 vx;			// 移動増加値
 	u16 vy;
 	u16 eventCnt;
 } ST_ENEMY_TAKO;
 	BgInit();
 	IrqInit();
 	KeyInit();
 
 	SprInit();
 	SpriteSetAlpha();
 
 // 敵2(イカさん)
 typedef struct {
  	u16 x;
  	u16 y;
  	u16 vx;			// 移動増加値
  	u16 vy;
  	u16 eventCnt;
  	u16 circleCnt;
 } ST_ENEMY_IKA;
 
 	char strX[32], strY[32], strM[32];
 	u16 cnt;
 
 // 敵3(サメさん)
  typedef struct {
  	u16 x;
  	u16 y;
  	u16 vx;			// 移動増加値
  	u16 vy;
  	u16 eventCnt;
  	u16 baseX;		// 一定時間経過したら、自機キャラの位置を格納
 	u16 baseY;
 } ST_ENEMY_SAME;
ここは宣言だけで、実体は作成しません。実体のない入れ物をどうするかは、次の説明で明らかになります。


-3.実体のない構造体を、ワークエリアにマッピングします。
 IWRAM_CODE EnemyExecIka(u16 no)
 {
 	ST_ENEMY_IKA* w = (ST_ENEMY_IKA*)&Enemy[no].work;
 	s16 x = ((240 / 2) - (8 / 2)) << 4;
 	s16 y = ((160 / 2) - (8 / 2)) << 4;
 	s16 m = 25;
 
 	//
 }
 	for(;;)
 	{
 	    VBlankIntrWait();
 
 		KeyExec();
 		cnt = KeyGetCnt();
 
 IWRAM_CODE EnemyExecSame(u16 no)
 {
 	ST_ENEMY_SAME* w = (ST_ENEMY_IKA*)&Enemy[no].work;
 		if(cnt & KEY_UP   ) y-=m;
 		if(cnt & KEY_DOWN ) y+=m;
 		if(cnt & KEY_LEFT ) x-=m;
 		if(cnt & KEY_RIGHT) x+=m;
 		if(cnt & KEY_R    ) m+=1;
 		if(cnt & KEY_L    ) m-=1;
 
 	//
 		if((x>>4) <      0) x = 0;
 		if((x>>4) >= 240-8) x = (240-8) << 4;
 		if((y>>4) <      0) y = 0;
 		if((y>>4) >= 160-8) y = (160-8) << 4;
 
 		if(m <=  0) m =  1;
 		if(m >= 50) m = 50;
 
 		SpriteMoveAlpha(x>>4, y>>4);
 
 		_Sprintf(strX, "x:%3d %4d 0x%04x", x>>4, x, x);
 		_Sprintf(strY, "y:%3d %4d 0x%04x", y>>4, y, y);
 		_Sprintf(strM, "m:%3d", m);
 
 		BgAsciiDrawStr(0, 17, strX);
 		BgAsciiDrawStr(0, 18, strY);
 		BgAsciiDrawStr(0, 19, strM);
 	}
 }
それぞれの処理関数の先頭で割り当てて、つまりマッピングしてしまえばいいのです。w->hogeと操作すれば、変数にアクセスすることが可能になります。めでたしめでたし(^^;。個人的にですが、このテクニックを始めてみたときは偉く感動しました。みなさんはいかがだったでしょうか。

** 動作画面 [#t9a62457]
-[[github:https://github.com/akkera102/gbadev-ja/tree/main/doc18%20%E5%9B%BA%E5%AE%9A%E5%B0%8F%E6%95%B0%E7%82%B9]]
#ref(1.png,nolink)

** 出典元 [#pb4ac969]
- M-KAIさんの「Witchシューティングゲーム制作記帳」


** 履歴 [#j0abcb62]
- 2023/04/18
- 2008/06/30


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