Scratch条件执行语句详解:从布尔判断到状态机实战

发布时间:2026/11/3 16:26:15
Scratch条件执行语句详解:从布尔判断到状态机实战

1. 条件执行语句到底在解决什么问题

在 Scratch 控制模块里,条件执行语句是绝大多数作品从"能动"跨到"会思考"的分水岭。我见过太多初学者,角色能走能跳、能跟着鼠标跑,但一碰到"碰到敌人扣血""分数到 100 才开门""按住上键才能跳"这类需求就卡住——不是不会拖积木,而是脑子里那套"从上往下一条线走到底"的执行模型,和条件语句要求的"走到岔路口先看一眼再决定往哪走"完全对不上。这篇文章就把 Scratch 条件执行语句这套东西讲透:它是什么、三种形态怎么选、布尔条件怎么写、嵌套和否则如果怎么搭、性能上有什么讲究、出问题怎么查。适合刚接触控制模块的新手,也适合已经能做完整小游戏、但条件逻辑写得又长又乱、改一处崩一片的中级玩家。

Scratch 里的条件执行语句,本质上就是三块积木的组合:如果 <条件> 那么、如果 <条件> 那么 … 否则,以及配套的等待 <条件>和重复执行直到 <条件>。它们的共同点是——积木里那个六边形的凹槽,只能塞进一个"是/否"的问题。这个"是/否"就是布尔值,它不返回数字、不返回文字,只返回真或者假。理解了这一点,条件语句的 90% 困惑就自动消解了:不是你在写"如果分数大于 100 就开门",而是你在问"分数大于 100 吗",然后系统回答真或假,真就走里面的积木,假就跳过去。

为什么条件执行语句值得单独拿出来讲?因为它是 Scratch 里唯一能把"状态"转成"行为"的通道。变量、侦测、计时器这些都是原料,条件语句是那个开关。没有它,角色只能按脚本写好的顺序机械播放;有了它,角色才能在每一帧重新判断"我现在该干什么"。这背后其实是游戏开发里最朴素也最重要的东西——状态机。一个能上台面的 Scratch 作品,骨子里都是一堆条件在打架,谁的判断写在前面、谁的判断嵌套了几层、谁的判断每帧都在重复执行,直接决定作品是丝滑还是卡顿、是稳定还是诡异。

我在帮人看作品时有个习惯:先不看美术,直接打开角色脚本,数一数它有几个"重复执行"套着"如果"。如果屏幕上半部分是几十个平铺的"如果",下半部分是乱成一团的嵌套,那这个作品后期基本改不动了。条件语句写得好不好,看的不是能不能跑起来,而是三个月后你还敢不敢动它。

1.1 条件执行语句和顺序执行的本质差别

顺序执行就像照着菜谱做菜:切菜、下锅、翻炒、装盘,一步一步来,中间不会因为你今天心情不好就跳到装盘。Scratch 里没有条件语句的脚本就是这样,绿灯一点,从头跑到尾,跑完就停。问题是现实需求几乎全是带分支的——按下空格才跳,没按下就站着;碰到地面才允许跳,空中再按不算;血量归零才结束,没归零就继续。

条件执行语句做的事,是在这条流水线上插了一个岔路口。程序走到岔路口,先算一遍条件,算出真就从"那么"里穿过去,算出假就整个跳过"那么"里的内容继续往下。注意"跳过"这两个字很关键:条件为假时,里面的积木是完全不执行的,不是执行了但没效果,是压根没进去。这一点解释了新手最常见的疑惑——"我在如果里面改了变量,为什么没变?"因为条件压根没成立,那行改变量的代码就没跑过。

我做过一个对比演示给学员看:同样一个角色,用顺序执行写"前进 10 步、碰到边缘就反弹",再换成条件执行写"如果碰到边缘 那么 反弹"。前者在角色走到边缘时会先撞出去再被拉回来,动作是卡顿的;后者在撞到边缘的同一帧就完成判断并转向,看起来顺滑得多。差别不在积木数量,在于判断发生的时机和位置。

1.2 三类条件积木的形态差异与选用原则

很多人把这几块积木混着用,其实它们的分工非常明确,选错了就会出现"只执行一次"或"每一帧都重复触发"的毛病。我把它们摆在一起对比一下:

