Arduboyで狙い撃つぜ!(atan2テーブルの作り方) その9
狙い撃ちの精度が悪いので、レーダー法のテーブルを64方向にしてみたのだが。
import math # 64方向 for y in range(23): for x in range(31): # 中心へずらす cx = x - 15 cy = y - 11 # ラジアンから度へ変換する angle = math.atan2(cy, cx) * 180 / math.pi # 角度が32分割した場合のどの値になるか求める idx = round(angle / (360 / 64)) # プラスの値にする if idx < 0: idx += 64 print(format(idx, '#2') + ',', end='') print('')
64方向にしてみても、水平・垂直方向への狙い撃ちの精度はあまり改善せず。
方向計算を1マス16ドットから、8ドットで行うようにしたところ、少しマシになった。(Aボタンで16、8ドットを切り替えられるようにした。)
方向数よりも、計算時の分割数を増やす方が効果があるようだ。
しかし、計算の有効範囲が248x184ドットに狭まってしまう。
Arduboyの画面サイズは128x64なので、十分といえば十分なのだが……。
#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; bool size16 = true; // 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); } // レーダー法のテーブル const uint8_t radarTbl[] PROGMEM = { 38,39,39,40,40,40,41,42,42,43,44,44,45,46,47,48,49,50,51,52,52,53,54,54,55,56,56,56,57,57,58, 38,38,39,39,40,40,41,41,42,42,43,44,45,46,47,48,49,50,51,52,53,54,54,55,55,56,56,57,57,58,58, 38,38,38,39,39,39,40,41,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,55,56,57,57,57,58,58,58, 37,37,38,38,38,39,39,40,41,41,42,43,44,46,47,48,49,50,52,53,54,55,55,56,57,57,58,58,58,59,59, 36,37,37,37,38,38,39,39,40,41,42,43,44,45,47,48,49,51,52,53,54,55,56,57,57,58,58,59,59,59,60, 36,36,36,37,37,38,38,39,39,40,41,42,43,45,46,48,50,51,53,54,55,56,57,57,58,58,59,59,60,60,60, 35,35,36,36,36,37,37,38,38,39,40,41,42,44,46,48,50,52,54,55,56,57,58,58,59,59,60,60,60,61,61, 35,35,35,35,36,36,36,37,37,38,39,40,41,43,46,48,50,53,55,56,57,58,59,59,60,60,60,61,61,61,61, 34,34,34,34,35,35,35,36,36,37,38,39,40,42,45,48,51,54,56,57,58,59,60,60,61,61,61,62,62,62,62, 33,33,34,34,34,34,34,34,35,35,36,37,38,40,43,48,53,56,58,59,60,61,61,62,62,62,62,62,62,63,63, 33,33,33,33,33,33,33,33,33,34,34,34,35,37,40,48,56,59,61,62,62,62,63,63,63,63,63,63,63,63,63, 32,32,32,32,32,32,32,32,32,32,32,32,32,32,32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31,31,31,31,31,31,31,31,31,30,30,30,29,27,24,16, 8, 5, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31,31,30,30,30,30,30,30,29,29,28,27,26,24,21,16,11, 8, 6, 5, 4, 3, 3, 2, 2, 2, 2, 2, 2, 1, 1, 30,30,30,30,29,29,29,28,28,27,26,25,24,22,19,16,13,10, 8, 7, 6, 5, 4, 4, 3, 3, 3, 2, 2, 2, 2, 29,29,29,29,28,28,28,27,27,26,25,24,23,21,18,16,14,11, 9, 8, 7, 6, 5, 5, 4, 4, 4, 3, 3, 3, 3, 29,29,28,28,28,27,27,26,26,25,24,23,22,20,18,16,14,12,10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4, 3, 3, 28,28,28,27,27,26,26,25,25,24,23,22,21,19,18,16,14,13,11,10, 9, 8, 7, 7, 6, 6, 5, 5, 4, 4, 4, 28,27,27,27,26,26,25,25,24,23,22,21,20,19,17,16,15,13,12,11,10, 9, 8, 7, 7, 6, 6, 5, 5, 5, 4, 27,27,26,26,26,25,25,24,23,23,22,21,20,18,17,16,15,14,12,11,10, 9, 9, 8, 7, 7, 6, 6, 6, 5, 5, 26,26,26,25,25,25,24,23,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9, 9, 8, 7, 7, 7, 6, 6, 6, 26,26,25,25,24,24,23,23,22,22,21,20,19,18,17,16,15,14,13,12,11,10,10, 9, 9, 8, 8, 7, 7, 6, 6, 26,25,25,24,24,24,23,22,22,21,20,20,19,18,17,16,15,14,13,12,12,11,10,10, 9, 8, 8, 8, 7, 7, 6, }; int16_t getAtan2(float y, float x) { int16_t ix, iy; if (size16) { // 1マス16ドットで計算する ix = (x + 8.f) / 16.f + 15.f; iy = (y + 8.f) / 16.f + 11.f; } else { // 1マス8ドットで計算する ix = (x + 4.f) / 8.f + 15.f; iy = (y + 4.f) / 8.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.pollButtons(); 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); } } // 1マス16ドット、8ドットを切り替える if (arduboy.justPressed(A_BUTTON)) { size16 = !size16; } arduboy.print(size16 ? "16" : "8"); arduboy.display(); }