VIVITABLOG

VIVITAで活躍するメンバーの情報発信サイト

Google Blockly ファーストステップ

こんにちは、VIVITAのエンジニアのかっしー(@kassy_vvt) です。本稿ではGoogle Blocklyの導入について記させていただきます。

Scratch 3.0 正式リリース

2019年1月初頭に、ビジュアルプログラミング開発環境のScratchの新バージョン、3.0が正式にリリースされました。VIVISTOP内でもScratchでゲーム等を開発している子供たちも多いので待望のリリースがついに、といったところです。

f:id:kassy_vvt:20190110202751p:plain

様々な機能追加が施されていますが、最大の変更は従来はFlashで作られていた環境がJavaScriptベースに刷新されたことです。これによりAndroid OSやiOSのタブレットからでも楽しめるようになりました。

そして、3.0からはブロックの配置・接続に関してGoogleのBlocklyというオープンソースライブラリが使用されています。このBlockly、Scratch以外でもさまざまなビジュアルプログラミング環境で使用されており、スタンダードとしての地位を確立しつつあるようです。

f:id:kassy_vvt:20190110204248p:plain

そこで本記事ではBlocklyの導入のファーストステップを整理してみたいと存じます。

Blockly導入

ここでは概要を記します。詳細はGoogleの公式ページhttps://developers.google.com/blockly/ を参照願います。 本稿では要点のみ記します。

Blocklyライブラリできること

Scratchのように手続き型プログラミング言語に準ずるブロックを配置・組み立てできます。組み立てたブロックはJavaScript, Python, PHP, Lua, Dart等の種々の既存言語にエクスポート可能です。逆にJavaScriptなどからブロック状態へのインポートはできませんが、ブロック情報をXML形式でシリアライズすれば保存・ロードが可能です。

環境

残念ながらiOSとAndroidは非推奨となり、Webに開発が一本化されました。AndroidからはWebViewを介しての使用が推奨されています。

ライセンス

Apache Licence v2.0が適用されており、比較的緩い条件で使用することができます。

ダウンロード

GitHubでソースが公開されています。

https://github.com/google/blockly

最新版をzipでダウンロードする場合はこちらのリンク です。

どんなもんか確認

まずはローカルで動かしてみます。 上記リンクからダウンロードした zipを解凍し、demos/index.htmlをブラウザで開きます。Blocklyの各機能を体感できます。

f:id:kassy_vvt:20190110211408p:plain

サイトに組み込み

以下のようにスクリプトを記述すれば自身のサイトに組み込むことができます。基本的にはサーバーに依存せずローカル環境で動作するので、HTML+JavaScriptのみで完結します。

ダウンロードしたフォルダから以下のスクリプトにパスが通るようにします。

blockly_compressed.js
blocks_compressed.js
msgフォルダ

以下、htmlファイル

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Blockly Demo: Generating JavaScript</title>
    <script src="/blockly_compressed.js"></script>
    <script src="/blocks_compressed.js"></script>
    <script src="/msg/js/ja.js"></script>
</head>
<body>
    <div id="blocklyDiv" style="height: 600px; width: 1024px;"></div>

    <xml id="toolbox" style="display: none">
        <category name="Loops" colour="%{BKY_LOOPS_HUE}">
            <block type="controls_repeat_ext">
                <value name="TIMES">
                    <block type="math_number">
                        <field name="NUM">10</field>
                    </block>
                </value>
            </block>
            <block type="controls_whileUntil"></block>
        </category>
        <category name="Math" colour="%{BKY_MATH_HUE}">
            <block type="math_number">
                <field name="NUM">123</field>
            </block>
        </category>
        <category name="Text" colour="%{BKY_TEXTS_HUE}">
            <block type="text"></block>
            <block type="text_print"></block>
        </category>
    </xml>

    <script>
        var toolbox = document.getElementById("toolbox");
        var workspace = Blockly.inject('blocklyDiv', {toolbox: document.getElementById('toolbox')});
    </script>