积木形态执行方式典型场景用错的后果
如果 <条件> 那么单次判断,逐帧由外层循环驱动碰撞检测、按键检测、状态刷新放在没有循环的脚本里只会判一次
如果 <条件> 那么 … 否则单次判断,二选一开关切换、成功/失败分流两个分支都写相同内容等于白写
等待 <条件>阻塞式,条件不成立就一直卡在这行等待点击开始、等待扣血动画播完用了它,后面所有脚本全部暂停
重复执行直到 <条件>循环式,条件不成立就反复跑里面蓄力条、冷却倒计时、按住加速条件写成永假会死循环,作品卡死

选用的核心判断只有一句话:这件事你要判一次,还是要一直判?判一次用"如果",一直判用"如果"套在"重复执行"里,或者干脆用"重复执行直到"。而"等待"是这三种里最容易用出事故的——它是阻塞的,一个角色卡在"等待 碰到 敌人",它的其他脚本还能跑,但这个脚本里"等待"之后的所有内容全部静止。我见过有人把整个游戏主循环都塞在"等待 鼠标按下"后面,结果游戏画面彻底冻住,还以为是自己电脑卡。

1.3 条件背后的布尔表达式:六边形凹槽里能放什么

说清楚条件语句,必须顺带说清楚那个六边形凹槽里到底能塞什么。Scratch 把它限定得很死,只能放返回真/假的积木,大致分四类:

  • 比较类:< >、< = >,可以比数字,也可以比文字(按字典序),左边右边都能嵌变量和运算积木。
  • 逻辑类:< > 与 < >、< > 或 < >、< 不成立 >,用来把多个条件拼起来。
  • 侦测类:碰到 <角色> ?、碰到颜色 <颜色> ?、颜色 <颜色> 碰到 <颜色> ?、按下 <键> 键?、按下鼠标?、响度 > <数值>、计时器 > <数值>、距离 <角色> <数值>等等。
  • 状态类:任何放在< >里的变量,配合比较积木使用。

这四类有一个共同特征:它们问的都是"现在这一刻"的事实,不记录历史。所以<分数 > 100>这条判断,在分数从 100 变成 101 的那一帧成立、之后一直成立,不会自己"触发一次就失效"。这就是为什么"过关"这种一次性事件必须自己加锁——用一个已过关变量做闸门,过了就置为真,条件写成<分数 > 100> 与 <已过关 = 假>,才能保证只触发一次。这个技巧后面还会细说,先记住结论:条件语句本身没有记忆,记忆得靠变量补。

2. 条件积木的核心细节与易错点拆解

真正把条件语句写崩的,往往不是大逻辑,而是一些小细节:比较运算的类型转换、字符串和数字的混用、逻辑运算的分组顺序、那个不起眼的"否则"该不该写。这些问题在积木层面上看不太出来,跑起来才发现"它怎么黑屏了""它怎么一直是对的"。这一章把这些细节拆开讲,都是我在实际带人和改作品时反复碰到的。

2.1 比较运算的隐形类型转换

Scratch 的比较积木相当"宽容",<"10" > "9">这种写法它就当数字比,得 10 > 9 为真;但如果你不小心把它写成了<"abc" > "9">,它就会退化成字符串比较,按字典序逐位比,结果会让你莫名其妙。更隐蔽的是空字符串和 0 的关系:变量没赋值时是空字符串,<变量 = 0>在很多情况下居然是真的,因为空串被转成了 0。我曾经帮人查一个"开局血量直接归零"的 bug,查了半小时,最后发现是血量变量在初始化之前就被拿去比较了,而那一帧刚好又触发了扣血条件。

我的习惯是:任何参与数值比较的变量,在作品开始的第一帧必须显式初始化。用绿旗事件下面第一件事就是"将 变量 设为 0",一个都不能漏。这看起来是废话,但十次数值错乱里有六次是这个原因。另外,需要比较小数的时候要特别小心浮点误差,0.1 + 0.2不等于0.3这种事在 Scratch 里一样存在。所以涉及金币数量、生命值这类整数,尽量保证运算全程是整数;涉及坐标比较,别用<x坐标 = 100>,用<x坐标 > 99.5 与 x坐标 < 100.5>这种区间判断,稳得多。

2.2 逻辑运算的组合与分组陷阱

