整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

可视化编程工具blockly-工具箱

可视化编程工具blockly-工具箱

明:

1. blockly代码下载,基础代码建议先阅读可视化编程工具blockly——工作区、可视化编程工具blockly——可调整大小的工作区两篇文章;

2. blockly工具箱支持xml和json两种方式定义,本文使用xml演示,json方式创建工具箱可参考google官网文档https://developers.google.cn/blockly/guides/configure/web/toolbox。

创建分组的工具箱

工具箱就是可供用户使用的代码块,默认显示在工作区的左侧,如果代码块较多的话最好能分类组织,工具箱的代码通过在index.html中id为toolbox的xml代码进行定义,blcokly会解析xml并注入到页面图形化的工具箱代码DOM,如下图的工具箱包含了控制、逻辑两个类别。

对应的xml定义代码如下,其中category定义了一个分类,block定义了代码块,type指定的类别都是blockly默认提供的,定义代码在./blockly/blocks目录下

<xml id="toolbox" style="display: none">
    <category name="控制">
      <block type="controls_if"></block>
      <block type="controls_whileUntil"></block>
      <block type="controls_for">
    </category>
    <category name="逻辑">
      <block type="logic_compare"></block>
      <block type="logic_operation"></block>
      <block type="logic_boolean"></block>
    </category>
  </xml>

工具箱还可以多层嵌套定义,如下xml定义的工具箱效果如下:

<xml id="toolbox" style="display: none">
    <category name="核心">
      <category name="控制">
        <block type="controls_if"></block>
        <block type="controls_whileUntil"></block>
      </category>
      <category name="逻辑">
        <block type="logic_compare"></block>
        <block type="logic_operation"></block>
        <block type="logic_boolean"></block>
      </category>
    </category>
    <category name="数学">
      <block type="math_constant"></block>
      <category name="算术">
        <block type="math_arithmetic"></block>
        <block type="math_number"></block>
      </category>
      <category name="三角">
        <block type="math_single"></block>
        <block type="math_trig"></block>
      </category>
    </category>
  </xml>


设置分组颜色

工具箱分组类别还可以指定颜色,通过colour属性进行设定,colour值的范围是0~360,如下xml代码生成的工具箱效果如下:

<xml id="toolbox" style="display: none">
    <category name="逻辑" colour="20">
      <block type="controls_if"></block>
      <block type="controls_whileUntil"></block>
      <block type="controls_for">
    </category>
    <category name="控制" colour="200">
      <block type="logic_compare"></block>
      <block type="logic_operation"></block>
      <block type="logic_boolean"></block>
    </category>
</xml>

动态类别的代码块

blockly提供的工具箱中有两类有着特殊的行为,分别是变量和函数,它们的xml定义中拥有custom属性,分别为VARIABLE和PROCEDURE,代码及显示效果如下:

<xml id="toolbox" style="display: none">
    <category name="变量" custom="VARIABLE"></category>
    <category name="函数" custom="PROCEDURE"></category>
  </xml>

其中变量分组中点击创建变量并按照提示输入变量名称后会生成如下三个代码块:

函数分组中包含三个代码块,前两个为方法定义,一个方法带有返回值,第三个为条件返回的代码块,通过这三个代码块我们可以编写新的方法来生成新的代码块,如下定义一个inc方法会生成一个新的代码块inc,该方法将输入的x变量增加1并返回,新生成的代码块又可以作为代码块进行使用:

带缺省值的代码块

有些代码块希望拥有缺省值,如下代码生成工具箱效果如下:

<xml id="toolbox" style="display: none">
    <block type="logic_boolean"></block>
    <block type="math_number">
      <field name="NUM">42</field>
    </block>
    <block type="math_arithmetic">
      <field name="OP">ADD</field>
      <value name="A">
        <shadow type="math_number">
          <field name="NUM">1</field>
        </shadow>
      </value>
      <value name="B">
        <shadow type="math_number">
          <field name="NUM">1</field>
        </shadow>
      </value>
    </block>
  </xml>

其中:

  1. logic_boolean不需要事先设置,默认值为真(true);
  2. math_number通过field设置缺省值42;
  3. math_arithmetic使用field、value,加上shadow blocks实现了缺省值的设置。

戏介绍

迷宫游戏总共有10个关卡,随着关卡等级的提高,积木会变多,难度也有相应的增加。游戏目的:了解方向积木、循环积木、以及判断积木的使用;游戏等级:1-10级;运行程序:单机可以执行积木脚本,积木脚本将控制企鹅运动,直到脚本执行完成或找到地图图标标志;方向积木:向左转、向右转;循环积木:重复执行直到,for或while循环;判断积木:如果前方可以通行,if or ifelse;

分析实现

积木定义

旋转方向积木

