Plane Shooter v3 — 技术文档

文件: esp32_plane_v3.ino (823 行 / 29.8 KB)
硬件: ESP32 + ST7789 240×240 TFT + 4×4 矩阵键盘 + 蜂鸣器
基础: Plane Shooter v2(移动、射击逻辑完全保留)


目录

  1. 版本对比总览
  2. 核心架构
  3. 功能详解
  4. 从 v2 到 v3 的改动清单
  5. 性能与内存

1. 版本对比总览

功能v2v3
敌人类型(4种)
敌人射击(敌弹)新增
Boss 战(每5关)新增
道具系统(4种)
暂停 / 重启
EEPROM 最高分
全键盘 16 键利用7键10键 扩展
A 键炸弹(500分)新增
B 键静音切换新增
C 键累计统计新增
升级视觉通知新增
Boss 入场警告新增
持久化静音状态新增
累计击杀/死亡/Boss击杀新增
Game Over 显示统计新增

2. 核心架构

┌────────────────────────────────────────────┐
│                  loop()                    │
│  每隔 TICK_MS(30ms):                       │
│    ├─ updateGame()   ← 游戏逻辑 + 渲染     │
│    └─ handleInput()  ← 键盘输入处理        │
│                                            │
│   updateGame():                            │
│    ├─ 计时器衰减                           │
│    ├─ Boss 更新 / 敌人生成                  │
│    ├─ 敌弹射击 + 移动                       │
│    ├─ 玩家子弹移动                          │
│    ├─ 敌人 / 道具移动                       │
│    ├─ 碰撞检测 checkCollisions()            │
│    ├─ EEPROM 自动存档                      │
│    └─ 逐层渲染(星→敌弹→子弹→敌人→Boss→道具→玩家→统计→HUD)│
│                                            │
│   handleInput():                           │
│    ├─ 扫描键盘 → h(bitmask) + extraKeys    │
│    ├─ 移动逻辑(不改)                      │
│    ├─ 射击逻辑(不改)                      │
│    └─ V3 扩展键: A=炸弹 B=静音 C=统计       │
└────────────────────────────────────────────┘

V3 全局变量一览

// ─── 原有 ───
float px, py;  bool gameOver, paused, tripleShot, shield;
int lives, score, highScore, level, levelKills, killsNeeded, moveSpeed;
int shootCD, invulnFrames, shakeTimer, flashTimer, levelMsg;
unsigned long pauseBlink, lastSave;
bool blAct[5], enAct[8], puAct[3];  // bullets, enemies, power-ups
 
// ─── V3 新增 ───
#define MAX_EB 12
bool ebAct[12];  float ebX[12], ebY[12];    // 敌弹激活/坐标
float ebVX[12], ebVY[12];                    // 敌弹速度向量
 
bool bossActive;  int bossHP, bossMaxHP;     // Boss 状态
float bossX, bossY, bossVX;                  // Boss 位置/速度
int bossPhase, bossTimer, bossPatternCD;     // Boss 阶段/计时/冷却
 
uint8_t extraKeys;                           // 扩展键值(A/B/C/D)
int totalKills, totalDeaths, bossKills;       // 累计统计
bool soundOn;                                 // 静音状态
int notifyTimer, notifyType;                  // 通知计时/类型
int showStats, bombCost;                      // 统计/炸弹HUD计时器

3. 功能详解

3.1 敌弹系统

设计: 敌方高速/蛇形/坦克兵种会发射子弹。普通敌人不射击。

常量定义:

#define MAX_EB 12                           // 最大敌弹数
float ebVX[MAX_EB], ebVY[MAX_EB];           // 速度向量存储

生成函数(3种弹道):

// 直线弹 — 高速敌人使用,垂直下落
void spawnEB_linear(int eIdx) {
  int s=-1; for(int i=0;i<MAX_EB;i++){if(!ebAct[i]){s=i;break;}} if(s<0) return;
  ebAct[s]=true;
  ebVX[s]=0; ebVY[s]=2.5;          // 纯垂直,速度 2.5px/frame
  ebX[s]=enX[eIdx]+EW/2.0-1; ebY[s]=enY[eIdx]+EH;
}
 