与、或、不成立这三块积木是可以互相嵌套的,Scratch 没有提供括号,只能靠物理嵌套来表达优先级。麻烦在于,与和或混在一起时,人的直觉理解往往和实际执行不一致。比如<A> 或 <B> 与 <C>,很多人的本意是"(A 或 B)并且 C",但如果你是把<B> 与 <C>塞进了或的右边,实际执行就是"A 或(B 且 C)",一个括号的差别,逻辑完全变了。

规避方法很土但很有效:混用与/或的时候,先把最内层的条件拼好,再一层层往外套,套完读一遍成句的中文。如果读出来和你想表达的不一样,就重搭。另外,不成立这块积木不要滥用,<不成立 <碰到 敌人>>实际上是可读的,但<不成立 << A > < B > 或 < C > < D >>>这种三层往里塞的,我自己写完第二天都看不懂。遇到复杂条件,正确做法是拆成多个"如果",或者用变量先把中间结果存下来,给变量起个像样名字,比死磕嵌套强得多。

2.3 那个经常被忽略的"否则"

新手写条件,几乎只用单分支的"如果 那么",遇到相反的情况就再写一个"如果 那么",把条件取反。这么写能用,但有两个隐患:一是条件一旦写错,两个分支可能同时不成立,角色进入一个谁都没处理的"黑洞状态",卡在那里不动;二是两个条件如果都成立(比如你忘了取反),两块积木会连着跑,出现抖动。

双分支的"如果 那么 否则"天然保证了二选一,永远有一条路被走到。我在写角色状态切换时,几乎全部用双分支:如果 <速度 > 0> 那么 面向 0 度 否则 面向 180 度。这种结构无论速度是多少,角色都一定有一个朝向,不会出现"贴图消失"这种诡异现象。所以我给自己的规矩是:只要存在明确的两种可能,一律用"否则",不留空白分支。代价是多拖一块积木,收益是排除了一个整类的状态遗漏 bug。

2.4 "否则如果"链的手工搭建方式

Scratch 原生没有"否则如果",多分支只能自己搭。最标准的做法是把下一层"如果"塞到上一个"否则"里面,形成一个阶梯状的嵌套。以分数评级为例:

如果 <分数 >= 90> 那么 将 等级 设为 "A" 否则 如果 <分数 >= 75> 那么 将 等级 设为 "B" 否则 如果 <分数 >= 60> 那么 将 等级 设为 "C" 否则 将 等级 设为 "D"

这个结构之所以成立,靠的是"从高到低依次判断,命中即停"。因为一旦第一层为真,就不会进否则分支,后面的判断全部跳过。所以顺序绝对不能乱——如果先判>= 60,那 95 分也会落到 C,因为第一层就命中了。这个坑我见过太多次,尤其是中途改阈值的时候,只改数字不改顺序,测试用的分数又刚好避开边界,上线就出事。

阶梯嵌套的视觉问题是很深,四层下去积木就缩成一条线了。我的处理办法是控制在三层以内,超过三层就改用变量或自制积木拆开,或者干脆把判断写在不同的角色、不同的脚本里。可维护性这件事,在 Scratch 里同样是真金白银。

3. 从零搭建一套可复用的条件控制实战案例

光讲原理容易飘,我拿一个最简单的横版小场景来把条件语句跑一遍:一个角色左右移动、跳跃落地、撞刺扣血、吃钥匙开门、血量归零结束。这个场景不复杂,但几乎覆盖了条件语句所有常见用法,把它的骨架吃透,其余需求都是在这个骨架上改条件。

先说清单。角色两个:主角、可碰撞的障碍物(用颜色或角色都行,这里用角色)。变量四个:x速度、y速度、血量、钥匙数。再加三个控制用的开关变量:已受伤、已过关、在地面。这套变量清单看着多,但只要每个变量只承担一件事,逻辑就会非常清楚——我强烈建议新手养成"一个变量一件事"的习惯,别搞成状态一个变量装十种含义,后面每加一个功能都要改它,改着改着就崩了。

3.1 主循环骨架:把判断分批装进重复执行

主角的脚本我通常分成三段,每段一个绿旗事件,各自独立跑:

第一段是输入采样,只负责读键盘,把结果写进变量:

当 绿旗 被点击 将 x速度 设为 0 将 y速度 设为 0 将 血量 设为 3 将 钥匙数 设为 0 将 已受伤 设为 假 将 已过关 设为 假 重复执行 将 x速度 设为 0 如果 <按下 右箭头 键?> 那么 将 x速度 设为 5 如果 <按下 左箭头 键?> 那么 将 x速度 设为 -5

