OpenTeens' Blog
1878 字
9 分钟
.sb3 文件格式
摄影 | 大帝

众所周知,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:这个积木的 “父对象” 是什么?是妈妈
  1. 一般情况下,一个积木的 “父对象” 就是它上面那个积木,与 “next” 相反。
  2. 但是如果一个积木 “嵌入” 在诸如if之类的语句中,那么它的父对象就是if
  • inputs & fields:这俩是你的输入。比如说 “移动10步”,我们通过opcode能知道具体是哪个积木——“移动n步”。但是移动几步呢? Scratch 有一个输入框:“移动__步” 。你可以输入任意步数,那么你输入的数字就在inputs里面。(如果是一个下拉菜单,那么就会存在fields里面,本质上他俩都是输入)
  • shadow:这个积木需不需要默认值?你如果不填那个 “移动__步” 的输入框,程序还想正常运行,怎么办? Scratch 会为每个参数设置默认值。当你不填的时候,shadow就会被激活,你不填的那个参数就会自动变成默认值。
  • topLevel:这个积木的上方有没有连接积木?Scratch 的积木有几种形状:
  1. 绿旗这类 “触发器积木” 上面不能加其他积木,他们的topLevel一定是 true
  2. “移动” 这类积木,上下都可以连接其他积木。那么只有在他们上面不连接积木时,其topLevel才会是 true。他们的topLevel 不一定是 true

特别地,当一个积木是topLevel时,他还会有xy两个参数,用于标记这一列积木到底在哪个位置。这样你每次重新打开的时候,积木就总会在同一个位置。

看晕了吗?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 提供了,比如说 StringNumber,但是有些东西 json 没提供,比如说 Scratch 变量。

所以我就需要自己创造一个统一的格式,能够表示 Scratch 中的所有数据类型。

不知道你是怎么想的,反正 Scratch 选择了列表。

刚刚那个 1,现在的4,都是 Scratch 自己的统一格式,用来告诉你接下来的东西是哪种类型的数据:文字数字变量或者更多。

这是有一个表的,记录了这种神奇数字和 Scratch 所有数据类型的对应关系:

以后遇到这种数字,查个表就知道了。

完了#

完了?完了。

只是说,如果你想要看更详细的有关.sb3文件格式的内容,可以去这个网站:https://en.scratch-wiki.info/wiki/Scratch_File_Format

祝你们在探索 Scratch 的路上 一路走好 一帆风顺~