Blockly.Blocks['maze_turn']={
  init: function() {
  //控制积木左转、右转
    var DIRECTIONS=[[BlocklyGames.getMsg('Maze_turnLeft'), 'turnLeft'],
         [BlocklyGames.getMsg('Maze_turnRight'), 'turnRight']];
    // Append arrows to direction messages.
    DIRECTIONS[0][0] +=Maze.Blocks.LEFT_TURN;
    DIRECTIONS[1][0] +=Maze.Blocks.RIGHT_TURN;
    this.setColour(Maze.Blocks.MOVEMENT_HUE);
     //添加元素下拉列表
    this.appendDummyInput()
        .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR');
    this.setPreviousStatement(true);
    this.setNextStatement(true);
    this.setTooltip(BlocklyGames.getMsg('Maze_turnTooltip'));
  }
};

Blockly.JavaScript['maze_turn']=function(block) {
  // 获得下拉列表值
  var dir=block.getFieldValue('DIR');
  return dir + '(\'block_id_' + block.id + '\');\n';
};

循环积木

Blockly.Blocks['maze_forever']={
  init: function() {
    this.setColour(Maze.Blocks.LOOPS_HUE);
    //添加图标
    this.appendDummyInput()
        .appendField(BlocklyGames.getMsg('Maze_repeatUntil'))
        .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16));
        //添加执行语句
    this.appendStatementInput('DO')
        .appendField(BlocklyGames.getMsg('Maze_doCode'));
    this.setPreviousStatement(true);
this.setTooltip(BlocklyGames.getMsg('Maze_whileTooltip'));
  }
};

Blockly.JavaScript['maze_forever']=function(block) {
  // Generate JavaScript for repeat loop.
  var branch=Blockly.JavaScript.statementToCode(block, 'DO');
  if (Blockly.JavaScript.INFINITE_LOOP_TRAP) {
    branch=Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g,
        '\'block_id_' + block.id + '\'') + branch;
  }
  //循环执行branch直到碰到地图图标
  return 'while (notDone()) {\n' + branch + '}\n';
};

判断积木

Blockly.Blocks['maze_if']={
 //判断积木是否可以前进、左转、右转
  init: function() {
    var DIRECTIONS=[[BlocklyGames.getMsg('Maze_pathAhead'), 'isPathForward'],
         [BlocklyGames.getMsg('Maze_pathLeft'), 'isPathLeft'],
         [BlocklyGames.getMsg('Maze_pathRight'), 'isPathRight']];
    // Append arrows to direction messages.
    DIRECTIONS[1][0] +=Maze.Blocks.LEFT_TURN;
    DIRECTIONS[2][0] +=Maze.Blocks.RIGHT_TURN;
    this.setColour(Maze.Blocks.LOGIC_HUE);
    //添加元素下拉列表
    this.appendDummyInput()
        .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR');
        //判断执行代码
    this.appendStatementInput('DO')
        .appendField(BlocklyGames.getMsg('Maze_doCode'));
    this.setTooltip(BlocklyGames.getMsg('Maze_ifTooltip'));
    this.setPreviousStatement(true);
    this.setNextStatement(true);
  }
};

Blockly.JavaScript['maze_if']=function(block) {
  // 获取下拉列表if中的条件代码
  var argument=block.getFieldValue('DIR') +
      '(\'block_id_' + block.id + '\')';
  var branch=Blockly.JavaScript.statementToCode(block, 'DO');
  var code='if (' + argument + ') {\n' + branch + '}\n';
  return code;
};

迷宫生成

企鹅迷宫对象:Maze.map,随着等级的变化map中的路线图也会变化地图图标位置:Maze.finish[y, x],用于判断企鹅是否到达finsih点企鹅当前位置:[pegmanY, pegmanX]企鹅方向:NORTH: 0:[pegmanY-1, pegmanX],EAST: 1:[pegmanY, pegmanX+1],SOUTH: 2:[pegmanY+1, pegmanX],WEST: 3:[pegmanY-1, pegmanX+1]

  // 添加地图图标
  var finishMarker=Blockly.utils.dom.createSvgElement('image', {
      'id': 'finish',
      'height': 34,
      'width': 20
    }, svg);
  finishMarker.setAttributeNS(Blockly.utils.dom.XLINK_NS, 'xlink:href',
      Maze.SKIN.marker);
// 添加企鹅图标
  var pegmanIcon=Blockly.utils.dom.createSvgElement('image', {
      'id': 'pegman',
      'height': Maze.PEGMAN_HEIGHT,
      'width': Maze.PEGMAN_WIDTH * 21, // 49 * 21=1029
      'clip-path': 'url(#pegmanClipPath)'
    }, svg);
  pegmanIcon.setAttributeNS(Blockly.utils.dom.XLINK_NS, 'xlink:href',
      Maze.SKIN.sprite);

积木执行

企鹅移动企鹅移动对象为[command, id],comand为东南西北四个方向,id为积木的id。