这里用两个独立的"如果"而不是"如果 否则",是有意的:左右同时按下时,后一个覆盖前一个,角色表现为往左走,不会抽搐。如果写成"如果 否则",同样是后一个覆盖前一个,效果一致,但二次判断结构更清晰,方便以后加"同时按下加速"这类逻辑。

第二段是物理更新,负责位置、重力、边界:

重复执行 将 x坐标 增加 (x速度) 如果 <<x坐标 > 220> 或 <x坐标 < -220>> 那么 将 x坐标 设为 ((x坐标) 和 -220 与 220 之间的夹取)

这里 Scratch 没有内置夹取,我一般直接用两个条件分别处理:

如果 <x坐标 > 220> 那么 将 x坐标 设为 220 如果 <x坐标 < -220> 那么 将 x坐标 设为 -220

这两条之所以分开写,是因为边界是两个独立的事实,合成一个条件再加分支要写嵌套,反而不如两条平铺读起来快。

第三段是状态判定,扣血、开门、结束,都是条件语句的重灾区,下面分开讲。

3.2 边界反弹与落地判定:条件里的"前一帧"思维

落地判定是经典难点。直觉写法是"如果 y坐标 < 地面高度 那么 在地面设为真"。但这个写法有个毛病:角色从下往上穿的时候也会被判成在地面。正确做法是结合 y 速度判断——只有在下落(y 速度小于 0)并且脚底已经低于地面高度时,才算落地:

如果 <<y速度 < 0> 与 <y坐标 < -80>> 那么 将 y坐标 设为 -80 将 y速度 设为 0 将 在地面 设为 真 否则 如果 <y坐标 > -80> 那么 将 在地面 设为 假

这段代码里,在地面变量的真假切换是靠两个分支完成的,逻辑上等价于"在地面上就是真,否则就是假"。为什么不用直接赋值将 在地面 设为 <y坐标 <= -80>?因为 Scratch 里布尔值可以当变量值用,但很多初学者不知道,而且直接塞进去的可读性差。用显式的条件赋值,虽然多两块积木,但一眼能看懂。

跳跃的条件就需要用到在地面了,而且必须加一个y速度 = 0或者在地面 = 真的闸门,否则空中可以无限跳(俗称"空中二段跳",很多小游戏就是被这个毁掉的):

如果 <<按下 上箭头 键?> 与 <在地面 = 真>> 那么 将 y速度 设为 12 将 在地面 设为 假

注意:这里在地面必须在跳跃瞬间立刻置假,否则同一帧内重力还没生效,下一帧判断时它还是真,就会出现"连按连跳"。我最初写这个逻辑时就漏了这一句,测试时疯狂按上键,角色能贴着墙一直往上爬。

3.3 受伤与无敌帧:用条件给事件"上锁"

碰撞扣血看起来最简单,实际是全篇最容易出 bug 的地方。最朴素的写法是:

如果 <碰到 尖刺?> 那么 将 血量 增加 -1

问题是,"碰到"在每一帧都成立,角色只要贴着尖刺,血量就会以每秒几十点的速度掉,一秒钟就从 3 掉到 -50。这就是典型的"条件持续成立,事件被重复触发"。解决办法就是 1.3 说的加锁,用已受伤变量做一个时间闸门:

如果 <<碰到 尖刺?> 与 <已受伤 = 假>> 那么 将 血量 增加 -1 将 已受伤 设为 真 重复执行 10 次 将 虚像特效 增加 10 将 虚像特效 设为 0 将 已受伤 设为 假

这段的结构值得细看:判断是"碰到 且 未受伤",进入后立刻把锁锁上,播完无敌动画再把锁打开。中间那段"重复执行 10 次改虚像"其实充当了一个简易计时器,同时还能做出闪烁效果。这里有个细节坑:闪烁循环跑在条件内部,这意味着在这 10 次循环期间,角色脚本是被卡住的(因为这是同一个脚本),其他脚本不受影响。如果你希望闪烁期间角色还能移动,就把闪烁逻辑拆到另一个脚本里,主脚本只负责扣血和上锁。

