AI脑洞板
完整教案
自学课件
第 33 课 · 共 33/51 课

飞机大战 组合、镜像与发布会

课时
待定
器材
ESP32-S3 触摸屏板 + USB 线
前置知识
无
配套代码
见项目 demo 目录

你好,我是吴老师。这份是给你自己读的——不用等我讲,看不懂就整段复制给 AI,加一句"请一次只解释一个概念,讲完问我一个小问题"。

读完之后,你应该能:

  • 说出"对象池"是什么(用几个固定槽位 + 一个开关来"借还");
  • 说清"判断子弹打中敌机"用哪两个数比较、为什么要"近似";
  • 明白"先画到 sprite 再 pushSprite"为什么能让游戏不闪,且这块画布要放 PSRAM。

用时:约 60 分钟。 准备:烧录 demo01_plane_battle 的板子 + 一根 USB 线。

一、让飞机满屏飞

前四课的游戏都"待在一个格子里"。今天让一架飞机在整块屏幕上自由地飞、自动开火、撞击敌机——它不再用网格,改成"每个东西记住自己的位置和速度"。

现象:飞机跟着你的手指飞,自动射击,头顶分数往上跳;被撞到就 GAME OVER。

二、我观察到的现象

✏️ 可以直接在表格里填写
现象勾选
手指按住,飞机就飞过去
每隔一会儿自动打一颗子弹
敌机从顶上不断掉下来
子弹打中敌机,敌机没了、分数 +1
敌机撞到飞机,GAME OVER

三、子弹和敌机:先"登记"再"出动"——对象池

同时可能有十几颗子弹、几架敌机,总不能让每颗都"新建"吧?太慢还占内存。

用一个巧法——对象池:先预备好固定数量的"空位置"(12 颗子弹、10 架敌机的"槽位"),每个槽位有个 on 开关。

struct Bullet { int x, y; bool on; };
Bullet bullets[12];   // 预备 12 颗子弹的"停车场"
  • 发射:找第一个 on==false 空槽,填位置,on=true;
  • 飞出屏:on=false,槽位还给下一颗。

像停车场:不是每来一辆车就盖一个新停车场,而是先画好一批车位,车进"在用"车位、开走就"释放"。位置固定、数量有上限,又快又省内存。

最该记住的:子弹、敌机是先备好槽位、用 on 开关借还——这就是对象池。

对象池 = 先备好一排车位(固定槽位)+ on 开关借还:发射借空槽、飞出屏释放

四、两个代码名,够了

1. 碰撞检测——"两样东西有没有碰到"

看两样东西的 x 差值和 y 差值,是不是都小于某个"半径和"。
   dx = |子弹.x - 敌机.x|;
   dy = |子弹.y - 敌机.y|;
   if (dx < 半径和 && dy < 半径和) 就认为"打中了"。

真正的子弹、敌机形状不同,但判"碰没碰上"不需要那么准——挨得够近就算命中,这叫"近似碰撞",又快又够用。

近似碰撞:比 x 差值 + y 差值是否都小于「半径和」,挨得近就算命中,又快又够用

2. fb.pushSprite(0, 0)——"整幅画一次性刷上去"

先把这一帧(飞机、子弹、敌机、分数)画到一块"草稿画布" sprite 上,
再 call 一次 pushSprite(0,0),把整块草稿一次性贴到屏幕。

为什么不一笔一笔画到屏?那样屏幕会一边画一边显示,快速运动就出现闪烁/撕裂。先画完整帧再整体刷,屏幕看到的永远是一张完整的画。这块草稿约 150KB,必须放 PSRAM(用 PSRAM=opi 编译)。

最该记住的:先画草稿、再整块刷屏(sprite + pushSprite)是关键,需要大的 PSRAM 画布。

先画 sprite 草稿再 pushSprite 整帧刷屏:不闪不撕裂,画布约 150KB 放 PSRAM

五、为什么用"自由坐标",而不是网格?

做法怎么动适合
网格(一格一格)x、y 是整数格号规则清晰的棋类/蛇
自由坐标(像素)x、y 是像素,每帧加速度平滑连续的射击/飞行

网格适合"对齐",坐标适合"流畅"。 飞机要满屏平滑飞,就用坐标;每帧 y 加一个速度,就连贯下落。

六、改一个条件:变难或变简单

✏️ 可以直接在表格里填写
我改了什么我预测实际
敌机速度 y+=2 → y+=4改 updateGame() 那行,其余不动
发射间隔 FIRE_MS 改小子弹更密,更好打中

难不难,往往只是一个数字。 速度/间隔/半径都是常量,改一个,整个节奏就变——这是游戏好调的秘密。

七、一条完整的"打一颗子弹并命中"链路

飞机在 (px,py) → 每隔 FIRE_MS 发一颗子弹(找空槽, on=true)
每 40ms(约25帧/秒):子弹 y 减6(往上),敌机 y 加2(往下)
撞不撞:每颗子弹×每架敌机,比 x/y 差值与半径和
    够近 → 两个都 on=false 消失, score++
敌机撞飞机 → GAME OVER over=true
每帧:把飞机/子弹/敌机/分数画到 sprite 草稿
    再 pushSprite(0,0) 一次刷上屏 → 不闪
GAME OVER 后点屏重开:分数清零, 所有槽位 on=false

真正要记住的只有一句:飞机大战 = 对象池(借还槽位)+ 近似碰撞(比差值)+ sprite 整帧刷屏(不闪)。

八、打不中 / 卡顿 / 判错,就这样排查

现象观察还是推测查什么
飞机不跟手观察触摸坐标接对没
打了半天敌机不清观察敌机出屏释放 on=false 没
子弹打中只加一次分观察命中后把两个都设 false 没
一闪一闪观察用 pushSprite 整帧刷没
越刷越卡观察槽位释放没

九、过关检查

  1. "对象池"是什么?为什么用固定槽位?
  2. 判断"子弹打中敌机"用哪两个数?为什么不用很精确?
  3. 为什么先画 sprite 再 pushSprite 就不闪?画布多大、放哪?
  4. 飞机为什么用坐标、贪吃蛇用网格?
  5. 想敌机更快更密,改哪几个数?
  6. ScreenMirror 镜像到网页有什么用?

留下三样东西:你打到最高分的那局、你改的参数、"游戏为什么流畅又不闪"的一句话。

十、还能玩什么

作品方向描述给 AI 的效果再想想
敌机编队一排敌机一起下压怎么让一排一起动?
道具掉落加速/护盾/三连发道具给玩家加什么?
三条命扣命而不是直接结束"生命值"也是状态,改哪?
双人积分赛两块板对打比分数需要什么通信?
试玩迭代请人试玩、收集反馈、改一版反馈哪些是现象、哪些是推测?

下节课预告:第六章游戏开发到此收尾。下一章让板子管学习——第 34 课做一张能翻的课程表。

第 33 课 · 飞机大战

第 33 课 · 飞机大战

手指拖动我的飞机躲敌机,飞机自动开火打敌机得分,被撞到就结束。

33游戏射击计分