众所周知,Scratch 是图形化编程的鼻祖。
但是“图形化”也就意味着它不像传统编程一样使用代码来保存程序。那么 Scratch 是怎么保存我的程序的呢?
所有玩过 Scratch 的人都知道,如果你点保存,电脑上就会多出一个.sb3文件(Scratch 3.0)。
不过什么是.sb3文件?它又是什么原理呢?
打开一个 .sb3 文件
我们不妨写一个最简单的 Scratch 项目,方便我们的研究:

好啦!这个项目只有一个角色,一段程序。那么接下来,我们就可以把它“拆开”了。
虽然 Scratch 项目的文件后缀是.sb3,但是它的格式却是zip格式。不信的话,你把文件名改成project.zip试试?
改完后缀再一解压,我们发现在这个 zip 包中有下面几个名称很诡异的文件:

不过,通过他们的文件后缀和图标,我们可以知道:
- 前两个是音频文件(
.wav) - 中间三个是图象文件(
.svg) - 最后一个是
json格式的文件
前五个都是资源文件,那我们的程序存储在哪里呢? 很明显,我们的程序就存储在project.json中。
“project.json”
打开我们的project.json (懒得动手点这个:https://jsonhero.io/j/aLa9Pdplt8O8) ,我们发现这个 json 文件的第一级只有四个字段:

中间两个是空的,最后一个是meta,存放的是版本信息。所以我们的程序一定存储在targets里面。
targets很关键。它存储着每一个 Scratch 角色的信息。 包括那个 “形似糖葫芦” 的角色、我们写的程序,甚至还包括舞台。 (嗯,在 Scratch 中,舞台其实也是一个角色)
那么targets有两项也就不难理解了: 一个是那个“糖葫芦”, 另一个是舞台。
targets中的每一个角色都包含一堆属性,看名字都能猜出什么意思。比如:isStage是不是舞台;name角色的名称;volume音量 等等。有兴趣的可以自己看看。

那么我们的代码究竟在哪?
请看我选中的blocks字段。(可算完了)
我们都知道,在 Scratch 中,你可以拖动一个一个的 “积木” 来给角色编程,比如移动10步、右转15度。 这些 “积木” 的英文就叫做block。
你每拖动一个block,其实就是把这个 block 和它相关的参数添加到blocks这个列表中。
所以通过查看 blocks 这个列表,我们就能知道这个程序的逻辑。
blocks
打开 blocks 一看,里面分别有 a, b, c三个东西。这分别对应我们程序的三个 block。而 a, b, c 则分别是这三个 block 的 id(唯一标识符)。
以id为索引是有用处的。我们接下来讲到这一点,你就会感叹设计者的神奇脑回路。
我们先来看看a长什么样:
{
"opcode": "motion_movesteps",
"next": "c",
"parent": "b",
"inputs": {
"STEPS": [
1,
[
4,
"10"
]
]
},
"fields": {},
"shadow": false,
"topLevel": false
}
原来a也有一堆属性:
opcode(指令):这个积木是干啥的?移动还是旋转?opcode能告诉你答案。比如说这个”motion_movesteps”就是移动n步这个积木。next:下一个积木是什么?这里的 “c” 是 c 积木的id。因为blocks的索引是 “积木 id”,所以通过 “c” 就能很快速地找到下一个积木。parent:这个积木的 “父对象” 是什么?是妈妈
- 一般情况下,一个积木的 “父对象” 就是它上面那个积木,与 “next” 相反。
- 但是如果一个积木 “嵌入” 在诸如
if之类的语句中,那么它的父对象就是if。
inputs&fields:这俩是你的输入。比如说 “移动10步”,我们通过opcode能知道具体是哪个积木——“移动n步”。但是移动几步呢? Scratch 有一个输入框:“移动__步” 。你可以输入任意步数,那么你输入的数字就在inputs里面。(如果是一个下拉菜单,那么就会存在fields里面,本质上他俩都是输入)shadow:这个积木需不需要默认值?你如果不填那个 “移动__步” 的输入框,程序还想正常运行,怎么办? Scratch 会为每个参数设置默认值。当你不填的时候,shadow就会被激活,你不填的那个参数就会自动变成默认值。topLevel:这个积木的上方有没有连接积木?Scratch 的积木有几种形状:
- 绿旗这类 “触发器积木” 上面不能加其他积木,他们的
topLevel一定是 true。 - “移动” 这类积木,上下都可以连接其他积木。那么只有在他们上面不连接积木时,其
topLevel才会是 true。他们的topLevel不一定是 true。
特别地,当一个积木是topLevel时,他还会有x和y两个参数,用于标记这一列积木到底在哪个位置。这样你每次重新打开的时候,积木就总会在同一个位置。
看晕了吗?Scratch 的核心就这些东西,难度肯定是有的。不过你看懂之后就无敌了。
奇怪的 inputs
慢着,上面的 inputs怎么这么奇怪?
"inputs": {
"STEPS": [
1,
[
4,
"10"
]
]
},
这是个啥啊,丑死了!
别着急,我们一点一点往下看。
首先是这个 "STEPS"。 刚刚我们说了,inputs 里面是你填的参数:移动几步、旋转几度…… 那么 "STEPS" 很容易理解,就是关于到底移动几步的参数。
STEPS 是一个列表,第一项是 1 ,第二项又是一个列表。
其中关于1这个位置的数字有特殊含义,稍后会讲到。先给出对照表:
| 数字 | 意思 |
|---|---|
| 1 | 这是一个有 shadow(默认值)的 “填空题” |
| 2 | 该参数没有默认值 |
| 3 | “填空题”。但是填了一个积木,而不是输入 |
1、2、3 分别对应如图所示的几种 Scratch Block:

ok 这是一个填空题。那下边一个列表又是什么意思呢?
我们看到了 “10”。很明显,这就是我填的东西。
请注意,"10" 是一个字符串,但是 “移动10步” 中的参数 10 显然应该是一个 数字。
问题就来了:一个填空题,它可以填文字、数字、日期,甚至还能填变量。有些东西 json 提供了,比如说 String和Number,但是有些东西 json 没提供,比如说 Scratch 变量。
所以我就需要自己创造一个统一的格式,能够表示 Scratch 中的所有数据类型。
不知道你是怎么想的,反正 Scratch 选择了列表。
刚刚那个 1,现在的4,都是 Scratch 自己的统一格式,用来告诉你接下来的东西是哪种类型的数据:文字、数字、变量或者更多。
这是有一个表的,记录了这种神奇数字和 Scratch 所有数据类型的对应关系:

以后遇到这种数字,查个表就知道了。
完了
完了?完了。
只是说,如果你想要看更详细的有关.sb3文件格式的内容,可以去这个网站:https://en.scratch-wiki.info/wiki/Scratch_File_Format
祝你们在探索 Scratch 的路上 一路走好 一帆风顺~


