项目结构
scratch3.0把相关环境分成了三个部分,vm、blocks和gui; vm管理项目配置和逻辑、gui管理页面渲染、blocks是管理积木模块内容。
项目启动
scratch-vm:
npm link
npm run watch
scratch-blocks:
npm link
npm run prepublish
新开一个shell,进入scratch-gui目录:
npm link scratch-vm scratch-blocks
npm start
自定义扩展积木属性
逻辑梳理
scratch-vm自定义block,然后生成scratch-blocks所需的json内容;scratch-blocks拿到json后,会解析成一个对象,然后触发积木创建事件,将该对象进行处理生成xml格式;scratch-vm监听积木事件,会提取xml的信息转换成对象。
详细步骤
scratch-vm:
1、修改extensions文件夹里定义积木,这些自定义积木由extension-manager.js管理,需要加入自定义积木的文件;
{
opcode: 'fourdigitdisplay_displayString',
blockType: BlockType.COMMAND,
text: formatMessage({
id: '4displayText',
default: '四位数码管 显示文本 “[VALUE]”',
description: 'text.'
}),
arguments: {
VALUE: {
type: ArgumentType.STRING,
defaultValue: formatMessage({
id: 'bangbao.4displayText',
default: '0000'
})
}
},
gen: {
arduino: ''
}
}
2、修改runtime.js的_convertBlockForScratchBlocks
方法,先获取积木blockInfo的代码属性,增加到blockJSON里面。
_convertBlockForScratchBlocks (blockInfo, categoryInfo) {
const extendedOpcode = `${categoryInfo.id}_${blockInfo.opcode}`;
let code = '';
if (blockInfo.arguments.CODE) {
code = blockInfo.arguments.CODE[0];
}
let func = null;
if (blockInfo.gen) {
func = blockInfo.gen.arduino;
}
const blockJSON = {
type: extendedOpcode,
code: code,
gen: func,
inputsInline: true,
category: categoryInfo.name,
colour: categoryInfo.color1,
colourSecondary: categoryInfo.color2,
colourTertiary: categoryInfo.color3,
extensions: ['scratch_extension']
};
这里增加了code
和gen
属性。
cratch-blocks:
1、 修改block.js数据模型,增加属性;
this.genCode = null;
···
Blockly.Block.prototype.setGenCode = function(genCode) {
this.genCode = genCode;
}
···
Blockly.Block.prototype.jsonInit = function(json) {
···
if (json['gen']) {
this.setGenCode(json['gen']);
}
};
2、根据block对象生成xml格式,修改xml.js文件,添加xml标签。
Blockly.Xml.blockToDom = function(block, opt_noId) {
var element = goog.dom.createDom(block.isShadow() ? 'shadow' : 'block');
element.setAttribute('type', block.type);
if (!opt_noId) {
element.setAttribute('id', block.id);
}
if (block.genCode) {
element.setAttribute('code', block.genCode);
}
···
scratch-vm:
1、 修改adapter.js,给block添加新属性,并用scratch-blocks里xml的标签属性赋值。
const domToBlock = function (blockDOM, blocks, isTopBlock, parent) {
if (!blockDOM.attribs.id) {
blockDOM.attribs.id = uid();
}
// Block skeleton.
const block = {
id: blockDOM.attribs.id, // Block ID
opcode: blockDOM.attribs.type, // For execution, "event_whengreenflag".
code: blockDOM.attribs.code,
inputs: {}, // Inputs to this block and the blocks they point to.
fields: {}, // Fields on this block and their values.
next: null, // Next block in the stack, if one exists.
topLevel: isTopBlock, // If this block starts a stack.
parent: parent, // Parent block ID, if available.
shadow: blockDOM.name === 'shadow', // If this represents a shadow/slot.
x: blockDOM.attribs.x, // X position of script, if top-level.
y: blockDOM.attribs.y // Y position of script, if top-level.
};
代码生成框架
scratch-vm
1、在自定义扩展积木时定义代码生成函数:
函数解释:第一个参数是Generator对象,里面定义了代码生成方法;第二个参数是block对象
2、将积木的代码生成方法放到积木里,修改runtime.js文件
_convertBlockForScratchBlocks (blockInfo, categoryInfo) {
const extendedOpcode = `${categoryInfo.id}_${blockInfo.opcode}`;
let code = '';
if (blockInfo.arguments.CODE) {
code = blockInfo.arguments.CODE[0];
}
let func = null;
if (blockInfo.gen) {
func = blockInfo.gen.arduino;
}
const blockJSON = {
type: extendedOpcode,
code: code,
gen: func,
inputsInline: true,
category: categoryInfo.name,
colour: categoryInfo.color1,
colourSecondary: categoryInfo.color2,
colourTertiary: categoryInfo.color3,
extensions: ['scratch_extension']
};
scratch-blocks
1、给积木原型加上代码生成方法属性,修改block.js文件的Blockly.Block.prototype.jsonInit方法,为了下一个步骤能取到代码生成函数变量。
this.genCode = null;
···
Blockly.Block.prototype.setGenCode = function(genCode) {
this.genCode = genCode;
}
···
Blockly.Block.prototype.jsonInit = function(json) {
···
if (json['gen']) {
this.setGenCode(json['gen']);
}
};
2、增加generator.js文件,该文件负责积木转换成代码。参考:https://github.com/BlocklyDuino/BlocklyDuino/blob/gh-pages/blockly/core/generator.js
- 修改blockToCode方法:
func.call第一个参数表示调用的对象,第二个参数表示genCode方法的第一个参数,第三个参数表示genCode方法的第二个参数。
- 修改init方法:http://www.superblockly.com/banbao/js/arduino.js
- 修改finish方法:可以参考http://www.superblockly.com/banbao/js/arduino.js
scratch-gui
1、 调用workspaceToCode生成代码:
代码生成框架优化
上面的代码生成框架能够满足生成代码的功能,但是需要定义多一个属性,并将属性传递到scratch-blocks,对框架的改动较大。故可以对代码进行优化。
scratch-blocks
1、获取积木使用var blocks = workspace.getTopBlocks(true);
2、调用代码生成函数,关键文件是generator.js,这里使用this[block.type],this指的是generator
3、定义代码生成方法 增加BBRobot.js文件,该文件负责传感器代码的生成,目录结构如下:
传感器代码生成如下:
aduino文件夹可使用奥松编程的: 地址:http://www.superblockly.com/banbao/generators/arduino/ 这些是代码辅助作用,比如有些text类型的积木,直接返回文本就可以。
4、修改build.py文件,把新增的文件加入打包路径。
scratch-vm
主要修改/extensions里面文件夹的index.js文件,注意以下几个地方的规范:
注意:getFieldValue的name必须和menu的值一致。
scratch-gui
调用代码生成:this.ScratchBlocks.Arduino.workspaceToCode(this.workspace);
最终效果
四位数码管代码生成:
温度传感器代码生成:
积木生成代码:
积木定义规范
原生积木
增加代码生成方法
以下步骤只需要在scratch-blocks项目操作。
- 方法名和积木type一致;
- valueToCode、getFieldValue、statementToCode第二个参数和积木定义的args的name一致。
- 返回值如果是代码段则直接返回拼接后的字符串;否则返回数组,第一个元素是拼接后的字符串,第二个参数参考generator.js以ORDER_前缀的属性
扩展积木
在scratch-vm项目增加积木定义
1、进入src/extensions对应积木文件夹,修改index.js文件,增加以下代码:
{
opcode: ‘积木ID',
blockType: 积木类型,参考block-type.js文件,
text: formatMessage({
id: '文本ID',
default: '显示的文本,[]表示变量',
description: '文本描述'
}),
arguments: {
变量名称: {
type: 变量类型,参考argument-type.js文件,
menu: 字符串,可选,定义下拉菜单,名称对应menus列表
defaultValue: formatMessage({
id: '变量ID’,
default: 默认值
})
}
}
}
2、按照定义的opcode定义方法,起运行积木功能的作用。
opcode名称 (args, util) {
}
3、如果有定义menu,则需要定义下拉菜单方法,返回一个数组,元素是有text和value组成的元素。
_initTemperatureSensorTypeParam () {
return [
{
text: formatMessage({
id: 'temperatureSensor.celsius',
default: temperatureSensorType.celsius.name
}),
value: temperatureSensorType.celsius.value
},
{
text: formatMessage({
id: 'temperatureSensor.fahrenheit',
default: temperatureSensorType.fahrenheit.name
}),
value: temperatureSensorType.fahrenheit.value
}
]
}
在scratch-blocks项目增加代码生成方法
- 增加一个方法,方法名由插件ID_积木OPCODE组成。
- valueToCode、getFieldValue、statementToCode第二个参数和积木定义的arguments的变量名称一致,如果是下拉菜单,则和menu名称一致。
- definitions_定义include头文件、变量定义和方法定义。
- setups_定义setup函数里的语句。
- 返回值如果是完整代码段则直接返回,如果是可以作为输入参数则返回数组,第一个元素是拼接的字符串,第二个参数参考generator.js以ORDER_前缀的属性。
FAQ
1、如何监听积木区变化,一旦积木区发生变化,产生对应的代码?
解决:舞台每次变化都会调用onWorkspaceMetricsChange方法,在该方法里调用this.sb2cpp()即可。
2、自定义扩展积木定义时参考奥松编程的代码去定义,这样后面可以重用奥松编程的代码:http://www.superblockly.com/banbao/js/arduino.js
解决:积木生成的时候会生成type属性,该属性与category的id和积木的opcode组成,例如categoryId为BB,积木的opcode为ds18b20_sersor,则type为BB_ds18b20_sersor。在scratch_blocks里定义方法时必须保持一样.
3、Generator的valueToCode和getFieldValue没有效果?
解决:分析代码知道block.getFieldValue是调用getField,而getField是通过查找当前block的inputList数组,但是对于下拉菜单的数据是放在了childBlocks_数组,所以修改getField方法: