Arduboyで狙い撃つぜ!(atan2テーブルの作り方) その3

atanをテーブルから求める方法だが、M-KAI氏の書かれた物を参考にさせてもらった。
http://web.archive.org/web/20021220090455/http://www02.geocities.co.jp:80/SiliconValley-Bay/8572/algobib/lv019.htm
ちなみにM-KAI氏のゲームはこちらでプレイできる。
store.steampowered.com


tanはx,yの比なので、
狙いたい方向の、x,yの比をテーブル化しておき、
現在のx,yの比に近い値をテーブルから探し出し、そのインデックスを使用する、
ということらしい。

テーブルの値は、1周分を持つ必要はなく、第1象限のデータがあれば、
後は場合分けで求まる。

atanテーブルはPythonで作成し、1周を64方向に分割することにし、
その4分の1のデータ(16個)を求めることにする。
角度を+0.5し、角度の境目を中間にずらしている所がミソですね。

import math

for angle in range(16):
    r = (angle + 0.5) / 64 * 2 * math.pi
    x = math.cos(r)
    y = math.sin(r)
    print(str(y / x) + ',')
#include <Arduboy2.h>
#include <float.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;

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方向の1/4のデータ
const float atanTbl[] PROGMEM = {
  0.049126849769467254,
  0.14833598753834742,
  0.25048696019130545,
  0.3578057213145241,
  0.4729647758913199,
  0.5993769336819237,
  0.7416505462720353,
  0.9063471690191469,
  1.1033299757334754,
  1.3483439134867197,
  1.668399205583507,
  2.11432235754864,
  2.7948127724904768,
  3.9922237837700827,
  6.7414524054149885,
  20.35546762498714,
};

int16_t getAtan2(float y, float x)
{
  // XとYの長さの比を求める
  // xが0(90° or 270°)の時は、割り算できないので、floatの最大値を入れてテーブルに引っかからないようにする
  // 第1象限のデータしかないので、absでマイナスの符号を消す
  float ratio = (x == 0.f) ? FLT_MAX : abs(y / x);

  // atanテーブルから、求めた比に近い値のインデックスを探す
  int16_t idx;
  for (idx = 0; idx < 16; ++idx) {
    if (ratio < pgm_read_float(atanTbl + idx)) {
      break;
    }
  }

  // idxは第1象限の値なので、実際の象限の値に調整する
  if (x < 0) idx = 32 - idx;
  if (y < 0) idx = 64 - idx;

  return idx;
}

void setup()
{
  arduboy.begin();
  arduboy.setFrameRate(60);
  arduboy.clear();

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