</body>
</html>

f:id:kassy_vvt:20190111100951p:plain

div要素の blocklyDiv がメインのワークスペースになります。 xml要素の toolbox が左側の部品リストです。このxml要素の中に取り揃えたい部品をblock 要素で追加していくとワークスペースのリストに反映されます。

JavaScriptのコードを吐き出し

組み立てたブロックからは冒頭の複数種の言語のコードをエクスポートすることができます。一例としてJavaScriptコードを吐くコードを記します

...
<head>
...
    <script src="../../javascript_compressed.js"></script>
...
</head>
<body>
    <p>
        <button onclick="showCode()">Show JavaScript</button>
    </p>
...
    <script>
...
        function showCode() {
            Blockly.JavaScript.INFINITE_LOOP_TRAP = null;
            var code = Blockly.JavaScript.workspaceToCode(workspace);
            console.log(code);
        }
    </script>
...

f:id:kassy_vvt:20190111103342p:plain

ブロックをXML化して保存・ロード

組み立てたブロック情報情報を保存・ロードするにはXML形式でシリアライズして保管します。上記のコードから直接Blockを復元することはできません。

...
</head>
<body>
    <p>
...
        <button onclick="showXml()">Show Xml</button>
        <button onclick="loadXml()">Load Xml</button>
    </p>
...
    <script>
...
        function showXml() {
            var xmlDom = Blockly.Xml.workspaceToDom(workspace);
            var xmlText = Blockly.Xml.domToPrettyText(xmlDom);
            console.log(xmlText);
        }


        function loadXml() {
            xmlText = "(シリアライズしたXML)"
            try {
                xmlDom = Blockly.Xml.textToDom(xmlText);
            } catch (e) {
                (エラー処理)
            }
            if (xmlDom) {
                workspace.clear();
                Blockly.Xml.domToWorkspace(xmlDom, workspace);
            }
        }
    </script>
...

カスタマイズ

ブロックを追加・削除する。

上述の通り、xml要素の toolbox が左側の部品リストです。このxml要素の中にブロックの定義を追加・削除することで部品リストに揃える部品を調整することが可能です。

    <category name="Math" colour="%{BKY_MATH_HUE}">
      <block type="math_number">
        <field name="NUM">123</field>
      </block>
      <block type="math_arithmetic"></block>
      <block type="math_single"></block>
    </category>
    <category name="Text" colour="%{BKY_TEXTS_HUE}">
      <block type="text"></block>
      <block type="text_length"></block>
      <block type="text_print"></block>
      <block type="text_reverse"></block>
      <label text="Procedure Definitions"></label>
      <block type="controls_whileUntil"></block>
    </category>

blockの一覧は (blocklyのルートフォルダ)/blocksの下のjsファイルに記述されています。また、 (blocklyのルートフォルダ)/tests/playground.html で全てのブロックを試すことが可能です。 f:id:kassy_vvt:20190112210107p:plain

オリジナルのブロックを作る

Blocklyではオリジナルの機能をもったブロックを作成することができます。そしてオリジナルブロックの作成環境もBlocklyで提供されています。(コードで作成することも可能です。)

上記の どんなもんか確認 の項で開いたページの一番下のリンク Blockly Developer Toolsを開きます。

f:id:kassy_vvt:20190112003321p:plain

ブロックを組み立てて、接続方向、インプットフィールド、色等を詰めていきます。 完成したら同ページ右側ペインの Block Definition の右プルダウンからJavaScriptを選び、該当コードをコピーします。

f:id:kassy_vvt:20190112210902p:plain

ペースト先のJavaScriptファイルを作成します。場所は (blocklyのルート)/blocks/(任意の名前).js とします。ここに以下のようなスクリプトを記載します。

'use strict';

goog.provide('Blockly.Constants.(任意の文字列)');