已受伤这个思路可以推广到几乎所有一次性事件:过关、拾取道具、触发对话、播放音效。统一模式是"条件 且 未触发 → 置为已触发 → 做事情 → 视情况复位"。记住这个模板,能省掉大量重复思考。

3.4 门禁、钥匙与多条件组合

"集齐钥匙才开门"是练习逻辑组合的好例子。条件本身不复杂:

如果 <<钥匙数 >= 3> 与 <已过关 = 假>> 那么 将 已过关 设为 真 广播 过关

但这里有个设计选择值得说:是每次循环都判断一次,还是只在吃到钥匙的那一刻判断一次?我的做法是放在吃钥匙的脚本末尾,吃完立刻判:

如果 <碰到 钥匙?> 那么 将 钥匙数 增加 1 删除 钥匙 克隆体 如果 <钥匙数 >= 3> 那么 广播 开门

这么写的好处是不用每帧轮询,开销小;坏处是如果钥匙数因为别的原因变了(比如调试时手动改),门不会自动开。对于作品来说,前者更重要。真要做成实时响应的,那就放回主循环里每帧判,代价是每次循环多一次比较——对 Scratch 来说微乎其微,取舍看你的具体需求。

另一种常见写法是用"碰到 门 且 钥匙数 >= 3"合成一个条件,我一般不建议。因为"碰到门"和"钥匙够不够"是两件独立的事,合成一个条件后,如果玩家钥匙不够又站在门上,你没法给出"还差几把钥匙"的提示。拆成两层判断,先判碰到,再判够不够,中间插一句提示文字,体验会好很多。条件语句不只是控制流程,它还是给玩家反馈的时机。

3.5 用"否则"链搭一个角色状态机

前面都是零散的条件,最后把它们串起来,就得到主角的状态机。状态机听起来高级,在 Scratch 里就是用状态变量 + 一串"否则如果"实现:

如果 <血量 <= 0> 那么 将 状态 设为 "死亡" 否则 如果 <已过关 = 真> 那么 将 状态 设为 "胜利" 否则 如果 <碰到 尖刺?> 那么 将 状态 设为 "受伤" 否则 将 状态 设为 "正常"

注意顺序:死亡优先于胜利,胜利优先于受伤,最后才是正常。这个顺序不是随便排的,它代表了优先级——一个人物同时"血量归零"和"过关"时,应该显示死亡还是胜利?看你的策划,但必须明确写死,不能让它随机。这就是条件语句顺序的意义:它是有语义的,不是随便摆的。

状态机搭好之后,后面所有行为都围绕状态展开,比如受伤状态才允许闪烁、死亡状态禁用输入、胜利状态冻结物理。这种结构最大的好处是扩展时不用改原有逻辑,只需在链上加一层。我后面再给这个角色加"冲刺"状态,只是插一层判断的事,前面那段一个字节都不用动。

4. 条件语句的性能与作用域:那些运行时才暴露的问题

积木搭对了,跑起来还是可能不对。Scratch 的执行模型有两件事必须搞明白:一是脚本之间的调度顺序,二是克隆体的独立作用域。这两块不懂,条件语句写得再工整也会出现玄学现象。

4.1 轮询频率、帧率与"每帧判一次"的代价

Scratch 默认一秒刷新约 30 次,每次刷新里,每个"重复执行"循环会跑一轮。这意味着你的条件判断频率大约是每秒 30 次。对于碰撞检测来说,这个频率够用,但如果角色速度快、体型小,30 次采样就可能"穿过"障碍物——在两帧之间从障碍物左边飞到右边,两帧都没有判定为"碰到",于是穿墙。这不是条件写错了,是采样点太少。

解决思路有两条。一是降低速度上限,或者给障碍物加宽,让角色不可能在两帧之间跨过去。假设角色一次移动 15 像素,障碍物厚度至少要到 20 像素以上才稳妥;如果速度是 30 像素一帧,障碍物就得 35 像素起步。这个参数是可以算出来的,别凭感觉调。二是在移动前预判:先算目标位置,判断目标位置是否碰到障碍物,碰到就不移动。这相当于把"碰撞检测"从"事后补救"改成"事前拦截",稳定性好很多:

将 目标x 设为 ((x坐标) + (x速度)) 如果 <<目标x > -220> 与 <目标x < 220>> 那么 将 x坐标 设为 (目标x)

代价是每帧多做几次运算,收益是角色不会再出现"卡进墙里再被弹出来"的恶心现象。

