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();
}