Arduboyで狙い撃つぜ!(atan2テーブルの作り方) その11
今までレーダー法を見てきたがいくつか不満点があった。
- 計算できる範囲に制限がある。
- 割り算が重い。
- 精度が低い。
3つ目の精度の問題は置いておいて、とても良い解決策がある。
それはこちら。
こびにぃのあそび:Arduboy
ソースコードがダウンロードできるので、
その中のApp.cppにAppGetAtan関数を見てほしい。
シフト演算で割り算を行っているので、今まで作ってきたatan2よりも、はるかに処理が軽い。
uint8_t AppGetAtan(int16_t y, int16_t x)
と引数が整数値になっているため、ゲーム中の座標値は固定小数点の方が良さそうだが、今回はfloatを渡して使っている。
特に問題は感じられなかった。
元は1周256方向だが、64方向でテーブルを作り直した。
import math # 64方向 for y in range(8): for x in range(8): angle = math.atan2(y, x) * 180 / math.pi index = round(angle / (360 / 64)) if index < 0: index += 64 print(format(index, '#2') + ',', end='') print('')
#include <Arduboy2.h> Arduboy2 arduboy; #define BOSS_SHOT 10 typedef struct CharaData { float x, y; float vx, vy; int life; }; CharaData player; CharaData boss; CharaData bossShot[BOSS_SHOT]; uint16_t counter; // 64方向の1/4のデータ(0°〜90°未満) const float sinTbl[] PROGMEM = { 0.0, 0.0980171403295606, 0.19509032201612825, 0.29028467725446233, 0.3826834323650898, 0.47139673682599764, 0.5555702330196022, 0.6343932841636455, 0.7071067811865475, 0.7730104533627369, 0.8314696123025451, 0.8819212643483549, 0.9238795325112867, 0.9569403357322089, 0.9807852804032304, 0.9951847266721968, }; float getSin(int16_t angle) { float result = 1.f; uint8_t idx = angle & 0x1f; if (idx < 16) { result = pgm_read_float(sinTbl + idx); } else if (idx > 16) { result = pgm_read_float(sinTbl + 32 - idx); } return (angle & 0x3f) > 32 ? -result : result; } float getCos(int16_t angle) { return getSin(16 + angle); } // 方向テーブル(64方向) const uint8_t radarTbl[] PROGMEM = { 0, 0, 0, 0, 0, 0, 0, 0, 16, 8, 5, 3, 2, 2, 2, 1, 16,11, 8, 6, 5, 4, 3, 3, 16,13,10, 8, 7, 6, 5, 4, 16,14,11, 9, 8, 7, 6, 5, 16,14,12,10, 9, 8, 7, 6, 16,14,13,11,10, 9, 8, 7, 16,15,13,12,11,10, 9, 8, }; const uint8_t radarOffset[] PROGMEM = { 0, 32, 0, 32, }; // |Y(-) // | // offset=11 | offset=10 // angle + 32 | -angle + 0 // | // | // -------------+------------- // X(-) | X(+) // | // offset=01 | offset=00 // -angle + 32 | angle + 0 // | // |Y(+) int16_t getAtan2(int16_t y, int16_t x) { // 絶対値を求めて、どの象限になるかoffsetフラグを立てる int8_t offset = 0; if (x < 0) { x = -x; offset |= 1; } if (y < 0) { y = -y; offset |= 2; } // テーブルサイズに収まるまで半分にしていく int16_t n = (y >= x) ? y : x; while (n >= 8) { n >>= 1; x >>= 1; y >>= 1; } // 角度の取得 uint8_t angle = pgm_read_byte(radarTbl + (y << 3)/*8倍する*/ + x); if (((offset + 1) & 2) != 0) { angle = -angle; } return angle + pgm_read_byte(radarOffset + offset); } void setup() { arduboy.begin(); player.x = 20; player.y = 64 / 2; boss.x = 128 / 2; boss.y = 64 / 2; } void loop() { if (!arduboy.nextFrame()) return; arduboy.clear(); if (arduboy.pressed(LEFT_BUTTON)) { player.x -= 1.f; } else if (arduboy.pressed(RIGHT_BUTTON)) { player.x += 1.f; } if (arduboy.pressed(UP_BUTTON)) { player.y -= 1.f; } else if (arduboy.pressed(DOWN_BUTTON)) { player.y += 1.f; } arduboy.fillRect(player.x - 8.f, player.y - 4.f, 16, 8); // ボス(三角形) arduboy.drawTriangle(boss.x, boss.y - 8.f, boss.x - 8.f, boss.y + 8.f, boss.x + 8.f, boss.y + 8.f); // 弾の作成 if (++counter % 10 == 0) { for (int16_t i = 0; i < BOSS_SHOT; ++i) { CharaData *p = &bossShot[i]; if (p->life == 0) { // 弾を使用中にする p->life = 1; // 弾の初期位置をボスの中央にする p->x = boss.x; p->y = boss.y + 2.f; // プレイヤの方向を求める int16_t r = getAtan2(player.y - p->y, player.x - p->x); // 弾の移動方向を求める p->vx = getCos(r); p->vy = getSin(r); break; } } } // 弾の表示 for (int16_t i = 0; i < BOSS_SHOT; ++i) { CharaData *p = &bossShot[i]; if (p->life > 0) { // 弾の移動 p->x += p->vx; p->y += p->vy; // 画面外の弾を消す if (p->x < 0.f || 128.f < p->x || p->y < 0.f || 64.f < p->y) { p->life = 0; } arduboy.fillCircle(p->x, p->y, 2); } } arduboy.display(); }