自学读本:跟着读、照着做,卡住就问 AI
你好,我是吴老师。
上一课我们学会了在屏幕上画点、画线、画方框、画圆。它们都很听话,也很安静——安静到一动不动。
今天我来给它们加一样东西:动起来。
先给你打个预防针:今天真正拦住你的,不是"画",是擦。你马上就会看到。
读完之后,你应该能:解释为什么"只画不擦"会拖出一条尾巴;用"先盖掉旧的、再画新的"做出干净的动画;算清楚整屏重画为什么做不到每秒 30 帧;独立写出一个撞墙反弹的弹球。
用时:约 90 分钟。准备:板子、数据线、已经接入板卡 Skill 的 WorkBuddy。
看不懂的地方,整段复制给 AI,加一句"请一次只解释一个概念,讲完问我一个小问题"。
一、先做一个"会失败"的实验
我们要做一件听起来很简单的事:让一个方块从屏幕左边跑到右边。
你会怎么做?我猜大多数同学会这么说:"每隔一小会儿,把它往右画一点点。"
很好,我们就照这个做。
请新建一个屏幕动画实验:
在屏幕上画一个边长 10 的黄色方块,
每一小段时间把它的横坐标加 1,然后在新的位置再画一个同样的方块。
从屏幕最左边一直画到最右边。
颜色亮度不超过 80,请用中文注释,保留旧程序,烧录前核对端口。
现在请你老实回答我一个问题:
你看到的是一个方块在移动,还是一整条黄色的粗线?
如果你的程序做对了,你看到的是一条线。
先别改。把这个画面记住,它是今天最值钱的一幕。
二、为什么会拖出一条尾巴
我们停下来想一想。
你让板子在第 10 列画了个方块,下一小会儿又让它到第 11 列画一个。那第 10 列那个呢?
答案是:它还好好地在那儿。
屏幕不会自己清除任何东西。你画上去的点就待在那里,直到你用别的颜色把它盖掉。屏幕里既没有"橡皮擦"这个零件,也没有"上一帧"这种记忆——它只记得每个点现在是什么颜色。
所以你画了一百次,屏幕上就留下一百个方块,连成一条线。
这像什么呢?像你拿印章在纸上盖了一个,手往右挪一点再盖一个,再挪一点再盖一个——纸上留下的是一排印章,不是一个会走路的印章。
也像蜗牛爬过留下的痕迹:它确实在移动,但痕迹不会自己消失。
三、屏幕上没有橡皮擦,只有"盖住"
既然没有橡皮擦,那怎么擦?
先想想教室里的黑板:白粉笔写字,黑板擦一抹就没了。
屏幕不是黑板。**它没有黑板擦。**你要擦掉一个白点,唯一的办法是——用黑笔把那个点重新描一遍。
看起来方块消失了,其实它是被黑色盖住了。
所以请记住今天最要紧的这句话:
屏幕上没有擦除,只有覆盖。
现在把程序修好:
请修改刚才的程序:
在画新位置的方块之前,先用背景色(黑色)把旧位置的方块画一遍。
也就是说,每一小段时间里做两件事:先盖掉旧的,再画上新的。
其余不变,亮度不超过 80,保留旧版本,我来观察效果。
这次你应该看到一个干干净净的方块在移动了。
再给你留个小问题
把移动速度调快(每次加 4 而不是加 1),尾巴会回来吗?
如果回来了,想想为什么:两步之间隔得太远,"盖掉旧的"这一步可能盖不准。擦和画必须成对出现,中间不能漏。
四、擦一小块,还是整屏一起擦
现在程序能跑了。我们来想一想"擦"的效率。
每次移动,其实只需要擦掉旧方块那一小块。但有些人图省事:把整块屏幕重新填成黑色,反正背景本来就是黑的。
两种方法效果一样,干的活差多少?
| 做法 | 像什么 | 要处理的点 | 要送的数据 |
|---|---|---|---|
| 擦掉一个 10×10 的小方块 | 用橡皮擦掉写错的一个字 | 100 个 | 200 字节 |
| 整块屏幕重新填成黑色 | 把整页作业撕了重写 | 76,800 个 | 153,600 字节 |
相差约 768 倍。
这个数字眼熟吧?第 5 课我们说过"改多少就送多少",现在它又回来了。
我们用第 3、5 课已经玩熟的 millis() 实测一次:
请做一个擦除方式的计时实验:
1. 记录开始时刻,反复做 100 次:画一个 10×10 的方块、再用背景色盖掉它;
2. 记录开始时刻,反复做 100 次:画一个 10×10 的方块、再用 fillScreen 把整屏填黑;
3. 两次的毫秒数都显示在屏幕上,并通过串口打印出来。
按 BOOT 可以重测一轮。亮度不超过 80。
| 做法 | 我预测 | 实测 | 相差几倍 |
|---|---|---|---|
| 局部擦除 100 次 | ____ | ____ | |
| 整屏重画 100 次 | ____ | ____ |
五、一帧,就是翻页动画里的一页
先问一个很根本的问题
我们做的这些,说到底就是:画一张、盖掉、再画一张、再盖掉。**每一张都是静止的。**那你为什么觉得它在动?
动的是画面吗?不是。
动的是你的眼睛和大脑。
这里有个好玩的现象:一个画面从眼前消失以后,它不会立刻从你的感觉里消失,还会在你脑子里多留一小会儿(大约十几分之一秒)。科学家叫它视觉暂留。
你可以自己试两件事:
- 盯着电风扇的叶片看——明明是分开的三片,转快了就看成一整片模糊的圆。
- 快速翻动课本页角——每页都是静止的小人,翻快了小人就动起来。
电影也是这个道理:电影每秒只放 24 张静止的照片,你的大脑自动把它们连成了连续动作。
这个比喻有个地方要修正:视觉暂留不是"眼睛把画面存起来了",更准确地说是大脑倾向于把前后紧挨着的画面连起来理解。不管叫什么,重点是:连续的动作是你自己补出来的,不是屏幕给你的。
所以动画真正的原理是:屏幕负责一帧一帧画静止的画,你的大脑负责把它们连成动作。
这也解释了一件事——为什么画面切得快一点,动作看起来就顺一点?因为你的大脑要"连得上"。切得太慢,它就连接不起来,你看到的就是一张一张跳过去的幻灯片。
小时候玩过翻页动画吗?在课本每一页的角落画一个小人,每页的动作变一点点,然后快速翻——小人就动起来了。
屏幕上的动画是同一个道理:
- 一帧 = 一次完整的画面(盖掉旧的 + 画上新的),就是翻页动画里的一页
- 帧率 = 一秒钟能画多少帧,就是一秒钟翻多少页
翻得太慢,你看到的是一张张静止的画;翻得够快,眼睛就把它连成了动作。
但是——等待的方式有讲究
程序里"每一小段时间"通常是用 delay() 做的。这有个毛病:
**
delay()期间,板子什么别的事都干不了。**它就那么干等着。
像什么呢?像你守在水壶前等水烧开,眼睛盯着,期间作业没写、门铃没听见。
更讲究的办法是 millis()——相当于定个闹钟:到点了再去画下一帧,中间这段时间你该干嘛干嘛。
这个你在第 2 课的反应力挑战、第 3 课的刷屏计时里都用过了。
请把刚才的程序改成用 millis() 控制节奏:
每隔 20 毫秒才更新一帧,其余时间不要空等,
同时在循环里检测 BOOT 按键,按下时暂停或继续。
请用中文注释,保留旧版本。
改完你会发现:动画照样跑,按键也有反应了。
delay() | millis() | |
|---|---|---|
| 像什么 | 盯着水壶等烧开 | 定个闹钟,先去写作业 |
| 等待时能不能干别的 | 不能 | 能 |
| 按键会不会没反应 | 会 | 不会 |
六、算一笔账:为什么整屏重画根本动不起来
这是今天我最想让你算清楚的一件事。
假设我们想让动画每秒 30 帧——这大概是"看起来比较流畅"的一个门槛。如果每一帧都把整块屏幕重画一遍:
每帧 153,600 字节 × 每秒 30 帧 = 4,608,000 字节
每秒大约 460 万个字节,差不多 4.6 MB。
这是什么概念?打个比方:你要用一根吸管,在一秒钟内喝完一桶水。吸管就那么粗,水就那么多,做不到就是做不到。
这些数据全都要从同一根 SPI 线上挤过去,而 SPI 的速度是有上限的。
所以结论是:整屏重画,想做到每秒 30 帧,物理上扛不住。
这就是为什么动画必须"只改该改的地方"。第 5 课我们说这句话的时候,它还是个建议;今天它变成了硬规矩——不是我们讲究,是做不到。
还有一句要记住:板子扛不住的时候不会报错。它不会跳出来说"我忙不过来了",画面只是变慢、变卡。又是一个"安安静静地做不好"的例子。
七、弹球:撞的是肚子,不是中心点
终于可以做弹球了。先把规则说清楚。
弹球要记住四样东西,你可以把它想成"小球脑子里的小本子":
| 记什么 | 现在是多少 | 干什么用 |
|---|---|---|
位置 x、y | 一开始在屏幕中间 | 现在在哪儿 |
速度 dx、dy | 每帧挪 2 和 3 | 下一帧往哪儿跑 |
| 撞左右边 | dx 变号 | 原来往右跑,改成往左跑 |
| 撞上下边 | dy 变号 | 原来往下掉,改成往上飞 |
写成中文步骤就是:
启动时:
初始化屏幕,整屏填黑
小球位置 x = 120,y = 160
小球每一帧挪动 dx = 2,dy = 3
每一帧:
用背景色在 (x, y) 画一个小球 ← 盖掉旧的
x = x + dx
y = y + dy
如果 x 超出了左右边界:dx = -dx ← 往回走
如果 y 超出了上下边界:dy = -dy
如果 BOOT 被按下:切换暂停 / 继续
用小球颜色在 (x, y) 画一个小球 ← 画上新的
等到下一帧的时间再继续
请你找一找:**哪一步是"擦",哪一步是"画"?**如果把它们的顺序调换会怎么样?
一个几乎人人都会踩的坑
我们的坐标指的是小球的圆心。可小球是有大小的,还有半径。
所以判断"撞到右边"的时候,不能只看 x > 239——那时候球已经有一半跑到屏幕外面去了。
打个比方:你闭着眼睛往后退,撞到墙的是你的肚子,不是你身体中心那个点。
正确的做法是留出身位:x + 半径 > 239 就算撞上了。
这就是编程里常说的边界条件。今天是你第一次遇到它,以后会经常见。
还有一件事
"撞到边界就往回走"——板子是真的知道墙在哪里吗?
不。它只是比较了两个数的大小。它眼里没有墙,只有数字。
回头看看:其实你早就会做动画了
弹球做出来了。但我想请你回头看一件事。
第 1 课我们做过一盏蓝色呼吸灯:一直是蓝色,慢慢变亮,再慢慢变暗。还有彩虹灯:红色慢慢变成黄色,再变成绿色。
那时候我们说的是"改颜色"。今天换个角度看——它们也是动画。
| 动画在改什么 | 例子 | 你哪节课做过 |
|---|---|---|
| 改位置 | 弹球在屏幕上跑 | 今天 |
| 改大小 | 一个圆由小变大,像心跳 | 今天就可以做 |
| 改颜色 | 呼吸灯由暗到亮、彩虹灯变色 | 第 1 课 |
你看,动画不只是"东西在移动"。只要有一个东西在随着时间变,它就是动画。
所以动画其实可以写成一个很简单的公式:
动画 = 一个会变的东西 + 时间
弹球会变的是位置,呼吸灯会变的是颜色,心跳会变的是大小。变的东西不一样,做法却是一模一样的:每隔一小会儿,把那个东西改一点点,然后重新画一次。
你可以让 AI 帮你把第 1 课的呼吸灯"翻译"成今天的说法:
请帮我看看第 1 课的蓝色呼吸灯程序:
如果也把它看成动画,它每一帧改变的是什么?
它有没有"擦"这一步?为什么呼吸灯不需要擦,而弹球必须擦?
请用今天的说法解释,一次讲一点,讲完问我一个问题。
提示:呼吸灯不用擦,是因为灯只有一颗,改颜色就直接盖住了;屏幕上有七万多个点,你必须自己说清楚"要盖掉哪一个"。
八、动手做
请做一个弹球动画:
黑色背景上有一个半径 6 的黄色小球,从屏幕中间出发,
每一帧横向挪 2 个像素、纵向挪 3 个像素。
撞到屏幕四个边就反弹(把对应的速度取相反数)。
每一帧都要先用背景色盖掉旧位置,再画新位置。
按 BOOT 可以暂停和继续。
请保留旧版本,用中文注释,烧录前核对端口,亮度不超过 80。
烧录后请你检查四件事:
- 小球后面有没有尾巴?
- 撞到下边和右边时,是正好贴着边反弹,还是有一半跑到屏幕外面去了?
- 把速度改成 5 和 7,动画变快了还是变卡了?
- 一直盯着看十秒钟,小球会自己乱跑或者卡住吗?
只改一个条件
挑一个来改,先说出你的预测,再动手:
| 我改的条件 | 我预测会怎样 | 实际怎样 |
|---|---|---|
| 把半径从 6 改成 20 | ____ | ____ |
| 把速度从 2、3 改成 8、12 | ____ | ____ |
| 把每帧等待从 20 毫秒改成 100 毫秒 | ____ | ____ |
只让小球横着跑(dy = 0) | ____ | ____ |
改完请回答:小球变大了以后,"擦"这一下要处理的点变多了还是变少了?动画有没有变慢?
九、自检小测
先自己想,再点开对答案。
1. 为什么第一次让方块移动,看到的却是一条线?
因为旧位置的方块没被覆盖掉。屏幕不会自动清除,画一百次就留下一百个方块,连成一条线。
2. 屏幕上"擦掉"一个东西,实际上做了什么?
用背景色在旧位置重新画一遍,把它盖住。屏幕没有橡皮擦,只有覆盖。
3. 擦 10×10 的小方块和整屏重画,数据量差多少?
100 个点 / 200 字节 vs 76,800 个点 / 153,600 字节,相差约 768 倍。
4. 为什么整屏重画做不到每秒 30 帧?
153,600 × 30 ≈ 460 万字节/秒(约 4.6 MB/s),超出 SPI 的实际带宽。板子不会报错,只会变慢变卡。
5. 为什么要用 millis() 而不是 delay()?
delay() 期间板子什么也做不了,按键读不到。用 millis() 到点才画下一帧,中间的空档可以响应输入。像"定闹钟"而不是"盯着水壶等"。
6. 判断弹球反弹时,为什么不能只看圆心?
因为小球有半径。撞到墙的是球的边缘,不是球心——就像你撞墙的是肚子,不是身体中心点。
7. 弹球撞墙反弹,板子真的知道墙在哪吗?
不。它只是比较了两个数的大小。它眼里没有墙,只有数字——所有的"聪明"都来自你写的规则。
十、还想玩点什么
| 方向 | 可以描述给 AI 的效果 | 值得继续想 |
|---|---|---|
| 流星雨 | 几十个小点从上往下掉,掉到底再回到顶上 | 每个点都要单独记住自己的位置吗? |
| 心跳 | 一个圆由小变大、再变回小 | 变大变小的时候,"擦"那一下要擦多大? |
| 跑马灯 | 几个方块轮流亮起来,像在跑 | 它和"一个方块移动"是同一件事吗? |
| 贪吃蛇 | 蛇头往前走,身体跟着头走 | 蛇尾那一格,应该怎么擦? |
| 打砖块 | 顶部一排砖块,被撞到就消失 | 砖块消失后,那里要不要再画一次背景? |
倒数第二行那道题我留给你慢慢想。看起来只是"多画几节身体",真正难的地方还是那个字:擦。
本课的配套资源
本节配套的演示程序源码与可烧录固件,已整合到课程页面底部的「配套资源 · 在线烧录」区——插上你的 ESP32-S3 触摸屏板(Chrome / Edge 浏览器),点「在线烧录」即可直接写入,无需安装软件。
给 AI 的第一句话(每次新开对话先说这句):
请使用课程的 esp32s3-touchboard-hardware-dev Skill,
我现在做第 6 课(屏幕动画),需要你帮我____。
改动前先说清依据的项目文件路径,烧录前先运行 arduino-cli board list 确认端口。