简单地编写一个Scratch扩展
随着图形化编程的兴起,比如 Scratch、编程猫和 Mind+ 等等变得越来越受欢迎。同时,像 Scratch 原有的内容已经不足以给各位提供足够的函数了。像 TurboWarp、Gandi IDE、Scratch Lab、Scratch X 等就提供了丰富的非原版函数库(即扩展,mod)。比如,你可以访问 TurboWarp扩展库 来查看 TurboWarp 为 Scratchers 提供的扩展。扩展和其他编程语言的“库函数”相似,都是添加原来没有的函数内容。如果你认为这些内容还不够你用,那你就得自己写了。怎么写呢?接下来就听我娓娓道来!
准备工作
很显然,你需要一个好用的文本编辑器(比如 VSCode、Notepad++等,当然,你直接用记事本也可以)、一个可以加载自定义扩展的 Scratch Mod (比如 TurboWarp、Gandi IDE 扩展模式等)、以及一些 JavaScript 基础。
如果你自恃拥有强大的编程能力和足够的英语基础,你可以前往 TurboWarp 扩展文档 查看更官方更权威的扩展教程。
基本格式
首先,我将为你展示 Scratch 扩展文件的基本结构:
1 |
|
如上,这就是最基本的 Scratch 扩展格式。其中,Scratch.translate.setup()
为你的扩展提供了 l10n (本地化)支持,可以提供这个函数来设置多语言。之后就是定义一个类了,这个类当中包含了你的扩展的信息(getInfo
)和函数定义。最后,通过Scratch.extensions.register()
函数来注册这个扩展。
接下来,让我们慢慢讲具体的内容~
Scratch.translate.setup()
l10n 支持
在 Scratch 扩展中,Scratch.translate.setup()
的语法如下:
1 |
|
给这个函数提供一个 JSON 参数,在之后重新调用Scratch.translate()
函数就可以将key
字符串自动转换为对应的值(也就是翻译内容)。比如,我在上面设置了"_key"
对应的值是"翻译"
,那么,当我的编辑器语言为简体中文(zh-cn)
的时候,Scratch.translate("key")
就会返回"翻译"
。
这是由 Scratch 本身提供的翻译功能,善用它会取得不错的成效。
getInfo()
给你的扩展编写信息
在getInfo()
函数中,你应该为其定义编写一堆内容,包括了扩展ID
,扩展名
,扩展积木颜色
,扩展积木
等等。接下来我会给出一个简单的 getInfo() 示例,它可以创建一个 COMMAND 块:
1 |
|
如上,在getInfo()
的定义中,你会看到一些键值对,比如id
、name
、color1
等等。其中,id
是扩展的唯一标识符,name
是扩展在工具箱(也就是左侧那个版块)显示的名称,color1
则是扩展的颜色,color1\2\3
分别对应了块的背景色、边框色和激活或选中时的颜色。
在上面,我省略了一些键值对,我把它们放到下面了:
类型 | 功能 | |
---|---|---|
menuIconURI | string(dataURL) | 设置显示在侧栏左侧按钮上的图标 |
blockIconURI | string(dataURL) | 设置显示在块左侧的图标 |
docsURI | string(URL) | 设置扩展文档的链接,当有这个内容时,会自动创建一个文档按钮 |
disableMonitor | boolen | 是否禁用监控器,如果为true,将允许这个可以返回值的块在舞台上显示一个监视器,就像变量一样 |
hideFromPalette | boolen | 是否在工具箱中隐藏,这是很有用的,有利于向后兼容 |
filter | Scratch.TargetType.SPRITE或Scratch.TargetType.STAGE | 一个过滤器,选择在哪些内容中显示,比如填写[Scratch.TargetType.SPRITE] 将只显示在角色的工具箱中。当然,如果填入[] 的效果和上一条的效果一样,但是我们应该尽量使用上一个来隐藏某个块。 |
block
后面对应的 Array 中,就是对扩展积木的定义。在这里,我定义了一个 COMMAND 块,上面显示的内容是“示例积木”,它的操作码(opcode
)是”SampleBlock”。
操作码是最重要的一个键,因为它对应了后面对这个块相应的函数的定义。假如我要为这个块编写代码,我就应该在后面定义一个SampleBlock()
函数,Scratch 在尝试将这些代码块释放到 Scratch 虚拟机
中的时候,会找到对应操作码的函数,绑定在块上,以便在编辑器中执行。
在定义一个块的时候,你可以使用一下内容:
类型 | 介绍 | |
---|---|---|
opcode | string | 该块运行时应该运行的函数的名称。例如,如果这是“Sample”,那么将运行类的“Sample”方法。每个扩展中的每个操作码必须是唯一的,因此多个扩展可以每个都有一个操作码为“Sample”的块 |
blockType | Scratch.BlockType.* | 用来决定块的形状,具体见下面一节 |
text | string | 将出现在块编辑器中的文本。当然,这里可能会出现一些特殊的语法,这将在后面讨论 |
arguments | object | 块中可输入的内容。是可选内容,同样在后面讨论 |
当然,还有些高级的内容我们可以在后面介绍。
BlockType
块的类型
玩多了 Scratch ,你应该知道,在 Scratch 中,有很多种不同的块,有像拼图一样的,有圆圆的,有六边形的,也有帽子形状的,还有像个嘴巴一样的 C 的形状的。这就是不同的积木类型,在定义它们的时候,需要根据需求设置它们的类型,接下来我将介绍三个最常见的块类型:
COMMAND 块
你应该注意到了,在上一个小节中,我说的是创建一个 COMMAND 块
,那什么是 COMMAND 块呢?COMMAND 的意思是指令,命令,也就是说,这个块将执行一个函数,但是不返回内容。所以它在 Scratch 中,就是那种拼图形状的块了。
REPORTER 块
我们在使用一些获取值的块的时候,它们通常是圆圆的,比如变量块。这就是 REPORTER 块,它们可以返回函数在运行之后返回的值给 Scratch 编辑器。
BOOLEN 块
当然,我们也会遇到一些判断相关的块,它们返回一个布尔值,要么真要么假。这就是 BOOLEN 块,它和 REPORTER 块很像,都可以返回函数的返回值。虽然口头上说的是返回布尔值,但是它们实际上是可以返回其他类型的值的。
帽子块和C型块将在后面介绍。
编写函数
现在,我为你提供一个最简单的扩展示例,这是在 TurboWarp 扩展文档上抄来的,它会返回一个随机数:
1 |
|
在这里,我定义了一个操作码为 random
的 REPORTER
块,上面显示的文本为 随机数
,之后,根据操作码 random
我在后面定义了一个 random()
函数,并返回一个随机数。这个随机数将会返回到你的 Scratch 编辑器中,你可以在 TurboWarp 加载这个扩展试试。
处理参数
恭喜你,已经学会了简单的扩展编写了,但是这完全不可能完成你的需求。你不可能只写一些只返回内容不处理信息的块(诶你可能真要写一个 Navigator 只返回信息呢)。所以传入参数和处理参数是一个很重要的内容。
首先,我们要学会如何编写一个可以传入参数的块。这其实十分简单,在块定义的text
中,使用一个方括号可以创建一个参数框,之后可以在下面定义参数框传入的参数是什么类型,比如String,Boolen等。格式如下:
1 |
|
其中,arguments
之后的键值对可以定义参数的类型和默认值。比如我在上述示例中,就在块中插入了一个ARG
参数,然后在arguments
中定义了其为STRING
类型的参数(即字符串参数)。当然,传入的参数不止有字符串,在下面列出所有可以传入的内容:
类型 | 介绍 | |
---|---|---|
Scratch.ArgumentType.STRING | 任意文本 | apple, 123, true |
Scratch.ArgumentType.NUMBER | 任意数字 | 123 |
Scratch.ArgumentType.BOOLEAN | 一个布尔值,只能塞入布尔块 | true |
Scratch.ArgumentType.COLOR | 一个十六进制的输入框,可以调用Scratch自带的颜色选择器 | #ff4c4c |
Scratch.ArgumentType.ANGLE | 一个角度输入框,90表示向右,逆时针增加 | 90, 180 |
Scratch.ArgumentType.MATRIX | 一个5x5的矩阵输入框,每个位置只能输入0或1 | 11101010101… |
Scratch.ArgumentType.NOTE | 一个音符输入框,可以调用Scratch自带的音符键盘 | ? |
Scratch.ArgumentType.IMAGE | 显示的是一个内联图像,并非实际的输入项。相关内容稍后会介绍。 | N/A |
Scratch.ArgumentType.COSTUME | 该精灵内的服装名称 | costume1 |
Scratch.ArgumentType.SOUND | 该精灵内的声音名称。 | recording1 |
Scratch虚拟机需要知道传入的参数的名字是什么,所以在定义参数的时候,需要使用一个方括号来定义参数的名字,比如[ARG]
,这样我们就可以获得一个名字叫ARG
的参数了,然后就可以在arguments
中定义参数的类型和默认值了。
同时,我们也可以通过这个名字来在函数里获取传入的参数了,就像下面那样:
1 |
|
静态的列表
如果你仔细观察,你会发现有些块会有个下拉框,从中可以选择一个项来获取参数。比如在运动模块中,你可以看到面向 [DIR]
,这个块中的DIR
可以提供下拉框选择面向哪个角色或者鼠标指针。在生活中我们不乏遇到选择,所以处理一个列表也是重要的一环。
列表项目的定义和普通参数一样,不过我们需要再arguments
中将defaultValue
改为menu
,这样Scratch就会在定义中读取一个数组作为静态列表。格式如下:
1 |
|
这是我在TurboWarp上抄来的实例,其中,处理了用户输入强制转换为字符串。这是一个很明智的选择,也是个很重要的选择,可以避免很多因为错误输入带来的问题。上述代码中,FORMAT_MENU
就是一个列表。在getInfo
中添加了一个menus
属性,之后定义了FORMAT_MENU
列表,其中items
是一个数组,也就是列表中显示的项目。
如果你希望文本和输入的值不同,你可以像这样编写items:
1 |
|
这样,下拉框中将不显示uppercase
或lowercase
,而是显示大写
或小写
。
我们可以看到,有个acceptReporter
键值对,这是用来确认这个列表的位置是否可以塞入一个高级的东西,比如REPORTER
块和BOOLEN
块。如果填写true
,那么就可以塞入一个变量之类的东西:这种情况,字符串处理就十分必要了。
在前面,我提到了使用Scratch.translate()
进行多语言支持,在text
后面你依然可以使用这个函数来确定显示的文本是什么。