// 扇形弹 — 蛇形(2发)/坦克(3发),扇形散布
void spawnEB_fan(int eIdx, int cnt) {
  for(int f=0; f<cnt; f++) {
    int s=-1; for(int i=0;i<MAX_EB;i++){if(!ebAct[i]){s=i;break;}} if(s<0) break;
    ebAct[s]=true;
    float a = -PI/2 + (f - (cnt-1)/2.0) * PI/6.0;  // 扇形张角 ±30°
    ebVX[s]=cos(a)*2.0; ebVY[s]=sin(a)*2.0;
    ebX[s]=enX[eIdx]+EW/2.0-1; ebY[s]=enY[eIdx]+EH;
  }
}
 
// 瞄准弹 — Boss专用,朝玩家方向
void spawnEB_aimed(int eIdx) {
  int s=-1; for(int i=0;i<MAX_EB;i++){if(!ebAct[i]){s=i;break;}} if(s<0) return;
  ebAct[s]=true;
  float dx=px+PW/2.0-(enX[eIdx]+EW/2.0);
  float dy=py-(enY[eIdx]+EH);
  float d=sqrt(dx*dx+dy*dy)+0.01;
  ebVX[s]=dx/d*1.8; ebVY[s]=dy/d*1.8;  // 归一化方向 × 1.8 速度
  ebX[s]=enX[eIdx]+EW/2.0-1; ebY[s]=enY[eIdx]+EH;
}

射击调度 (updateGame() 中):

static int enShootClock=0;                  // 全局射击时钟
if(!bossActive) {
  enShootClock++;
  for(int e=0; e<MAX_EN; e++) {
    if(!enAct[e]) continue;
    if(enType[e]==0) continue;             // 普通敌人不射击
    int rate=60;                           // 基础射击间隔
    if(enType[e]==1) rate=55;              // 高速
    if(enType[e]==2) rate=40;              // 蛇形
    if(enType[e]==3) rate=30;              // 坦克(最短间隔)
    if((enShootClock+e*7)%rate==0) {       // 分散各敌射击帧
      if(enType[e]==1) spawnEB_linear(e);
      else if(enType[e]==2) spawnEB_fan(e,2);
      else if(enType[e]==3) spawnEB_fan(e,3);
    }
  }
}
敌人类型弹道射速(帧)射速(秒)
普通 (type=0)
高速 (type=1)直线下坠55~1.7s
蛇形 (type=2)2发扇形40~1.2s
坦克 (type=3)3发扇形30~0.9s

敌弹移动 (updateGame() 中):

for(int i=0; i<MAX_EB; i++) {
  if(!ebAct[i]) continue;
  ebX[i] += ebVX[i];  ebY[i] += ebVY[i];
  if(ebX[i]<-5 || ebX[i]>GW+5 || ebY[i]<-5 || ebY[i]>GH+5)
    ebAct[i] = false;
}

敌弹渲染(红色小点,3×3 像素):

for(int i=0; i<MAX_EB; i++) {
  if(!ebAct[i]) continue;
  spr.fillRect(ebX[i]-1, ebY[i]-1, 3, 3, 0xF800);   // 红色外框
  spr.fillRect(ebX[i],   ebY[i],   1, 1, 0xFD40);    // 亮红色核心
}

玩家碰撞检测:

// ★ Enemy bullet vs Player
if(invulnFrames==0) {
  for(int e=0; e<MAX_EB; e++) {
    if(!ebAct[e]) continue;
    if(overlap(ebX[e], ebY[e], 3, 3, px, py, PW, PH)) {
      ebAct[e]=false;
      if(shield) { shield=false; sfxPower(); }
      else loseLife();
      break;
    }
  }
}

3.2 Boss 战

设计: 每 5 级触发一次 Boss 战。入场阶段 Boss 从屏幕顶部滑入,随后进入循环攻击模式。半血狂暴。

触发时机doLevelUp() 中:

// ★ Boss check
if(level%5==0 && !bossActive) startBoss();

Boss 实体定义:

#define BOSS_W 48     // 宽度
#define BOSS_H 28     // 高度

初始化和入场阶段:

void startBoss() {
  bossActive=true;  bossHP = 15 + level*2;  bossMaxHP = bossHP;
  bossX=GW/2-BOSS_W/2;  bossY=-BOSS_H;        // 从屏幕顶部外开始
  bossVX=1.2;  bossPhase=0;  bossTimer=0;  bossPatternCD=0;
  notifyTimer=120;  notifyType=NFY_BOSSWARN;   // 2秒BOSS警告
  // 清场现有敌人 + 敌弹
  for(int i=0;i<MAX_EN;i++) { if(enAct[i]) { explode(...); enAct[i]=false; score+=3; } }
  for(int i=0;i<MAX_EB;i++) ebAct[i]=false;
}

