Blocklyのカスタムブロックの各種設定
この記事では、ビジュアルプログラミング言語のBlocklyで、カスタムブロックを定義する方法を紹介します。
See the Pen
Blockly on CodePen 2 by Imai (@imai1)
on CodePen.
なお、Blocklyの基本的な使い方について知りたい方は前の記事をご覧いただけたら嬉しいです。
- カスタムブロックの追加とツールボックスへの登録
- カスタムブロックの下・上・左にブロックを接続できるよう設定
- カスタムブロックの右にブロックを接続できるよう設定
- Inputのインライン化
- その他の設定
- コード生成の定義
カスタムブロックの追加とツールボックスへの登録
まず、新しいカスタムブロックを定義するためには、Blockly.Blocks[新しいカスタムブロックの識別子]
に要素を追加します。
Blockly.Blocks["block1"] = { init: function() { // 設定を記述。 } }
ブロックから特定の言語へのコード生成を定義する場合は、Blockly.言語[新しいカスタムブロックの識別子]
に要素を追加します。
Blockly.JavaScript["block1"] = function(block) { // コード生成処理を記述。 }
定義したカスタムブロックは、ツールボックスに登録するなどで利用できます。
// ツールボックス内に設置するブロックの定義 var toolbox = { "contents": [ { "kind": "block", "type": "block1" }, ] } // 指定したIDのタグにワークスペース(Blocklyのウィジェット)を注入。 const workspace = Blockly.inject("blocklyDiv", {toolbox: toolbox});
カスタムブロックの下・上・左にブロックを接続できるよう設定
まずはblock1
の定義をご覧ください。ツールボックスの一番上のブロックです。
Blockly.Blocks["block1"] = { init: function() { this.setNextStatement(true); // 下にブロック(次の処理)のジョイントを追加。 this.setPreviousStatement(true, "Number"); // 上にブロック(前の処理)のジョイントを追加。第二引数で型を指定。 this.setOutput(true, "Number"); // 左にブロック(このブロックを引数として受け取る処理)のジョイントを追加。第二引数で型を指定。 this.setColour(160); // HSVで0°〜360°を指定。 } };
init
メソッド内で、setNextStatement
setPreviousStatement
setOutput
を実行することで、カスタムブロックの下、上、左のジョイントを追加することができます。
なお、この三つのメソッドでは第二引数に型を指定できます。次の節で説明する右向きのジョイントにも型を設定することで、一致するブロックのみ接続できるよう制限が可能です。ただし、ここで言う型とは、コード生成先の言語の型などではなく、ユーザが任意に定義できる単なる文字列です。
カスタムブロックの右にブロックを接続できるよう設定
次は先にこちらのコードをご覧ください。ツールボックスの上から2番目のブロックに対応するblock2
の定義です。
Blockly.Blocks["block2"] = { init: function() { // 何も接続しないInputを追加。 this.appendDummyInput() .appendField("ABC") // 文字列を追加。 .appendField("DEF") // 文字列を追加。 .appendField("GHI"); // 文字列を追加。 // setOutput(true)のブロックを接続できるInputを追加。 this.appendValueInput("VALUE") .appendField("JKL") // 文字列を追加。 .appendField(new Blockly.FieldNumber(1), "NUM1") // 初期値1の数値入力を追加。 .appendField(new Blockly.FieldTextInput("xyz"), "TEXT1") // 初期値xyzの文字列入力を追加。 .setCheck("Number"); // 接続を数値型に限定。 // setPreviousStatement(true)のブロックを接続できるInputを追加。 this.appendStatementInput("STATEMENT") .appendField(new Blockly.FieldMultilineInput("abc\ndef"), "TEXT2") // 初期値"abc\ndef"(2行)の複数行文字列入力を追加。 .setCheck("String"); // 接続を数値型に限定。 this.setColour(160); // HSVで0°〜360°を指定。 } };
まず前提として、Blocklyのブロックの内部は、論理的には行列風の構造になっています。一行は一つのInput
と対応し、最大一つの右向きジョイントを設定できます。また、Input
の中にはField
と呼ばれる要素を列方向に並べていきます。
Input
の種類は以下の3つです。
DummyInput
:何も接続できないInput。ValueInput
:setOutput(true)
のブロックを接続できるInput。StatementInput
:setPreviousStatement(true)
のブロックを接続できるInput。
Field
はラベルやパラメータを設定するための要素です。
FieldLabel("文字列")
または単に文字列
:単なるラベル。FieldNumber()
:数値型パラメータの入力欄。デフォルト値を設定する場合は第一引数を追加。FieldTextInput()
:文字列型パラメータの入力欄。デフォルト値を設定する場合は第一引数を追加。FieldMultilineInput()
:文字列型パラメータの複数行入力欄。デフォルト値を設定する場合は第一引数を追加。
コードと表示を見比べたりコードを書き換えたりすると、対応関係がより理解できるのではないかと思います。
なお、appendField
の第二引数は、コード生成時にField
を参照するための識別子です。
setCheck
はInput
に接続されるブロックの型チェックです。冒頭のCodePenで、block2
に対してblock1
を接続しようとすると、VALUE
には接続できるのにSTATEMENT
には接続できないことが確認できます。
Input
のインライン化
Blocklyの利用例において、一行に複数のInput
を持っていたり、Input
の右にもラベルを含んでいるブロックを見たことがあるのではないでしょうか。例えば、日本語で「%1 を %2 に代入」というようなカスタムブロックで、この文章がそのまま一行に横並びしているブロックです。
block3
のようにsetInputsInline(true)
を実行すると、連続するValueInput
とDummyInput
を一行に並べることができます。StatementInput
は必ず一行に独立するセパレータのようになっており、それ以外のInput
が横に連結されます。
Blockly.Blocks["block3"] = { init: function() { // ブロックを諸々定義。 this.setInputsInline(true); // StatementInput以外を1行にまとめる。 } }
その他の設定
setEditable
setDeletable
setMovable
でフィールドパラメータの変更、ブロックの削除、ブロックの移動を制御できます。
Blockly.Blocks["block4"] = { init: function() { this.appendDummyInput() .appendField("消せないし動かせないし変更できない") .appendField(new Blockly.FieldNumber(1)); this.setEditable(false); this.setDeletable(false); this.setMovable(false); } }
例えば教育用に、デフォルトで配置されているブロックを変更されたくない場合などに利用できます。
なお、これらの設定はGUIからの操作のみに適用されます。JavaScriptのコードでblock.dispose()
またはblock.moveBy(dx, dy)
を実行すれば、ブロックを直接削除・移動することはいつでも可能です。
コード生成の定義
ブロックがInput
およびField
から構成されることを把握できていれば、コード生成は簡単に理解できると思います。
Blockly.JavaScript["block2"] = function(block) { const value = Blockly.JavaScript.valueToCode(block, "VALUE", Blockly.JavaScript.ORDER_ATOMIC); const statement = Blockly.JavaScript.statementToCode(block, "STATEMENT"); console.log("VALUE in block2: " + value); console.log("STATEMENT in block2: " + statement); console.log("NUM1 in block2: " + block.getFieldValue("NUM1")); console.log("TEXT1 in block2: " + block.getFieldValue("TEXT1")); console.log("TEXT2 in block2: " + block.getFieldValue("TEXT2")); return ["This is a code of block2", Blockly.JavaScript.ORDER_NONE]; }
valueToCode
:指定した識別子のValueInput
に接続されたブロックのコードを生成。statementToCode
:指定した識別子のStatementInput
に接続されたブロックのコードを生成。getFieldValue
:指定した識別子のField
に接続されたブロックのコードを生成。Field
の識別子はappendField
の第二引数で指定。
block2
およびその他のブロックを適当に接続して「実行」ボタンを押すと、ブラウザのコンソールにblock2
の接続先のブロックのコードが出力されるはずです。動作確認用に実装したのでJavaScriptのコードとしては意味を為していませんが、実際に触ってみて確かめてみてください。
カスタムブロックの右に接続されたブロックをコード化する方法がわかったら、後はカスタムブロックのコード生成をスクラッチで自由に定義するだけです。
Blockly.JavaScript.ORDER_ホニャララ
は、Blocklyがブロックの処理の優先順序(和より積の方が優先、など)に応じて自動的に括弧を追加してくれる機能のために定めるものらしいです。コード生成処理に優先順序付けのための括弧を適切に設定しさえすれば、valueToCode
にはORDER_ATOMIC
を、コード生成関数の戻り値ではORDER_NONE
に固定して問題ないとのことです。