Arduboyで狙い撃つぜ!(atan2テーブルの作り方) その8
レーダー法
http://beach.biwako.ne.jp/~beaver/msx/msxtecho/tablsp4.htm
前回作成したレーダーテーブル(32方向)で、プレイヤを狙い撃つようにした。
sinテーブルを32方向で作り直し、getSin関数も32方向に修正する。
import math # 32方向の1/4のデータ for i in range(8): t = math.sin(i / 32 * 2 * math.pi) print(str(t) + ',')
レーダーテーブルの1マスを16x16ドットと考えるようにしたので、496x368ドット外にプレイヤを移動すると、
テーブルの値を正しく取得できなくなり、弾がおかしな方向に撃ち出される。
範囲を超えないように制限するというのも、一つの手だとは思うが、
それを意識しなくてもよいgetAtan2関数にすべきだろう。
#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; // 32方向の1/4のデータ(0°〜90°未満) const float sinTbl[] PROGMEM = { 0.0, 0.19509032201612825, 0.3826834323650898, 0.5555702330196022, 0.7071067811865475, 0.8314696123025451, 0.9238795325112867, 0.9807852804032304, }; // 32方向 float getSin(int16_t angle) { float result = 1.f; // 90°をデフォルト値にする uint8_t idx = angle & 0x0f; // マスクし、0〜15(0°〜180°未満)にする if (idx < 8) { // 0°〜90°未満のデータは用意しているので、そのまま求まる result = pgm_read_float(sinTbl + idx); } else if (idx > 8) { // 90°より大〜180°未満の値は、90°からテーブルを折り返すことで求まる result = pgm_read_float(sinTbl + 16 - idx); } // マスクし、0〜31(0°〜360°未満)にする // 17〜31(180°より大〜360°未満)の時は符号をマイナスにする return ((angle & 0x1f) > 16) ? -result : result; } // 32方向 float getCos(int16_t angle) { return getSin(8 + angle); } // レーダー法のテーブル(32方向) const uint8_t radarTbl[] PROGMEM = { 19,19,20,20,20,20,21,21,21,21,22,22,23,23,24,24,24,25,25,26,26,27,27,27,27,28,28,28,28,29,29, 19,19,19,20,20,20,20,21,21,21,22,22,23,23,23,24,25,25,25,26,26,27,27,27,28,28,28,28,29,29,29, 19,19,19,19,19,20,20,20,21,21,21,22,22,23,23,24,25,25,26,26,27,27,27,28,28,28,29,29,29,29,29, 18,19,19,19,19,19,20,20,20,21,21,22,22,23,23,24,25,25,26,26,27,27,28,28,28,29,29,29,29,29,30, 18,18,19,19,19,19,19,20,20,20,21,21,22,23,23,24,25,25,26,27,27,28,28,28,29,29,29,29,29,30,30, 18,18,18,18,19,19,19,19,20,20,20,21,22,22,23,24,25,26,26,27,28,28,28,29,29,29,29,30,30,30,30, 18,18,18,18,18,18,19,19,19,20,20,21,21,22,23,24,25,26,27,27,28,28,29,29,29,30,30,30,30,30,30, 17,17,18,18,18,18,18,18,19,19,19,20,21,22,23,24,25,26,27,28,29,29,29,30,30,30,30,30,30,31,31, 17,17,17,17,17,17,18,18,18,18,19,19,20,21,22,24,26,27,28,29,29,30,30,30,30,31,31,31,31,31,31, 17,17,17,17,17,17,17,17,17,18,18,18,19,20,22,24,26,28,29,30,30,30,31,31,31,31,31,31,31,31,31, 16,16,16,16,16,17,17,17,17,17,17,17,18,18,20,24,28,30,30,31,31,31,31,31,31,31, 0, 0, 0, 0, 0, 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16,16,16,16,16,15,15,15,15,15,15,15,14,14,12, 8, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 15,15,15,15,15,15,15,15,15,14,14,14,13,12,10, 8, 6, 4, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15,15,15,15,15,15,14,14,14,14,13,13,12,11,10, 8, 6, 5, 4, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 15,15,14,14,14,14,14,14,13,13,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 3, 3, 2, 2, 2, 2, 2, 2, 1, 1, 14,14,14,14,14,14,13,13,13,12,12,11,11,10, 9, 8, 7, 6, 5, 5, 4, 4, 3, 3, 3, 2, 2, 2, 2, 2, 2, 14,14,14,14,13,13,13,13,12,12,12,11,10,10, 9, 8, 7, 6, 6, 5, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 14,14,13,13,13,13,13,12,12,12,11,11,10, 9, 9, 8, 7, 7, 6, 5, 5, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 14,13,13,13,13,13,12,12,12,11,11,10,10, 9, 9, 8, 7, 7, 6, 6, 5, 5, 4, 4, 4, 3, 3, 3, 3, 3, 2, 13,13,13,13,13,12,12,12,11,11,11,10,10, 9, 9, 8, 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 3, 3, 13,13,13,12,12,12,12,11,11,11,10,10, 9, 9, 9, 8, 7, 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 13,13,12,12,12,12,11,11,11,11,10,10, 9, 9, 8, 8, 8, 7, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, }; // 32方向 int16_t getAtan2(float y, float x) { // 1マスを16x16ドットとする // x,yを+8して、マス目の中央に合わせる // xを+15、yを+11して、radarTblテーブルの中心に合わせる int16_t ix = (x + 8.f) / 16.f + 15.f; int16_t iy = (y + 8.f) / 16.f + 11.f; return pgm_read_byte(radarTbl + ix + iy * 31); } 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(); }