4.2 广播与条件的竞态

广播和条件语句混用是另一个高频事故点。典型的错误写法是:在一个脚本里"如果 条件 那么 广播 开始游戏",另一个脚本里"当接收到 开始游戏"直接开始跑,结果发现开始游戏跑了两遍。原因往往是这个"如果"写在一个每帧都跑的循环里,条件一旦持续为真,广播就每秒发 30 次。

修法还是那一条——给广播加锁,或者把广播从循环里拿出来,只在条件从假变真的那一刻发。判断"从假变真"需要一个历史变量,写法是:

如果 <<条件> 与 <上次状态 = 假>> 那么 将 上次状态 设为 真 广播 触发

这种"边沿触发"的写法,凡是需要"某个状态刚出现时做一次事"的场景都能用,比每帧发广播要稳得多。

提示:广播发出后,接收脚本的执行时机不一定是立刻,它可能排在当前帧的其他脚本之后。如果你在广播之后紧接着判断"接收方有没有做完事",几乎必然得到错误答案。涉及顺序依赖的,用"等待"或者用一个双方共享的变量做握手,别指望广播的时序。

4.3 嵌套层级与自制积木的拆分策略

条件嵌套超过三层,除了难看,还有个实际影响:调试成本直线上升。你没法单独测试最内层,因为它外面套着三层条件。我的经验是,超过三层就拆成自制积木,把内层逻辑封装进去,主流程只保留"如果 那么 执行 XX 积木"。

比如前面那个状态机,四层嵌套下去已经很深了。可以拆成三个自制积木:判定是否死亡、判定是否胜利、判定是否受伤,每个里面只有一层判断,返回布尔值,主流程写成:

如果 <判定是否死亡> 那么 ... 否则 如果 <判定是否胜利> 那么 ...

这样每层逻辑都独立可测,主流程也一眼能读完。自制积木在 Scratch 里还支持"运行时不刷新屏幕",对于纯判断类的积木勾上这个选项能让执行更连贯。不过要注意,勾了不刷新之后,里面的等待、移动这些需要画面反馈的操作就看不到了,这个开关是给纯计算用的。

4.4 克隆体中的条件作用域

克隆体是条件语句最容易出谜案的地方。核心规则只有一条:克隆体的变量分两种,全局变量所有克隆共享,私有变量每个克隆一份。如果你用了全局变量然后每个克隆都去改它,就会出现几十个克隆抢同一个变量,结果是一团乱麻。

举个真实例子。做一个弹幕,每个子弹克隆体在"如果 碰到 玩家 那么 广播 扣血",结果一碰到就扣几十点血——因为每一个克隆体都有自己的条件判断,几十个子弹同时判定成立,同时广播。修法是把扣血逻辑收到一个统一的地方,子弹只负责"碰到后删除自己",血量由玩家角色自己判:

// 子弹克隆体 如果 <碰到 玩家?> 那么 删除此克隆体 // 玩家角色 如果 <已受伤 = 假> 那么 如果 <碰到 子弹?> 那么 将 血量 增加 -1 将 已受伤 设为 真

职责一分开,所有并发问题自动消失。我后来的习惯是:扣血、加分这类"改全局状态"的事,永远只让一个角色负责,其他角色最多发通知。

另外,克隆体里有一个经典陷阱:当作为克隆体启动时下面的脚本,和原角色脚本是各自独立的。很多人在克隆体脚本里写重复执行 如果 碰到边缘 那么 删除此克隆体,忘了先把克隆体位置设好,结果克隆体刚出生在原点就被判碰到边缘删掉了,表现为"子弹发不出来"。排查时要先看克隆体出生那一刻的坐标。

5. 常见问题与排查技巧实录

条件语句的 bug 有个共同特征:它们不会报错,只会让行为变得奇怪。所以排查靠的不是看日志,而是靠缩小范围、加探针。下面这几类是我遇到频率最高的,配上一套我自己的排查顺序。

5.1 条件永远成立或永远不成立

这是第一大类。现象是角色一直抖、一直扣血、或者该触发的从不触发。排查顺序我固定是四步:

第一,把条件拆开单独测。<A 与 B>不成立,就分别打印 A 和 B,看是哪个不对。做法很简单,临时建两个变量,一个存 A 的结果,一个存 B 的结果,在屏幕上勾选显示。

