アクション系ゲームのキャラクタ移動など、1ドット単位よりも細かな座標指定を擬似的に扱うにはどうすればよいのでしょうか。ストレートに考えれば、小数点を変数に持たせることが必要になると思います。そこでfloat型を使用して・・・となりますが、ちょっと待ってください。浮動小数点の取扱いはCPUの処理に時間が掛かり、整数計算の数十倍は時間が掛かるといわれています。実は整数型を使った良い方法があるのです。方法は単純で16を1ドット分とみなすようにするだけです。この決まり事を事前にプログラム上の約束にすることで「座標 % 16」という結果の「0~16までの値」を小数として利用できます。
10進数 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
16進数 | 0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xA | 0xB | 0xC | 0xD | 0xE | 0xF | 0x10 | 0x11 | 0x12 |
内訳としては、
typedef struct { u16 x; u16 y; } ST_WORD; typedef struct { u8 xl; u8 xh; u8 yl; u8 yh; } ST_BYTE; 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; chr.w.x = 16384; chr.w.y = 32768; DispChr(chr.b.xh, chr.b.yh);
1/256精度もあれば、大抵は実用に耐えると思います。ところが精度は半分でもいいから、画面外処理のために移動範囲を余裕に持ちたい、ということなら「128を1とみなす」ようにしても良いでしょう。さらにST_WORDやunionを使用せずu16だけを使い、表示座標がほしいときだけ「座標>>7」に変換するだけでもかまわないと思います。
IWRAM_CODE u8 ChrGetTransPos(u16 src) { return src >> 7; }
GBAならイチオシの使い方なので、よかったら使用してみてください。ただし最近のCPUなどでは、double型を直接使用したほうが有益とされています。何故有益かというと、整数と小数の速度の差異自体、もはや気にするレベルでは無いですし、こっちの方が正確だし、組んでていちいち256を意識する必要が無いからです。
シューティングゲーム製作時、敵キャラクタごとにイベント用変数を用意したものの個々に複雑化してしまい、管理が面倒になってしまった経験はないでしょうか。ここでは「多種多様な敵キャラを、1つのワークエリアに割り当てる方法」を紹介します。コードを読んでみることが理解の近道のようなので、さっそく見ていきましょう。
typedef struct { u16 id; // オブジェクトID u16 work[12]; // 汎用ワークエリア } ST_ENEMY; ST_ENEMY Enemy[128];至って普通です。コメントしようがありません(^^;。
// 敵1(タコさん) typedef struct { u16 x; u16 y; u16 vx; // 移動増加値 u16 vy; u16 eventCnt; } ST_ENEMY_TAKO; // 敵2(イカさん) typedef struct { u16 x; u16 y; u16 vx; // 移動増加値 u16 vy; u16 eventCnt; u16 circleCnt; } ST_ENEMY_IKA; // 敵3(サメさん) typedef struct { u16 x; u16 y; u16 vx; // 移動増加値 u16 vy; u16 eventCnt; u16 baseX; // 一定時間経過したら、自機キャラの位置を格納 u16 baseY; } ST_ENEMY_SAME;ここは宣言だけで、実体は作成しません。実体のない入れ物をどうするかは、次の説明で明らかになります。
IWRAM_CODE EnemyExecIka(u16 no) { ST_ENEMY_IKA* w = (ST_ENEMY_IKA*)&Enemy[no].work; // } IWRAM_CODE EnemyExecSame(u16 no) { ST_ENEMY_SAME* w = (ST_ENEMY_IKA*)&Enemy[no].work; // }それぞれの処理関数の先頭で割り当てて、つまりマッピングしてしまえばいいのです。w->hogeと操作すれば、変数にアクセスすることが可能になります。めでたしめでたし(^^;。個人的にですが、このテクニックを始めてみたときは偉く感動しました。みなさんはいかがだったでしょうか。