AI 编程课
完整教案
自学课件
6 课 · 共 6/48

让画面动起来 ——移动、擦除和帧

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

自学读本:跟着读、照着做,卡住就问 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 次________

五、一帧,就是翻页动画里的一页

帧与帧率

先问一个很根本的问题

我们做的这些,说到底就是:画一张、盖掉、再画一张、再盖掉。**每一张都是静止的。**那你为什么觉得它在动?

动的是画面吗?不是。

动的是你的眼睛和大脑。

这里有个好玩的现象:一个画面从眼前消失以后,它不会立刻从你的感觉里消失,还会在你脑子里多留一小会儿(大约十几分之一秒)。科学家叫它视觉暂留

你可以自己试两件事:

  1. 盯着电风扇的叶片看——明明是分开的三片,转快了就看成一整片模糊的圆。
  2. 快速翻动课本页角——每页都是静止的小人,翻快了小人就动起来。

电影也是这个道理:电影每秒只放 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 课我们说这句话的时候,它还是个建议;今天它变成了硬规矩——不是我们讲究,是做不到。

还有一句要记住:板子扛不住的时候不会报错。它不会跳出来说"我忙不过来了",画面只是变慢、变卡。又是一个"安安静静地做不好"的例子。

七、弹球:撞的是肚子,不是中心点

终于可以做弹球了。先把规则说清楚。

弹球要记住四样东西,你可以把它想成"小球脑子里的小本子":

记什么现在是多少干什么用
位置 xy一开始在屏幕中间现在在哪儿
速度 dxdy每帧挪 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。

烧录后请你检查四件事:

  1. 小球后面有没有尾巴
  2. 撞到下边和右边时,是正好贴着边反弹,还是有一半跑到屏幕外面去了?
  3. 把速度改成 5 和 7,动画变快了还是变卡了?
  4. 一直盯着看十秒钟,小球会自己乱跑或者卡住吗?

只改一个条件

挑一个来改,先说出你的预测,再动手:

我改的条件我预测会怎样实际怎样
把半径从 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 确认端口。

本节配套资源 · 在线烧录

查看全部固件 →

下列演示程序已编译为可烧录固件。把你的 ESP32-S3 触摸屏板 用 USB 接上电脑(需 Chrome / Edge 浏览器),点「在线烧录」即可直接写入,无需安装任何软件。

显示图形测试
可烧录

综合演示基本绘图、字符串与旋转功能的显示测试。适合第一次烧录验证屏幕是否正常。

显示 图形 入门
✓ 可在线烧录
第 6 课 · 会拖尾的方块(故意失败)
可烧录

故意不擦旧位置,方块拖出一条长长的黄色尾巴。用来理解屏幕不会自动清除画过的东西。

06 动画 反例
第 6 课 · 擦干净再画
可烧录

先用背景色盖掉旧位置,再画新位置。干净的单方块从左移到右,没有拖尾。

06 动画 基础
第 6 课 · 局部擦 vs 整屏清
可烧录

局部擦一小块和清掉整屏各做 100 次,屏幕与串口报出毫秒数和相差倍数,按 BOOT 重测。

06 计时 实验
第 6 课 · 弹球
可烧录

小球撞到四条边就反弹,按 BOOT 暂停或继续。用 millis() 控节奏,擦除时连半径一起算。

06 动画 作品