第二,检查变量类型。如果比较对象里有变量,先确认它在第一帧被初始化过。空值参与数值比较是万恶之源。

第三,检查比较方向。<10 < 分数>和<分数 > 10>在数学上等价,但人在改代码时很容易只改一半,导致<10 > 分数>这种反向写法。数值刚好落在边界另一侧时才会暴露,测的时候专挑边界值试。

第四,检查作用域。如果这个条件写在克隆体脚本里,确认它读的变量是私有还是全局,克隆之间有没有互相覆盖。

5.2 条件只触发一次或疯狂触发

第二大类,本质是触发频率控制问题。只触发一次通常是因为判断放在了没有循环的脚本里——脚本从头跑到尾,跑完就停,条件只算了那么一次。疯狂触发则相反,判断在循环里、条件又持续为真。

现象最可能原因处理方式
只生效一次判断没套在重复执行里外套重复执行
每秒触发几十次条件持续为真,事件无锁加锁变量,改边沿触发
触发两三次后停循环被"停止这个脚本"打断检查循环内的停止积木
每次运行次数不一样与帧率相关,缺时间去重改用计时器做冷却

冷却这件事值得单独提。用"重复执行 N 次"做冷却,N 的值其实和帧率绑死,机器快慢不同效果就不同。更稳的做法是用计时器:

如果 <<计时器 > 冷却结束时间> 与 <按下 空格 键?>> 那么 将 冷却结束时间 设为 ((计时器) + 0.5) 发射子弹

这样"冷却 0.5 秒"是真实时间,不受帧率影响。这个写法我在做射击类作品时全靠它。

5.3 多个条件互相打架

第三类最隐蔽:条件本身都对,但组合起来结果不对。典型是移动脚本和碰撞脚本抢坐标——一个脚本把角色推到墙外,另一个脚本又把它拉回来,表现为高频抖动。根源是同一帧内两个脚本都在改同一个属性。

处理办法是明确"谁拥有这个属性"。位置只让物理脚本改,碰撞脚本只读不改,需要改就发一个标志变量,由物理脚本下一帧处理。这套思路在正经游戏开发里叫单一职责,在 Scratch 里同样适用。我改别人的作品时,第一件事就是把所有角色的脚本按"改什么"分类,发现两个角色同时改 x 坐标的,一律合并或者拆权。

还有一种打架是逻辑上的:状态变量一会儿被这条脚本设成"受伤",一会儿被那条设成"正常",谁最后跑谁赢。Scratch 的脚本调度顺序不保证,所以这种竞争的结果是不确定的。解决办法是只让一个地方写状态,其他地方只读。这也是自制积木拆分最大的价值——把写操作收拢到一个入口。

5.4 排查速查表与几条硬经验

最后给一张速查表,我一般贴在显示器旁边,出问题先照着过一遍:

检查项怎么查常见结论
变量初始化了吗绿旗脚本第一屏忘了设初值,空值参与比较
条件在循环里吗看积木外层没套循环,只判一次
事件加锁了吗找触发后的置位语句持续触发,需要锁
类型对得上吗单独打印条件两侧字符串和数字混比
克隆体作用域对吗看变量是全局还是私有多个克隆抢同一变量
是否同一帧被改两次按属性归类脚本两个脚本抢同一属性
边界值试过了吗专门用 0、1、最大值测差一错误、方向写反

再补三条不在表里的经验。第一,先在纸上画条件树再动手搭积木,尤其是嵌套超过两层的,纸上五分钟,积木上半小时。第二,每加一个新条件,就把旧的测试用例重跑一遍,Scratch 没有自动化测试,全靠手动回归,懒一次就埋一个雷。第三,条件里不要写"魔法数字",把 90、75、60 这些阈值设成变量,改的时候一次改完,不会漏。

我个人在实际做作品时最深的一点体会是:条件执行语句写得好的标志,不是它能实现多复杂的功能,而是你在加新功能时不需要回头改老逻辑。那些能一路迭代下去的作品,脚本打开都是清清爽爽的几层判断,每个条件只管一件小事,锁和状态都摆得明明白白。反过来,凡是写成一坨嵌套、改一处崩三处的,基本都是把条件当万能胶,见缝就贴。把这套骨架吃透,剩下的就是往里面填内容——而这件事,只有你自己动手搭过几十遍,才会真的变成手感。