在这一部分,我们来介绍一些 Scratch 的“高级特性”——代码的执行顺序。这一部分的知识包括我自己其实也一直懵懵懂懂,直到最近才弄明白。 了解代码的执行顺序,可以帮助我们解决一些让人抓狂的疑难 bug(例如,为什么一个角色跟随另一个角色移动,有时候会产生延迟,有时候则不会;为什么有的作品要“双击绿旗”才能正确初始化;为什么一段代码总是先于另一段代码执行;等等)
前置知识:什么是帧
(如果你已经知道了,可以跳过这部分)
下面的两段代码,运行效果一样吗?
答案是不一样!两段代码的运行效果如下:
这是因为,第一段代码并不是一瞬间执行,每次循环之间实际上等待了约 0.033 秒的间隔。因此,重复执行的效果相当于每次移动后等待0.033秒,再继续,就可以看到角色逐步移动的过程:
而下面的代码则是一瞬间执行完毕,中间不会等待:
这里介绍帧的概念:Scratch的运行是连续不断的帧组成的,每一帧相隔0.033秒。每次重复执行可以理解为一帧,而连续一串的代码通常会一帧内执行完毕(除了“等待”类的积木)
经常说的 FPS(Frame Per Second),其实就是“每秒帧数”的意思。Scratch中FPS通常为30(也就是 1 秒 30 帧,即1 秒 30 次重复执行),如果一帧的计算量很大,FPS就会变低(每秒重复执行次数变低)。因此可以通过测量 FPS 来判断是否卡顿(例如Scratch 中 30 FPS 就是很流畅,某些游戏 5 FPS,就是一秒只有 5 帧,所谓的“卡成 PPT”……)
不同代码片段的执行顺序
- 在 Scratch 中,看似同时执行的代码段,也是有先后顺序的!
- 如果把代码放进循环里,也是一样,每一帧按特定的顺序依次执行一整段代码:如下图,每次循环按先左再右,或先右再左整段地执行。运行结果是变量总是 1 或者总是 2
- 此外,如果加入了“等待”类积木,则不会在一帧内执行完,而是在等待处跳过数帧,再接着执行。
- 另外,“等待 0 秒”并不是不等待,而是仍然会跳过一帧。例如下图,左边的代码第一帧会跳过,第一帧只执行了右边的代码,然后第二帧再执行左边。因此“变量”最终结果总是 1。(所以“等待 0 秒”经常用于“改变”各段积木的执行顺序,来进行初始化之类的操作)
比如下面的代码,点击绿旗,运行结果是什么?答案是1吗?
结果是:1 或者 2,取决于左右代码的先后执行顺序。 Scratch中,代码并不是交错执行。例如下面的执行顺序是错的:
正确的执行顺序应该是整段整段地依次执行:如下图,先①后②,或者先②后① (至于谁先执行,和代码的创建顺序有关,先被创建的“点击绿旗”会优先执行) 如果把代码放进循环里,也是一样,每一帧按特定的顺序依次执行一整段代码:如下图,每次循环按先左再右,或先右再左整段地执行。运行结果是变量总是 1 或者总是 2
答:如果先执行了右边的代码,而没有执行左边的血量初始化为 100,可能导致克隆体一产生,检测到自己血量<1,就直接删除自己。 一种办法是,确保左边的“当作为克隆体启动”创建时间早于右边的“当作为克隆体启动”,这样执行顺序就是先左边再右边,避免问题。 还有一种办法就是使用“等待 0 秒”,这样右侧的代码总是比左侧的代码后执行:
不同角色的代码执行顺序
此外,不同角色的代码执行顺序是有先后的。每一帧,会按某个顺序依次执行各个角色的代码,一个角色的当前帧代码执行完毕后,才会执行下一个角色的代码(不会出现不同角色的代码交错执行的情况) 例如:下面代码的输出结果,要么是“1257、346”要么是“346、1257”,而不会是“1234567”
那么,角色之间的代码先后顺序是什么呢?
答案是:根据角色图层顺序决定。图层靠前的角色的代码先执行,图层靠后的角色后执行。
- 例如下图,运行结果是1还是2?(苹果和香蕉是两个不同角色)
答案是 2。由于角色“苹果”的图层在角色“香蕉”之上,苹果的代码先执行,将变量设为1;之后,香蕉将变量设为2。因此最终变量值为2。
广播的“特性”
还有一个困扰大部分 Scratcher 的问题,是广播的执行时机。
- 运行下面的代码,角色会说什么值?
- 例如,下面的代码,运行结果是什么?
- 再比如,下面的代码希望通过设置变量和广播,来实现删除特定编号的克隆体(比如下面的删除1、2号克隆体)。由于广播总是会放到一帧的末尾执行,当收到广播时,变量是2。导致编号为 1 克隆体没有被删除。
答案是2!这是因为,广播在发出后并不是立即执行,而是会放到当前帧的最后执行(等待当前帧其他代码执行完,再执行)
答案是 5(广播会在当前帧其他代码运行完毕后再执行)
克隆体启动的“特性”
类似广播,“作为克隆体启动”也是等待当前帧的其他代码执行完毕,才开始执行。如下,克隆体会说 2
(注:其中“变量”是全局变量。如果是私有变量,结果则是 1,即继承克隆时的值):
因此类似下面的代码,试图使用全局变量向克隆体传递信息,会出错:“作为克隆体”启动的代码放在了本帧末尾执行,而此时设置的坐标都是(10,20),第一个子弹也移到了第二个子弹的(10,20)位置:
循环的“特性”
已知左侧的“绿旗被点击”比右侧“绿旗被点击”创建的更早,请问输出的顺序是如何?
答案是:1、2、4、5、6、3
由此可以看出循环的特性:循环前的代码和第一次循环的代码会一起执行,而循环结束后的代码会放在下一帧执行。
解决跟随延迟问题
上面介绍了 Scratch 代码执行顺序的特性,总结起来就是:
- 不同角色代码执行顺序:图层在上角色先执行;
- 同一角色不同代码执行顺序:先被创建的积木先执行;
- 广播、克隆体事件会被放到当前帧的末尾执行;
- 循环前的代码和第一次循环的代码会同一帧执行,而循环结束后的代码会放在下一帧执行。
在前面的教程中,你应该注意到了一个问题:武器跟随玩家有延迟!下面,我们就来应用前面的知识,解决这个问题。
首先,玩家和武器的代码执行顺序是怎样的?答案是,由于武器的图层总是在玩家之上,武器的代码总是先于玩家执行。枪械先移到玩家,然后玩家再移动,导致枪械移到的总是玩家之前的位置,进而导致枪械总是比玩家慢一帧。
要解决这个问题,可以用广播:
- 首先,在舞台添加如下代码:
- 将玩家的显示代码修改如下,循环中的代码改成接受广播:
- 玩家武器的显示代码做类似修改:
- 其他角色的显示如果也有延迟问题,代码也可以做类似修改。例如“敌人”角色的显示代码:
- “敌人”武器也可以做类似修改:
- 类似的,地图背景、遮挡物显示代码调整如下:
然后,让其他角色在收到这个广播后,才刷新自己的位置。由于广播的特性,显示的代码会被加到本帧的末尾执行,这样,玩家角色的移动计算就可以先执行,然后再执行接受广播“刷新渲染”,更新各角色的显示位置,此时玩家和武器移到的就是最新的位置。
运行代码,会发现“敌人”角色的本体也显示了出来。这是因为本体也接收了广播并显示。
因此我们可以新建私有变量“是克隆体?”来区分是本体还是克隆体,只有克隆体才执行显示动作:
主体将私有变量设为0,克隆体将私有变量设为1。这样就可以通过私有变量来区分本体、克隆体:
这样就解决了跟随延迟问题~
总结
- 不同角色代码执行顺序:图层在上角色先执行;
- 同一角色代码执行顺序:先被创建的积木先执行;
- 广播、克隆体事件会被放到当前帧的末尾执行;
- 循环前的代码和第一次循环的代码会同一帧执行,而循环结束后的代码会放在下一帧执行。
本节附录
本节关于代码执行顺序的知识参考了 -6 的 Scratch 创作参考手册(附录)。这里面介绍了很多 Scratch 常见疑难问题,以及各种特性,强烈建议阅读(刷新认知.jpg)
- Scratch创作参考手册(0-2):https://learn.ccw.site/post/399b9480-ca8a-4dd8-8daa-e285aabbd2d6
- Scratch创作参考手册(3-5):https://www.ccw.site/post/cc2590e6-99d1-48d1-8f6c-fde7b27ce07d
- Scratch创作参考手册(6):https://www.ccw.site/post/1361d877-2fa3-42c3-b9d0-f7ed9fca0e82
- Scratch创作参考手册(附录):https://www.ccw.site/post/510a0dde-3296-4b0d-b386-1df78da59b0b
<iframe
width="100%"
height="800px"
scrolling="no"
src="https://www.ccw.site/embed?id=STG202/Arkos/Lec6/01&type=comment"
title="{射击6-代码执行顺序}"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
scrolling="0"
></iframe>