Maze.move=function(direction, id) {
//判断企鹅某个方向是否可以移动
  if (!Maze.isPath(direction, null)) {
    Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]);
    throw false;
  }
  var effectiveDirection=Maze.pegmanD + direction;
  var command;
  //构造企鹅移动方向对象
  switch (Maze.constrainDirection4(effectiveDirection)) {
    case Maze.DirectionType.NORTH:
      Maze.pegmanY--;
      command='north';
      break;
    case Maze.DirectionType.EAST:
      Maze.pegmanX++;
      command='east';
      break;
    case Maze.DirectionType.SOUTH:
      Maze.pegmanY++;
      command='south';
      break;
    case Maze.DirectionType.WEST:
      Maze.pegmanX--;
      command='west';
      break;
  }
  Maze.log.push([command, id]);
};

企鹅转动方向

Maze.turn=function(direction, id) {
  if (direction) {
    // Right turn (clockwise).
    Maze.pegmanD++;
    Maze.log.push(['right', id]);
  } else {
    // Left turn (counterclockwise).
    Maze.pegmanD--;
    Maze.log.push(['left', id]);
  }
  Maze.pegmanD=Maze.constrainDirection4(Maze.pegmanD);
};

企鹅某个方向是否可移动

Maze.isPath=function(direction, id) {
  var effectiveDirection=Maze.pegmanD + direction;
  var square;
  var command;
  switch (Maze.constrainDirection4(effectiveDirection)) {
    case Maze.DirectionType.NORTH:
      square=Maze.map[Maze.pegmanY - 1] &&
          Maze.map[Maze.pegmanY - 1][Maze.pegmanX];
      command='look_north';
      break;
    case Maze.DirectionType.EAST:
      square=Maze.map[Maze.pegmanY][Maze.pegmanX + 1];
      command='look_east';
      break;
    case Maze.DirectionType.SOUTH:
      square=Maze.map[Maze.pegmanY + 1] &&
          Maze.map[Maze.pegmanY + 1][Maze.pegmanX];
      command='look_south';
      break;
    case Maze.DirectionType.WEST:
      square=Maze.map[Maze.pegmanY][Maze.pegmanX - 1];
      command='look_west';
      break;
  }
  if (id) {
    Maze.log.push([command, id]);
  }
  return square !==Maze.SquareType.WALL && square !==undefined;
};

脚本执行回调

Maze.initInterpreter=function(interpreter, scope) {
  var wrapper;
  moveForward=function(id) {
    Maze.move(0, id);//前进
  };
  moveBackward=function(id) {
    Maze.move(2, id);//后退
  };
  turnLeft=function(id) {
    Maze.turn(0, id);//左转
  };
  turnRight=function(id) {
    Maze.turn(1, id);//右转
  };
  isPathForward=function(id) {
    return Maze.isPath(0, id);//是否可前进
  };
  isPathRight=function(id) {
    return Maze.isPath(1, id);
  };
  isPathBackward=function(id) {
    return Maze.isPath(2, id);
  };
  isPathLeft=function(id) {
    return Maze.isPath(3, id);
  };
  notDone=function() {//游戏是否结束
    return Maze.notDone();
  };
};

其他

迷宫游戏某个等级,脚本区只能使用固定数量的积木,此函数提示脚本区剩余积木数量。

lockly Games 是一系列编程教育小游戏。搜索“少儿编程教程网”就可以找到“Blockly游戏”(https://blockly-games.kidscoding8.com/blockly-games/zh-hans/index.html?lang=zh-hans)。

“鸟”这个关卡在迷宫关卡的编程知识基础上学习运用关系表达式来控制鸟的飞行方向,让鸟合理规划线路吃到虫子后回到自己的巢。

前6关是基本块的训练。通过对角度、“没有蠕虫”块、XY坐标位置块、关系表达式块的训练掌握怎样用条件判断和逻辑控制鸟的复杂飞行。

每次过关后还会将你编写的图形化代码转换为对应的JavaScript代码,让你对JavaScript有一个初步感性的认识。

从第7关开始路线开始变得复杂了。在处理多个判断条件时需要用到不同的关系表达式——如果if、否则如果else if、否则else。


如果if(条件1):

如果条件1为真,执行这里(条件为真才执行);

否则如果else if(条件2):

否则,当条件2为真执行这里(当条件1不为真,条件2为真执行这里);

否则else:

条件1,条件2都不为真,执行这里。


第7关代码

第8关出现了新的积木块“和”,可以连接多个需要同时满足的条件。

第8关路线

第8关代码

第9关路线

第9关代码

作为最后一关,难度自然比较高,判断条件也更加复杂,需要用两个“和”方块完成3个条件的判断。

第10关路线

第10关代码

完成“鸟”关卡后,相信你对如果(if)、否则如果(else if)、否则(else)这几个判断已经有了一个更深刻的认识。