goog.require('Blockly.Blocks');
goog.require('Blockly');

// (以下さっきコピーしたコード)
Blockly.Blocks['custom_block1'] = {
  init: function() {
    this.appendValueInput("input1")
        .setCheck("Number")
        .appendField("test");
    this.setColour(230);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

次にこの新製ブロックをコードに(ここではJavaScript)エクスポートしたときのコードを定義します。Developer Tools右ペイン下の Generator stubのプルダウンをJavaScriptを選び、コードをコピーします。

f:id:kassy_vvt:20190112010904p:plain

貼り付け先のjsファイルを作成します。場所は (blocklyのルート)/generators/javascript/(任意の名前).js です。記述は以下のようになります。

'use strict';


goog.provide('Blockly.JavaScript.(任意の文字列)');

goog.require('Blockly.JavaScript');

Blockly.JavaScript['custom_block1'] = function(block) {
  var value_input1 = Blockly.JavaScript.valueToCode(block, 'input1', Blockly.JavaScript.ORDER_ATOMIC);
   //ここで吐き出すコードを考慮の上修正する
  var code = 'test_custom();\n';
  return code;
};

blockの定義を追加したら全体ビルドをかけて、blocks_compressed.js を更新する必要があります。コマンドラインから以下のコマンドを実行します。(予めPythonのインストールが必要です。)

$ cd (blocklyのルートフォルダ)
$ python build.py

ここでビルドに際し、追加の Closure Library が必要になります。こちらのページ https://developers.google.com/blockly/guides/modify/web/closure?hl=jaからダウンロードして、Blocklyのrootフォルダと同じ階層に配置します。

ビルドが成功したら導入ページのtoolboxにblockを追加します。categoryを分けることも可能ですが以下の例ではしれっとLoopsのcategoryの一番うえに挿入しています。

...
<body>
    <xml id="toolbox" style="display: none">
        <category name="Loops" colour="%{BKY_LOOPS_HUE}">
            <block type="custom_block1"></block>
...

f:id:kassy_vvt:20190112010648p:plain

作成したブロックが現れたら成功です。

文言リソースを定義する

(blocklkyのルートフォルダ)/msg/json/ の配下が文言のリソースファイル群です。英語は en.json, 日本語は ja.json に記述します。文言を追加した場合は上と同じく python build.py のビルドを経て使用可能になります。

ビルドが成功したら、htmlファイルのhead内に以下の一文を追加するのを忘れずに。

  <script src="../../msg/js/(言語).js"></script>

ブロックの形状を変更する

ブロックの形状はsvgで描画されており、すべてプログラム的に定義されていますので、形状のカスタマイズも可能です。(下手にいじると形状が破綻する可能性もあります。)ここでは簡単にいじれる部分のみ紹介します。

(bloklyのルートフォルダ)/core/block_render_svg_js を開くと、ブロックに関するサイズ関連の定義が並んでいますので、これを変更します。 変更した後はやはり python build.py でビルドします。

/**
 * Minimum height of a block.
 * @const
 */
Blockly.BlockSvg.MIN_BLOCK_Y = 25;
/**
 * Height of horizontal puzzle tab.
 * @const
 */
Blockly.BlockSvg.TAB_HEIGHT = 20;
/**
 * Width of horizontal puzzle tab.
 * @const
 */
// Blockly.BlockSvg.TAB_WIDTH = 8;
Blockly.BlockSvg.TAB_WIDTH = 4;
/**
 * Width of vertical tab (inc left margin).
 * @const
 */
Blockly.BlockSvg.NOTCH_WIDTH = 30;
/**
 * Rounded corner radius.
 * @const
 */
Blockly.BlockSvg.CORNER_RADIUS = 8;

f:id:kassy_vvt:20190112210248p:plain

とりあえず、今回はここまでとさせてください。また機会があればもっと詳しいことや凝ったことを書かせていただきたく存じます。