做少儿编程教学和课程内容这几年,Scratch 3 里被问得最多的两类项目,一个是山地足球,一个是射击游戏。前者考的是物理直觉和坐标系,后者考的是克隆体管理和状态机,看着八竿子打不着,但我一直把它们放在同一节课或者同一个系列的第一、第二讲里讲,原因很简单:这两个项目合起来,刚好把 Scratch 3 里最难讲清楚的两块——变量驱动的运动模拟和克隆体的生命周期管理——用孩子能听懂的方式讲完。你如果是刚接手编程社团课的老师,或者想自己带孩子在家玩一玩,又或者是刚接触 Scratch 想找个能落地的练手项目,这篇基本上能当成一份可以直接照着做的施工笔记。山地足球我做过滚动球、弹跳球、双人对踢三个版本,射击游戏我做过固定靶、移动靶、限时挑战三个难度梯度,中间踩过的坑一个没少踩,下面按我实际的搭建顺序把细节全摊开。
1. 为什么把这两个项目凑成一组来练
1.1 山地足球真正训练的是"用变量描述运动"
足球这个角色在舞台上看起来会滚、会弹、会减速,但 Scratch 本身没有任何物理引擎,它所有的"真实感"都是一帧一帧算出来的。所以我教山地足球的第一个动作,不是让孩子去画造型,而是先建两个变量:vx和vy。这两个名字看着专业,其实就是"这一帧球往右挪多少"和"这一帧球往上挪多少"。
一旦你接受了这个设定,后面所有事情都顺了:重力就是每帧给vy减一个固定值,弹跳就是碰到地面时把vy反向再乘个系数,摩擦就是每帧给vx乘个 0.98 之类的数。孩子第一次看到自己调出来的球在地上弹三下停住,那种"我控制了物理"的表情,比讲十遍坐标变换都管用。
山地足球之所以比平地足球值得做,是因为地形一变,判定逻辑就必须升级。平地你只要判断"碰到舞台下边缘",山地你要判断"碰到哪一层的山坡",这中间就会自然引出"用颜色判定""用列表存高度""用函数算高度"三种完全不同的实现路径。这三条路径的取舍,是我这个项目里信息密度最高的部分,后面单独拆。
1.2 射击游戏真正训练的是"克隆体的一生"
射击游戏的表面是"发射—命中—得分",底层其实是三个问题:怎么同时存在很多个弹丸、每个弹丸怎么独立运动、弹丸什么时候该消失。这正好对应 Scratch 3 的克隆体机制:创建、独立执行、删除。
我见过太多孩子写出来的版本是:弹丸只有一个角色,按一下发射,它就飞出去,飞出舞台边缘停了,再按一次,它从原地又飞一次——因为他没删克隆体,也没重置位置。这种 bug 不是粗心,是他脑子里没有"每个克隆体是一个独立的小生命"这个概念。
所以我讲射击游戏,一定会花整节课讲克隆体的"出生—工作—死亡"三段式:出生时把私有变量(速度、方向、寿命)设好,工作时每一帧更新自己的坐标并检查是否该结束,结束时第一件事就是"删除此克隆体"。这个习惯一旦养成,后面做飞机大战、弹幕躲避、粒子特效,全部一通百通。
1.3 两个项目共用的底层能力
别看一个是球一个是靶,它们共用的东西比想象中多:
| 能力点 | 山地足球里的体现 | 射击游戏里的体现 |
|---|---|---|
| 坐标系与方向 | 球的 x、y 与速度分解 | 弹丸朝目标方向发射 |
| 每帧循环更新 | 重力、摩擦、位置累加 | 弹丸位置累加、出界判定 |
| 碰撞/命中判定 | 球与地面、球与球门 | 弹丸与靶子 |
| 状态变量 | 比分、球的位置、速度 | 得分、剩余时间、靶子血量 |
| 事件广播 | 进球后重置球、播报 | 开始、暂停、游戏结束 |
| 难度参数化 | 重力、弹性、摩擦 | 靶速、发射冷却、靶子数量 |
我把这张表打印出来贴在教室墙上,孩子做完第一个项目,第二个项目就能自己对着表去找"这个我学过"。教学效率提升非常明显。
2. 山地足球:地形是最大的变量
2.1 角色与舞台的划分方式
我的角色表很固定,五个角色:球员、足球、球门左、球门右、山坡。舞台背景只用一张纯色,所有地形都画在山坡这个角色上,原因是 Scratch 里"碰到某个角色""碰到某个颜色"两种判定各有脾气,把地形集中在一个角色上,后面切换判定方式时改动最小。
坐标基准一定要先定死,不然后面全乱。Scratch 3 舞台是 480×360,中心是 (0,0),x 从 -240 到 240,y 从 -180 到 180。我要求球员的脚底大概落在 y = -120 这条线上,球门高度 100 像素左右,球门宽度 60 像素。这些数字没有绝对标准,但必须全组统一,因为球门判定要用到具体坐标值。
造型方面有个小经验:足球不要画得太圆。太圆的球在滚动时看不出旋转,孩子会觉得"球没动"。给它画上黑白块或者几道条纹,再用"旋转方式"配合移动距离做角度变化,视觉上立刻就有滚动的感觉。这一步是纯美术收益,不改任何逻辑,但对成品的观感影响巨大。
2.2 山地地形的三种实现路径怎么选
这是整个项目最值得掰扯的部分。三种做法我都实做过,各有明确的适用场景。
第一种,颜色判定法。山坡角色画成深绿色,足球的循环里写"如果碰到颜色深绿,那么 y 坐标增加 1,vy 设为 vy × -0.7"。优点是零基础也能五分钟搭出来,缺点是它本质上是"试探式"的:球掉进山坡,往上顶一像素,再判断,还陷着就再顶,一帧里可能要顶十几次,球在斜坡上会明显"抖"。而且球速一快,一帧位移超过山坡厚度,就直接穿过去了。
第二种,列表存高度法。建一个列表地面高度,长度 481,第 n 项存 x = n - 241 处地面的 y 坐标。球的每一帧只做一件事:如果 y坐标 < 地面高度的第 (x坐标 + 241) 项,就修正位置并把 vy 反向。这个方法的判定是瞬时的、精确的,不会有抖动,也不会有穿透(只要山坡不是垂直的)。代价是要提前生成这张表,孩子得理解"列表的第几项对应屏幕的哪一列"这个映射关系。
生成表的代码可以这样写,用一个隐藏的"画笔"角色跑一遍:
当绿旗被点击 删除 [地面高度 v] 的全部项目 将 [i v] 设为 -240 重复执行 481 次 将 [h v] 设为 (120 * sin( (i / 60) ) + 40 * sin( (i / 25) ) - 40) 将 (h) 加入列表 [地面高度 v] 将 [i v] 增加 1这里的sin叠加是最省事的山形生成思路:低频正弦给大起伏,高频正弦给小坑洼,两条一叠加就是很自然的丘陵。参数120和40是振幅,60和25是波长,改波长地形变陡,改振幅地形变高。我一般让孩子先把120调到 200 感受一下"悬崖",再调回来,他对振幅的理解就固化了。
第三种,数学函数法。不存列表,每帧直接用同样的sin公式算当前 x 处的地面高度。省内存,但对于每一帧都要算两次三角函数,而且孩子调试时看不到"地形长什么样",容易懵。我的建议是:教学用第二种,做成品的游戏用第三种。
三种路径的对比我整理成表,方便你直接照抄决策:
| 方案 | 实现难度 | 斜坡稳定性 | 高速穿透风险 | 适合场景 |
|---|---|---|---|---|
| 颜色判定 | 低 | 差(有明显抖动) | 高 | 第一节课、低年级体验 |
| 列表高度表 | 中 | 好 | 无 | 常规教学项目、双人对战 |
| 数学函数 | 中高 | 好 | 无 | 追求性能、地形要动态变化 |
注意:颜色判定法里那个"y 坐标增加 1"的修正量千万别设大,设成 3 以上球会像踩了弹簧一样在坡上跳。1 到 2 之间是甜区。
2.3 重力、弹性、摩擦这三个参数怎么调
这三个数决定了球的"手感",也是孩子最爱调的三个数。我给的经验值范围是这样的:
| 参数 | 变量名 | 建议范围 | 调大的效果 | 调小的效果 |
|---|---|---|---|---|
| 重力 | 重力 | 0.4 ~ 1.2 | 球下坠快、弹跳低、像铅球 | 球飘、像月球 |
| 弹性 | 弹性 | 0.5 ~ 0.85 | 弹得高、停不下来 | 落地就趴、像沙包 |
| 摩擦 | 摩擦 | 0.95 ~ 0.995 | 滚得远、滑 | 很快停住、像草地 |
具体在循环里就这么几行:
当绿旗被点击 将 [vx v] 设为 0 将 [vy v] 设为 0 重复执行 将 [vy v] 增加 (-1 * 重力) 将 [x坐标 v] 增加 (vx) 将 [y坐标 v] 增加 (vy) 将 [vx v] 设为 (vx * 摩擦) 如果 <(y坐标) < (地面高度 的第 ((x坐标) + 241) 项)> 那么 将 y坐标 设为 (地面高度 的第 ((x坐标) + 241) 项) 如果 <(vy) < -0.5> 那么 将 [vy v] 设为 (vy * (-1 * 弹性)) 否则 将 [vy v] 设为 0 结束这里有个细节值得单独说:如果 vy < -0.5 才反弹,否则直接归零。这一行是"球最终会停下"的关键。如果不加,球会在vy很小的正负值之间无限抖动,肉眼看着像在地上抽搐。阈值 0.5 是我试出来的,太小了球停不下来,太大了球落地就没弹性。你也可以用 0.6 或者 0.4,看手感。
摩擦为什么用乘法不用减法?因为乘法是"按比例衰减",无论球滚多快,减速的比例是一致的,看起来自然;减法会让高速球减速不明显、低速球瞬间停死,很不自然。这是个小知识点,但讲清楚了孩子对"指数衰减"会有直观印象。
2.4 碰撞判定:为什么不建议全用"碰到颜色"
球员踢球这个动作,最省事的写法是"如果碰到足球,就把球的速度设成某个值"。但这么写有两个毛病:一是球员站着不动、球滚过来撞他,球也会被"踢"出去;二是踢球的方向永远是固定的,不管从哪个角度碰。
我的做法是:碰撞只负责"接触检测",方向由两者的位置差决定。具体是判断球员和球的 x 坐标差,球在球员右边就向右踢,在左边就向左踢,y 方向给一个固定的上抛初速度。这样视觉上就有"从球下方挑起来"的感觉。
如果 <碰到 [足球 v] ?> 那么 如果 <(足球的 x坐标) > (x坐标)> 那么 将 [vx v] 设为 (8 + abs(球员速度)) 否则 将 [vx v] 设为 (-8 - abs(球员速度)) 结束 将 [vy v] 设为 98和9这两个初速度是我调了很久的结果:vx太小球飞不远,太大了直接飞出舞台;vy太小挑不起来,太大了球飞过球门顶。配合重力 0.8,球大概能飞 1.2 秒、跨越 200 像素左右,正好够从场地中间打到球门。
球门判定就简单了,用坐标范围判断比"碰到球门角色"更可靠:
如果 <<(x坐标) > 200> 与 <(y坐标) < (球门顶)> 与 <(y坐标) > (球门底)>> 那么 将 [左队得分 v] 增加 1 广播 [进球 v]为什么要用坐标而不是碰角色?因为球门角色通常会被画得比可视范围大一点(防止球从边缘擦过去),而且球门角色和球同时移动时,"碰到"判定在极端速度下会漏检。坐标判定是数学上确定的,不会有意外。
实操心得:调试球门的时候,先把球门的判定范围画成半透明矩形显示出来,能看清边界。孩子调完参数经常出现"球明明进了却没算分",九成是判定框和视觉边界差了十几个像素。
3. 射击游戏:把克隆体的"一生"讲透
3.1 弹丸的角色设计与私有变量
先明确一点,我这里的"射击"指的是靶场打靶:屏幕上有移动的圆形靶子,玩家控制发射器发射小球,打中得分。这是编程教学里非常经典的一类玩法,重点全在逻辑,跟任何现实内容无关,家长也放心。
角色三个:发射器、弹丸、靶子。弹丸这个角色是克隆体工厂,它本身在舞台上不显示(初始隐藏),只在需要时创建克隆体。
关键一步,也是最容易被忽略的一步:在弹丸角色里建私有变量寿命、飞行方向、飞行速度,并且一定要勾上"仅适用于当前角色"。勾上之后,每个克隆体都有自己独立的一份,互不干扰;不勾,所有克隆体共用一个值,飞出去的小球会集体拐弯,孩子看到这现象会一脸懵。
3.2 弹丸的三段式生命周期
我把弹丸的逻辑固定成一个模板,学生照着填:
当 [空格 v] 键被按下 如果 <(冷却计时) > 8> 那么 生成 [弹丸 v] 的克隆体 将 [冷却计时 v] 设为 0 当作为克隆体启动时 将 [寿命 v] 设为 0 将 [飞行速度 v] 设为 (10 + (得分 / 20)) 移到 [发射器 v] 面向 (方向) 显示 重复执行 移动 (飞行速度) 步 将 [寿命 v] 增加 1 如果 <<<(y坐标) > 175> 或 <(y坐标) < -175>> 或 <(寿命) > 120>> 那么 删除此克隆体 结束这里的移动 10 步配合面向方向是 Scratch 里最省事的方向运动方案,不用自己算 sin/cos。但如果你想让孩子理解三角函数,可以改成手动分解:
将 x坐标 增加 ((飞行速度) * (sin (方向))) 将 y坐标 增加 ((飞行速度) * (cos (方向)))注意 Scratch 的方向 0 度是朝上、90 度是朝右,跟数学课本上的角度是顺时针转 90 度再镜像的关系。这个"错位"一定要提前讲,不然孩子按课本公式算出来的弹道会是歪的。我一般让学生先做实验:设方向为 90,用两组公式各跑一遍,看哪组往右飞,五分钟就能记住。
寿命这个变量的作用是兜底。理论上弹丸飞出舞台就会被 y 坐标判定清掉,但如果方向正好水平,它可能贴着上下边缘一直飞,或者因为某些 bug 原地不动,克隆体就会越积越多,最后撞上 300 个的上限,新弹丸直接不出来了。加一个寿命 > 120的强制清理,等于给系统上了保险。
3.3 命中判定:距离法比"碰到"更稳
弹丸打靶子,最直觉的写法是"如果碰到靶子,就加分、删弹丸"。能跑,但有两个坑:
第一,碰到判定是矩形包围盒的近似。靶子画成圆形,它的包围盒是方的,四个角上有"打不中却算中"的误判,孩子会觉得"我明明没打到"。
第二,克隆体之间的碰到判定在极端情况下会漏。弹丸速度设到 15 以上,一帧跨过 15 像素,靶子直径只有 40 像素时,理论上还是能检到,但如果靶子也在同方向移动,相对速度变小反而更容易叠加,实际测试中出现过偶发漏判。
我统一改成距离判定:
如果 <(sqrt (((x坐标) - (靶子x)) * ((x坐标) - (靶子x)) + ((y坐标) - (靶子y)) * ((y坐标) - (靶子y)))) < 28> 那么 广播 [命中 v] 删除此克隆体28 这个数等于靶子半径 20 加上弹丸半径 8,是两个圆真正相切的距离,比包围盒精确得多。代价是要把靶子的坐标写到全局变量靶子x、靶子y里,因为克隆体读不到别的克隆体的私有变量。这一步是我觉得整个项目里最"工程"的地方:信息要靠全局变量传递,不能靠"感觉"。
如果靶子不止一个,就建两个列表靶x、靶y,主循环里实时更新,弹丸判定时遍历列表。这个写法稍微复杂,但对理解"数据结构"非常有帮助,我一般放在进阶任务里。
3.4 得分、难度曲线与游戏节奏
一个只有"打中加分"的游戏,玩两分钟就腻。我给它加了三层机制:
第一层,连击。记录"连续命中次数",每命中一次加一,脱靶(弹丸飞出且未命中)清零,连击数超过 5 时每次得分翻倍。这个机制让玩家的操作有了"风险收益",也让代码里多了个值得讲的分支逻辑。
第二层,靶子加速。得分每增加 50,靶子的移动速度加 0.5。写成靶子速度 = 2 + (得分 / 100)这种公式,孩子一眼能看懂"分数越高越难"是怎么实现的。
第三层,限时。用计时器做 60 秒倒计时,时间到广播结束,结算画面显示总分和最高连击。计时器用 Scratch 自带的"计时器"最简单,但要注意它只能读不能设,重置要用"计时器归零"积木。如果要做暂停功能,就得自己用变量每帧加 1/30,不能用自带计时器,因为归零会丢掉已用时间。
难度梯度我是这样排的,你可以直接拿去当关卡设计表:
| 关卡 | 靶子数量 | 靶子速度 | 发射冷却 | 目标分数 |
|---|---|---|---|---|
| 入门 | 1(固定不动) | 0 | 12 帧 | 10 分 |
| 简单 | 1(左右匀速) | 2 | 10 帧 | 20 分 |
| 中等 | 2(上下左右) | 3 | 8 帧 | 40 分 |
| 挑战 | 3(随机变速) | 4 + 得分/200 | 6 帧 | 60 分 |
冷却帧数越大,发射越慢,越难。8 帧在 30fps 下大概是 0.27 秒一发,已经是比较快的节奏了,6 帧基本是连续扫射,适合做"爽快感"演示。
4. 从零到跑起来的完整搭建流程
4.1 前期统一规范,能省掉一半的沟通成本
不管是一个班十几个孩子,还是你自己一个人做,开工前把这几个约定定死:
- 变量命名统一:
vx、vy、重力、弹性、得分、生命,不要一会儿speed一会儿速度。 - 全局变量和私有变量分清:得分、时间、游戏状态做全局;每个对象自己的速度、寿命做私有。
- 角色命名带前缀:
足_球员、足_足球、射_弹丸,以后角色多了不至于找瞎。 - 所有角色初始状态集中在"绿旗被点击"里:位置、方向、大小、是否显示,全部显式设置一遍。不要依赖"上一次运行留下的状态",这是新手最常见的"明明刚才还好好的"来源。
4.2 山地足球的搭建步骤
第一步,搭静态场景。画一个山坡角色,铺满下半屏;左右各放一个球门;球员放在中间偏左。这一步不写任何代码,只调位置,让孩子先有"场地感"。
第二步,让球动起来但不受重力影响。只写将 x坐标 增加 vx和将 y坐标 增加 vy,手动把vx设为 5,看球往右匀速飞。这一步的目的是让孩子理解"位置是靠累加改变的",而不是"设到某处"。
第三步,加重力。每帧vy 增加 -0.8,球会开始走下抛物线。此时不加地面判定,球会掉出舞台,正好用来讲"什么是边界"。
第四步,加地面判定。先用最简单的"碰到舞台下边缘就反弹",验证弹跳逻辑对不对,再升级成山坡判定。
第五步,加球员控制。左右方向键改变球员 x 坐标,空格跳跃(vy赋初值 + 每帧减重力)。跳跃这一段可以复用球的逻辑,孩子会有"这个我写过"的成就感。
第六步,加踢球判定和进球计分。这里开始引入广播:进球后广播进球,球和球员收到广播后回到初始位置,比分变量加一。广播比"直接改对方位置"好在解耦,后面加音效、加特效都只是多一个接收者。
第七步,调参。这是最花时间也最有意思的一步,让学生两两交换作品试玩,互相提"球太轻了""坡太陡了",然后自己回去改数字。我通常给 15 分钟专门做这件事。
4.3 射击游戏的搭建步骤
第一步,先把发射器做好。让它跟随鼠标 x 坐标或者用左右键移动,限制在舞台底部。这一步很简单,但一定要先做,因为它是所有方向的基准。
第二步,做一个"只能发射一次"的版本。不加克隆体,就一个弹丸角色,按空格把它移到发射器位置、显示、然后每帧移动。孩子会很自然地问"为什么只能打一发",这个问题就是引入克隆体的最佳时机。
第三步,改成克隆体。加"生成克隆体"和"作为克隆体启动时",把移动逻辑搬进去。第一次跑的时候一定会出现"弹丸停在原地不动",原因是忘了在克隆体启动时设置初始位置,或者忘了显示。这两个 bug 我每次都让学生自己找,找到一次记一辈子。
第四步,加边界清理。出界就删除克隆体,这一步做完,连发就没问题了。
第五步,加靶子。靶子先用克隆体做左右移动,碰到边缘就反弹一行搞定。然后把靶子坐标写进全局变量。
第六步,加命中判定和得分。用距离法,把公式摊开讲一遍,让孩子自己算一遍 28 是怎么来的。
第七步,加难度递增和结算。把速度公式、冷却公式、倒计时分别加上,最后一个广播把所有角色叫停。
4.4 联调阶段最容易出问题的三个地方
图层顺序。弹丸必须显示在靶子之上,山坡必须在球的后面,球门要在球的前面(视觉上球进门的瞬间会被门柱挡一下,更有立体感)。Scratch 的图层顺序在编辑器里调一次就行,但如果用了克隆体,新克隆体会盖在最上层,这时候要靠"下移几层"来控制。
广播的时序。广播和广播并等待是两回事。进球后如果直接广播 进球,球会在同一帧继续执行后面的代码,可能又触发一次进球判定,比分一次加两分。正确做法是广播之后立刻停止这个脚本,或者用广播并等待。
变量重置的时机。绿旗点击时把所有变量清零,这一步千万别忘。我见过太多"第一局正常,第二局分数接着上一局"的案例,原因就是得分变量没在开始时归零。
5. 常见问题与排查实录
5.1 球或弹丸的速度跟设备有关
Scratch 3 的循环默认跑在 30fps 左右,但实际帧率会随设备性能浮动。如果你在循环里写"每帧移动 5 步",在性能不同的设备上速度会有肉眼可见的差异。
解决办法是做时间归一化,用计时器算每帧的真实间隔:
将 [上一时刻 v] 设为 (计时器) 重复执行 将 [dt v] 设为 ((计时器) - (上一时刻)) 将 [上一时刻 v] 设为 (计时器) 将 x坐标 增加 ((vx) * (dt * 30))dt * 30的意思是"把这一帧的实际时长折算成标准帧数"。设备快,dt 小,移动少;设备卡,dt 大,移动多。整体速度就一致了。
这个技巧在弹丸上效果不明显(因为弹丸寿命短),但在球上非常必要,尤其是双人对战的时候,两个孩子的设备不一样,球速不同就完全没法玩。
5.2 克隆体数量爆炸
现象:玩了几十秒以后,弹丸突然不发射了,或者游戏明显变卡。
原因基本就一个:克隆体没有被删除,累积到 300 个上限。Scratch 3 对克隆体总数的限制是 300,超过之后"生成克隆体"积木直接不生效,而且不报错,非常隐蔽。
排查方法是做一个调试面板,用一个变量实时显示克隆体数量,每生成一个加一,删除时减一。如果数字一直涨,就在所有"删除此克隆体"的地方逐个检查,看是不是漏了哪个分支。
容易漏的典型分支:
| 漏删场景 | 表现 | 修法 |
|---|---|---|
| 水平飞行出界 | 弹丸贴着边缘不消失 | 加 x 坐标的边界判断 |
| 命中后没删 | 打中的弹丸卡在靶子上 | 命中分支里加删除 |
| 游戏结束后 | 重新开始后克隆体还在飞 | 广播结束时统一清空 |
| 暂停时 | 弹丸停在原地不减 | 暂停时也继续执行寿命判断 |
统一清空的写法是:在弹丸角色里加一个"当接收到 游戏结束"的脚本,重复执行直到 克隆体数量 = 0,删除此克隆体。注意要循环删,因为一个脚本只能删掉自己那一份。
5.3 球在山坡上抖动或者穿模
抖动的成因已经说过,是颜色判定法的"试探式修正"。如果你的项目必须用颜色判定(比如山坡是孩子手绘的、没法用函数表达),可以加阻尼:每次修正时把vy乘以 0.5 而不是直接反向,抖动会小很多。
穿模(球穿过山坡掉出去)的成因是一帧位移超过山坡厚度。解法有两个,一是限制最高速度,二是做"分步移动"——把一帧的位移拆成若干个不超过 5 像素的小步,每步都判断一次。
将 [步数 v] 设为 (ceiling ((abs (vy)) / 5)) 重复执行 (步数) 次 将 y坐标 增加 ((vy) / (步数)) 如果 <(y坐标) < 地面高度 的第 ((x坐标) + 241) 项> 那么 修正位置 停止这个脚本 结束这个"分步移动"的思想在游戏开发里叫连续碰撞检测,孩子现在不需要记这个名字,但见过一次,以后遇到高速物体穿墙就有直觉了。
5.4 常见问题速查表
| 现象 | 最可能的原因 | 快速验证方式 |
|---|---|---|
| 球落地不停抽搐 | 反弹阈值太小 | 把 0.5 改成 1 |
| 球滚得停不下来 | 摩擦系数接近 1 | 改成 0.97 试 |
| 球踢出去没反应 | 变量作用域搞错 | 检查 vx 是不是私有变量 |
| 弹丸原地不动 | 克隆体启动时没设位置 | 加"移到发射器" |
| 打中不加分 | 广播接收者没写 | 在靶子里加显示分数的调试 |
| 第二局分数接着涨 | 开始时没清零 | 绿旗里统一归零 |
| 靶子被弹丸推着走 | 靶子也在接收命中广播 | 命中逻辑只放在弹丸里 |
| 音效叠成一片 | 每个克隆体都播声音 | 只让主角色播 |
提示:调试克隆体问题时,把"克隆体数量"做成一个大字号显示在角落,能解决八成的玄学问题。
6. 教学递进与后续扩展的几条路子
6.1 按能力分层的任务设计
同一个项目,不同水平的孩子做出来的东西差距会很大。我一般准备三层任务卡,让每个人都能找到合适的难度:
基础层:球能滚、能弹、能被踢,山坡用颜色判定。射击游戏能发射、能打中、能加分。这一层重点是"跑通",不求完美。
进阶层:要求加两个功能,任选。比如足球加"风"(每帧给 vx 加一个随机小量)、加"双人模式"(两套按键、两个球门);射击游戏加"连击"、加"移动靶"、加"三关难度"。
挑战层:足球加"斜坡加速"(下坡时 vx 自动增加)和"球旋转角度与速度关联";射击游戏加"弹道预测线"(发射前画一条虚线显示弹丸会飞到哪)和"排行榜"(用列表存历史最高分)。
"弹道预测线"这个任务我自己做过,用的是同样的运动公式跑一个透明虚线的克隆体,飞一段距离后停下。做完之后孩子对"公式预测未来"这件事会有非常直观的感受,比讲一百遍函数都强。
6.2 从这两个项目能延伸出去的方向
山地足球往下走,很自然能接到"平台跳跃"类型——把球换成人,山坡换成平台,重力系统直接复用。射击打靶往下走,能接到"弹幕躲避"——把发射者和靶子互换角色,从"我打别人"变成"我躲别人",核心的克隆体管理完全一样。
再往上一层,两个项目都能接到"关卡系统":用列表存每一关的参数(重力、速度、目标分),切换关卡时读取对应行。这一步做完,孩子就理解了"数据驱动"的基本思路——游戏的行为不是写死在代码里,而是从数据里读出来的。
我自己最喜欢的一个扩展是给足球加"回放":把球每一帧的 x、y 坐标存进两个列表,进球后重新播放一遍轨迹。这个功能技术上不难,但视觉效果非常好,而且能让孩子第一次体会到"记录状态"的价值。
最后分享一个我在实际带项目中反复验证过的体会:这两个项目真正的分水岭不在代码量,而在参数调试的那 15 分钟。代码照着抄谁都能跑起来,但让球弹得好看、让打靶打得爽,全靠一个个数字去试。所以我从来不把"完成作品"当成结束,而是留出时间让学生互相玩、互相吐槽、回去改。那些改过五六版参数的孩子,下次遇到新的运动类项目,第一反应就是"我先建两个速度变量",这个直觉,比记住任何一块积木都有用。