Boss 战斗循环 (updateBoss()):

void updateBoss() {
  if(!bossActive) return;
 
  // Phase 0: 入场(滑入到 y=20)
  if(bossPhase==0) {
    bossY += 0.8;
    if(bossY >= 20) { bossY=20; bossPhase=1; bossTimer=0; }
    return;
  }
 
  // Phase 1: 战斗
  bossTimer++;
 
  // 正弦水平移动
  bossX += bossVX;
  if(bossX<=0 || bossX+BOSS_W>=GW) {
    bossVX = -bossVX;
    bossX = constrain(bossX, 0, GW-BOSS_W);
  }
 
  // 攻击模式循环
  if(--bossPatternCD <= 0) {
    int p = (bossTimer/120) % 3;        // 每120帧切一次模式
    switch(p) {
      case 0: spawnEB_fan_boss(5);  bossPatternCD=35; break;   // 5发扇形
      case 1: spawnEB_aimed_boss(); spawnEB_aimed_boss();      // 3发瞄准
              spawnEB_aimed_boss();  bossPatternCD=25; break;
      case 2: bossVX=(rng(0,1)?1:-1)*3.0; bossPatternCD=50; break; // 冲锋冲刺
    }
    // 半血狂暴:冷却-5帧
    if(bossHP < bossMaxHP/2) bossPatternCD -= 5;
    if(bossPatternCD < 12) bossPatternCD = 12;
  }
}
模式弹幕CD半血CD
扇形散射 (p=0)5发扇形35帧30帧
瞄准射击 (p=1)3发追踪25帧20帧
冲锋冲刺 (p=2)高速移动50帧45帧

Boss 受击与击败:

void hitBoss(int dmg) {
  bossHP -= dmg;  sfxHit();
  if(bossHP <= 0) {
    // 大爆炸视觉效果
    for(int i=0; i<18; i++) {
      float a=random(0,628)/100.0; int d=rng(10,40);
      int bx=bossX+BOSS_W/2+cos(a)*d, by=bossY+BOSS_H/2+sin(a)*d;
      if(bx>0&&bx<GW&&by>0&&by<GH) spr.fillCircle(bx,by,rng(2,5),0xFBE0);
    }
    score+=50; levelKills+=5; bossKills++;
    bossActive=false; shakeTimer=20; flashTimer=15;
    // 掉落2个道具
    spawnPowerUp(bossX+BOSS_W/2-10, bossY+BOSS_H/2);
    spawnPowerUp(bossX+BOSS_W/2+10, bossY+BOSS_H/2);
    saveEEPROM();
  }
}

Boss 渲染:

void drawBoss() {
  if(!bossActive) return;
  // 身体
  spr.fillRect(bossX, bossY, BOSS_W, BOSS_H, 0xC618);
  spr.fillRect(bossX+2, bossY+2, BOSS_W-4, BOSS_H-4, 0x7BEF);
  // 驾驶舱(眼)
  spr.fillRect(bossX+BOSS_W/2-4, bossY+5, 8, 8, 0xF800);
  spr.fillRect(bossX+BOSS_W/2-1, bossY+7, 2, 3, TFT_WHITE);
  // 机翼
  spr.fillTriangle(bossX-6, bossY+BOSS_H,
                   bossX+4, bossY+BOSS_H-8,
                   bossX+12, bossY+BOSS_H, 0x9CD3);
  spr.fillTriangle(bossX+BOSS_W+6, bossY+BOSS_H,
                   bossX+BOSS_W-4, bossY+BOSS_H-8,
                   bossX+BOSS_W-12, bossY+BOSS_H, 0x9CD3);
  // 受击闪烁
  if(flashTimer>0 && flashTimer%3<2) return;
  // HP 血条
  float hpR = (float)bossHP / bossMaxHP;
  int barW = BOSS_W;
  spr.drawRect(bossX, bossY-6, barW, 4, 0xFFFF);
  spr.fillRect(bossX+1, bossY-5, barW-2, 2,
    hpR>0.3 ? 0x07E0 : (hpR>0.15 ? 0xFBE0 : 0xF800));
}

3.3 全键盘利用

设计: 原 v2 使用 7 个键(2/4/6/8/5/#/0/*),v3 扩展到 10 键,新增 A/B/C 键。D 键预留。

关键设计: 在不触碰原 scanHeld() 返回值的前提下,通过独立全局变量 extraKeys 追踪扩展键。

uint8_t extraKeys;  // bit0=A  bit1=B  bit2=C  bit3=D
 
uint8_t scanHeld() {
  uint8_t h=0;  extraKeys=0;                   // ★ 每次清零
  for(int r=0; r<4; r++) {
    for(int rr=0; rr<4; rr++) digitalWrite(RP[rr], rr==r?LOW:HIGH);
    delayMicroseconds(50);
    for(int c=0; c<4; c++) {
      if(digitalRead(CP[c])==LOW) {
        const char m[16]={'1','2','3','A','4','5','6','B',
                          '7','8','9','C','*','0','#','D'};
        char k=m[r*4+c];
        // ─── 原 v2 位掩码(不变)───
        if(k=='2') h|=0x01; if(k=='4') h|=0x02;
        if(k=='6') h|=0x04; if(k=='8') h|=0x08;
        if(k=='5') h|=0x10; if(k=='#') h|=0x20;
        if(k=='0') h|=0x40; if(k=='*') h|=0x80;
        // ─── V3 扩展键 ───
        if(k=='A') extraKeys|=0x01;
        if(k=='B') extraKeys|=0x02;
        if(k=='C') extraKeys|=0x04;
        if(k=='D') extraKeys|=0x08;
      }
    }
  }
  return h;  // 返回值依然是 uint8_t,0x01-0x80
}

扩展键处理 (handleInput() 中):

void handleInput() {
  static uint8_t prev=0;
  static uint8_t prevExtra=0;               // ★ 扩展键去抖
  uint8_t h = scanHeld();
  uint8_t ex = extraKeys;
 
  // ... 原有处理 ...
 
  // ★ A键炸弹(边沿触发,消耗500分)
  if((ex&0x01) && !(prevExtra&0x01) && !gameOver && !paused) {
    useBomb();
  }
  // ★ B键静音切换(边沿触发)
  if((ex&0x02) && !(prevExtra&0x02)) {
    soundOn = !soundOn;  saveEEPROM();
  }
  // ★ C键显示统计
  if(ex & 0x04) showStats = 60;
 
  // ... 原有的移动/射击代码(不变)...
 
  prev=h;  prevExtra=ex;
}
按键属性功能触发方式
AextraKeys & 0x01炸弹(500分全屏清敌+敌弹)边沿(按下即触发)
BextraKeys & 0x02静音开关(EEPROM持久化)边沿(按下即触发)
CextraKeys & 0x04累计统计显示(60帧)电平(按住持续刷新)
DextraKeys & 0x08预留

炸弹实现:

void useBomb() {
  if(score < 500) return;          // 不足500分不触发
  score -= 500;  bombCost = 60;    // 扣分 + HUD闪烁60帧
  clearScreen();                   // 清屏(敌人+敌弹)
}

3.4 扩展 EEPROM 存档

EEPROM 布局(64 字节,从 v2 的 32 字节扩容):

地址变量类型大小
0highScoreint (4字节)最高分
4soundOnuint8_t (1字节)静音状态
5totalKillsuint16_t (2字节)累计击杀
7totalDeathsuint16_t (2字节)累计死亡
9bossKillsuint16_t (2字节)Boss击杀
11reserved预留

存储/读取:

void saveEEPROM() {
  EEPROM.put(EE_HS,   highScore);
  EEPROM.put(EE_SND,  (uint8_t)soundOn);
  EEPROM.put(EE_TKIL, (uint16_t)totalKills);
  EEPROM.put(EE_TDTH, (uint16_t)totalDeaths);
  EEPROM.put(EE_BKIL, (uint16_t)bossKills);
  EEPROM.commit();
}
 
void loadEEPROM() {
  EEPROM.get(EE_HS, highScore);
  uint8_t snd;  EEPROM.get(EE_SND, snd);
  soundOn = (snd==1);
  uint16_t v;
  EEPROM.get(EE_TKIL, v);  totalKills = (v<9999)?v:0;
  EEPROM.get(EE_TDTH, v);  totalDeaths = (v<9999)?v:0;
  EEPROM.get(EE_BKIL, v);  bossKills = (v<9999)?v:0;
  if(highScore > 99999) highScore = 0;   // 未初始化保护
}

存档时机:

时机函数调用
升级doLevelUp()
玩家受伤loseLife()
Boss 击败hitBoss()
自动保存updateGame() 每 5 秒
静音切换handleInput() B键触发

3.5 静音开关

设计: 通过宏 BEEP() 全局控制所有音效,静音时保持相同延迟以保证游戏节奏不变。

bool soundOn;  // 默认 true
 
// 底层蜂鸣器(静音时仅延迟,不发声)
void beep(int f, int ms) {
  if(!soundOn) { delay(ms); return; }
  ledcWriteTone(BZ, f);  delay(ms);  ledcWriteTone(BZ, 0);
}
 
// 所有音效通过此宏调用
#define BEEP(f, ms) do { if(soundOn) { beep(f, ms); } else { delay(ms); } } while(0)

HUD 显示 (drawHUD() 中):

if(!soundOn) {
  tft.setTextColor(0xAD55, TFT_BLACK);
  tft.setCursor(SW/2-12, 22);
  tft.print("MUTE");
}

3.6 视觉通知系统

通知类型:

#define NFY_SPEEDUP  1     // "SPEED+" — 每3级变速提示
#define NFY_ALERT    2     // "ALERT+" — 难度提升警告
#define NFY_NEWENEMY 3     // "NEW ENEMY!" — 新敌人解锁
#define NFY_BOSSWARN 4     // "BOSS!" — Boss入场警告

触发时机doLevelUp() 中:

if(level%3 == 0) { notifyTimer=45; notifyType=NFY_SPEEDUP; }
if(level == 2)   { notifyTimer=60; notifyType=NFY_NEWENEMY; }
if(level == 4)   { notifyTimer=60; notifyType=NFY_NEWENEMY; }

渲染drawHUD() 中:

if(notifyTimer>0) {
  switch(notifyType) {
    case NFY_SPEEDUP:
      tft.setTextColor(0xFBE0, TFT_BLACK);
      tft.setCursor(SW-64, 22);  tft.print("SPEED+");  break;
    case NFY_ALERT:
      tft.setTextColor(0xF800, TFT_BLACK);
      tft.setCursor(SW-64, 22);  tft.print("ALERT+");  break;
    case NFY_NEWENEMY:
      tft.setTextColor(0xF81F, TFT_BLACK);
      tft.setCursor(SW-80, 22);  tft.print("NEW ENEMY!");  break;
    case NFY_BOSSWARN:
      tft.setTextColor(0xF800, TFT_BLACK);  tft.setTextSize(2);
      tft.setCursor(SW/2-36, 14);  tft.print("BOSS!");
      tft.setTextSize(1);  break;
  }
}

3.7 累计统计

全局变量:

int totalKills;    // 累计击杀(含所有敌兵 + Boss 清场击杀)
int totalDeaths;   // 累计死亡次数
int bossKills;     // Boss 击杀数

统计更新点:

事件代码位置更新内容
击杀敌人checkCollisions()totalKills++
玩家死亡loseLife()totalDeaths++
Boss 击败hitBoss()bossKills++

C 键实时显示updateGame() 渲染层:

if(showStats > 0) {
  spr.setTextColor(TFT_WHITE, TFT_BLACK); spr.setTextSize(1);
  spr.setCursor(4, GH-36);
  spr.print("K:");  spr.print(totalKills);
  spr.print(" D:"); spr.print(totalDeaths);
  spr.print(" BOSS:"); spr.print(bossKills);
}

3.8 Game Over 扩展

原 v2: 仅显示 SCORE + LV + 新高分提示
V3 新增: 累计 K/D/Boss 统计

void drawGameOver() {
  spr.fillSprite(TFT_BLACK);
  // "GAME OVER" 闪烁标题
  spr.setTextColor((millis()/400)%2 ? 0xFBE0 : 0x528A, TFT_BLACK);
  spr.setTextSize(2);
  spr.drawCentreString("GAME OVER", GW/2, GH/2-40, 1);
 
  // SCORE + LV
  spr.setTextColor(TFT_WHITE, TFT_BLACK); spr.setTextSize(1);
  String s = "SCORE:" + String(score) + "  LV:" + String(level);
  spr.drawCentreString(s.c_str(), GW/2, GH/2-15, 1);
 
  // 新高分
  if(score>=highScore && score>0) {
    spr.setTextColor(0x07E0, TFT_BLACK);
    spr.drawCentreString("NEW HIGH SCORE!", GW/2, GH/2+2, 1);
  }
 
  // ★ 累计统计
  spr.setTextColor(0xAD55, TFT_BLACK);
  String st = "K:" + String(totalKills) +
              "  D:" + String(totalDeaths) +
              "  BOSS:" + String(bossKills);
  spr.drawCentreString(st.c_str(), GW/2, GH/2+20, 1);
 
  spr.setTextColor(0x8410, TFT_BLACK);
  spr.drawCentreString("Press * to restart", GW/2, GH/2+40, 1);
  spr.pushSprite(0, SH-GH);
}

4. 从 v2 到 v3 的改动清单

新增代码块(按行号)

行范围函数/定义功能
头部注释版本描述更新v2 → v3
L58#define MAX_EB 12敌弹上限
L66-69#define NFY_*通知类型枚举
L85-86ebAct/ebX/ebY敌弹数组
L89-92bossActive/HP/X/Y/VX/Phase/Timer/CDBoss 状态
L104-109extraKeys/totalKills/Deaths/bossKills/soundOn/notify/...扩展全局变量
L112#define EE_*EEPROM 新布局
L115-133saveEEPROM() / loadEEPROM()EEPROM 扩写
L137beep()if(!soundOn)静音支持
L138#define BEEP(f,ms)静音宏
L148-154scanHeld()extraKeys扩展键扫描
L164-165initGame()ebAct[]=false; bossActive=false初始清零
L206-209float ebVX[12], ebVY[12]敌弹速度向量
L211-240spawnEB_aimed/fan/linear()敌弹生成函数
L244spawnEnemy()if(bossActive) returnBoss期间禁刷敌
L255-371Boss 全部函数Boss 系统
L361-364checkCollisions() 中 Boss 碰撞玩家子弹打Boss
L372-381checkCollisions() 中 敌弹 vs 玩家敌弹碰撞检测
L396clearScreen() 中清敌弹炸弹清屏
L403-414doLevelUp() 中通知触发升级通知
L418loseLife() 中清敌弹死亡后清弹
L432-436useBomb()A键炸弹
L467-502drawHUD() 扩展静音/炸弹/通知渲染
L523-534updateGame() 中 Boss更新/敌人生成Boss调度
L537-554updateGame() 中敌弹射击+移动敌弹调度
L576-581updateGame() 中敌弹渲染敌弹绘制
L585updateGame()drawBoss()Boss渲染
L589-594updateGame() 中统计叠加C键统计
L618-627drawGameOver() 中统计行Game Over统计
L634-635handleInput()prevExtra/ex扩展键处理
L639-645handleInput() 中 A/B/C 键逻辑扩展键动作
L651-653handleInput() 中移动/射击批注不改标识
L658setup()EEPROM.begin(64)EEPROM 扩容
L659setup()soundOn 默认静音初始化

不变代码(明确保留)

代码段内容
scanHeld() 返回值 uint8_t + 位掩码 0x01-0x80原有 7 键位不变
移动逻辑moveSpeed=3+(level/3) + 4方向判定 + 边界约束
射击触发if(h&(0x01|0x10|0x20|0x08)) spawnBullet()
spawnBullet()完整保留,CD=8,三重射击偏移
敌人类型/外观drawEnemy() 完整保留
道具系统spawnPowerUp/applyPowerUp 完整保留
爆炸效果explode() 完整保留
暂停逻辑drawPaused() 完整保留

5. 性能与内存

指标v2v3变化
代码行数~500823+65%
文件大小~21KB29.8KB+42%
全局变量~200 bytes~280 bytes+80 bytes
Flash占用估算~270KB~335KB+65KB
MAX_EB(敌弹)12新增
EEPROM 大小32 bytes64 bytes+32 bytes
帧率33 FPS33 FPS不变
每帧额外计算Boss AI + 敌弹12发可忽略

Flash 占用在 ESP32 4MB 容量内(~8%),RAM 全局变量增量 80 字节在 520KB SRAM 内完全安全。


文档生成时间: 2026-06-18
*代码位置: `esp32_joystick_test\esp32_plane