在前面的章節中,我們已經學習了如何使用 Blockly 的基本功能。然而,Blockly 的真正強大之處在於它的可擴展性,特別是創建自定義積木的能力。本章將深入探討如何創建自定義積木,以滿足特定的應用需求。
雖然 Blockly 提供了豐富的內建積木,但在實際應用中,我們經常需要創建自定義積木,原因如下:
特定領域功能:為特定領域(如機器人控制、數據分析、遊戲開發等)提供專門的積木。
簡化複雜操作:將多個基本操作組合成一個積木,簡化使用者的操作。
整合外部 API:創建與外部 API 或服務交互的積木。
自定義視覺效果:創建具有特定視覺效果或行為的積木,以提升使用者體驗。
創建自定義積木的第一步是定義積木的結構和行為。Blockly 使用 JavaScript 對象來定義積木。
以下是一個基本的積木定義示例:
Blockly.Blocks['example_block'] = {
init: function() {
this.appendDummyInput()
.appendField("這是一個示例積木");
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(230);
this.setTooltip("這是一個示例積木的提示文字");
this.setHelpUrl("https://example.com/help");
}
};
這個定義創建了一個名為 'example_block' 的積木,它有以下特性:
- 顯示文字「這是一個示例積木」
- 可以連接到其他積木的上方和下方
- 顏色為藍色(色調值 230)
- 有提示文字和幫助 URL
Blockly 積木可以有多種類型的輸入:
虛擬輸入(Dummy Input):用於添加標籤、圖像等,不接受其他積木的連接。
this.appendDummyInput()
.appendField("標籤文字");
值輸入(Value Input):接受返回值的積木連接。
this.appendValueInput("VALUE")
.setCheck("Number") // 限制只接受數字類型的積木
.appendField("數值");
語句輸入(Statement Input):接受語句積木的連接,用於創建包含其他語句的積木(如迴圈、條件判斷等)。
this.appendStatementInput("DO")
.appendField("執行");
字段是積木上的交互元素,如文字標籤、下拉菜單、複選框等。
文字字段:顯示固定文字。
this.appendDummyInput()
.appendField("文字標籤");
下拉菜單:允許使用者從預定義的選項中選擇。
this.appendDummyInput()
.appendField("選擇")
.appendField(new Blockly.FieldDropdown([
["選項1", "OPTION1"],
["選項2", "OPTION2"],
["選項3", "OPTION3"]
]), "DROPDOWN");
文字輸入:允許使用者輸入文字。
this.appendDummyInput()
.appendField("名稱")
.appendField(new Blockly.FieldTextInput("默認文字"), "NAME");
數字輸入:允許使用者輸入數字。
this.appendDummyInput()
.appendField("數量")
.appendField(new Blockly.FieldNumber(0, 0, 100), "QUANTITY");
複選框:允許使用者選擇是/否。
this.appendDummyInput()
.appendField("啟用")
.appendField(new Blockly.FieldCheckbox("TRUE"), "ENABLED");
顏色選擇器:允許使用者選擇顏色。
this.appendDummyInput()
.appendField("顏色")
.appendField(new Blockly.FieldColour("#ff0000"), "COLOUR");
Blockly 積木可以有不同的類型和形狀,取決於它們在程式中的角色。
語句積木代表程式中的一個語句或指令。它們有上下連接點,可以垂直連接形成程式的執行流程。
Blockly.Blocks['statement_block'] = {
init: function() {
this.appendDummyInput()
.appendField("執行操作");
this.setPreviousStatement(true, null); // 允許連接到上方積木
this.setNextStatement(true, null); // 允許連接到下方積木
this.setColour(230);
}
};
值積木返回一個值,可以插入到其他積木的值輸入槽中。它們通常有圓角的外形。
Blockly.Blocks['value_block'] = {
init: function() {
this.appendDummyInput()
.appendField("返回值");
this.setOutput(true, "Number"); // 設置為輸出積木,返回數字類型
this.setColour(160);
}
};
複合積木結合了語句和值的特性,可以同時接受輸入和提供輸出。
Blockly.Blocks['compound_block'] = {
init: function() {
this.appendValueInput("INPUT")
.setCheck("Number")
.appendField("處理數值");
this.appendStatementInput("DO")
.appendField("執行");
this.setOutput(true, "Number"); // 設置為輸出積木
this.setColour(290);
}
};
Blockly 提供了一個強大的開發者工具,可以幫助您可視化地創建自定義積木,而不需要手動編寫所有代碼。
Blockly 開發者工具是一個網頁應用程式,可以在 https://blockly-demo.appspot.com/static/demos/blockfactory/index.html 訪問。
使用步驟:
1. 在左側的工作區中,使用積木來設計您的自定義積木的外觀和行為。
2. 右側會實時顯示生成的積木預覽和 JavaScript 代碼。
3. 您可以複製生成的代碼,並將其粘貼到您的專案中。
4. 您還可以保存您的積木設計,以便稍後繼續編輯。
以下是使用 Blockly 開發者工具創建一個自定義積木的示例:
假設我們要創建一個「發送 HTTP 請求」的積木,它接受 URL 和方法作為輸入:
1. 在積木工廠中,拖曳積木來設計我們的自定義積木。
2. 添加一個文字字段「發送 HTTP 請求」。
3. 添加一個下拉菜單,包含 GET、POST、PUT、DELETE 等選項。
4. 添加一個值輸入,用於 URL。
5. 設置積木的顏色、提示文字等。
6. 複製生成的代碼。
生成的代碼可能如下:
Blockly.Blocks['http_request'] = {
init: function() {
this.appendDummyInput()
.appendField("發送 HTTP 請求")
.appendField(new Blockly.FieldDropdown([
["GET", "GET"],
["POST", "POST"],
["PUT", "PUT"],
["DELETE", "DELETE"]
]), "METHOD");
this.appendValueInput("URL")
.setCheck("String")
.appendField("URL");
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(230);
this.setTooltip("發送 HTTP 請求到指定的 URL");
this.setHelpUrl("");
}
};
除了基本的積木定義外,Blockly 還提供了許多進階功能,可以創建更複雜和交互性更強的積木。
您可以使用 `setCheck` 方法來限制可以連接到輸入的積木類型:
// 只接受數字類型的積木
this.appendValueInput("NUMBER")
.setCheck("Number")
.appendField("數字");
// 接受多種類型的積木
this.appendValueInput("VALUE")
.setCheck(["Number", "String", "Boolean"])
.appendField("值");
// 接受任何類型的積木
this.appendValueInput("ANY")
.appendField("任何值");
您可以創建能夠根據使用者輸入或其他條件動態更新的積木:
Blockly.Blocks['dynamic_block'] = {
init: function() {
this.appendDummyInput()
.appendField("類型")
.appendField(new Blockly.FieldDropdown([
["數字", "NUMBER"],
["文字", "TEXT"],
["布林", "BOOLEAN"]
], this.updateShape), "TYPE");
this.appendValueInput("VALUE")
.appendField("值");
this.setOutput(true, null);
this.setColour(230);
},
// 當下拉菜單值變更時調用
updateShape: function(newValue) {
var block = this.getSourceBlock();
if (!block) return;
// 獲取值輸入
var valueInput = block.getInput("VALUE");
// 根據選擇的類型設置檢查
if (newValue === "NUMBER") {
valueInput.setCheck("Number");
} else if (newValue === "TEXT") {
valueInput.setCheck("String");
} else if (newValue === "BOOLEAN") {
valueInput.setCheck("Boolean");
}
}
};
您可以創建具有可變數量參數的積木,類似於函數可以接受不同數量的參數:
Blockly.Blocks['variable_params'] = {
init: function() {
this.appendDummyInput()
.appendField("函數")
.appendField(new Blockly.FieldTextInput("myFunction"), "NAME");
this.appendValueInput("PARAM0")
.setCheck(null)
.appendField("參數");
this.setMutator(new Blockly.Mutator(['param_item']));
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(290);
this.paramCount_ = 1;
this.updateShape_();
},
// 保存積木的可變參數數量
mutationToDom: function() {
var container = document.createElement('mutation');
container.setAttribute('params', this.paramCount_);
return container;
},
// 從 DOM 恢復積木的可變參數數量
domToMutation: function(xmlElement) {
this.paramCount_ = parseInt(xmlElement.getAttribute('params'), 10);
this.updateShape_();
},
// 在 Mutator 對話框中顯示的積木
decompose: function(workspace) {
var containerBlock = workspace.newBlock('param_container');
containerBlock.initSvg();
var connection = containerBlock.getInput('STACK').connection;
for (var i = 0; i < this.paramCount_; i++) {
var paramBlock = workspace.newBlock('param_item');
paramBlock.initSvg();
connection.connect(paramBlock.previousConnection);
connection = paramBlock.nextConnection;
}
return containerBlock;
},
// 從 Mutator 對話框更新積木
compose: function(containerBlock) {
var paramBlock = containerBlock.getInputTargetBlock('STACK');
var connections = [];
var i = 0;
while (paramBlock) {
connections[i] = paramBlock.valueConnection_;
paramBlock = paramBlock.nextConnection && paramBlock.nextConnection.targetBlock();
i++;
}
this.paramCount_ = connections.length;
this.updateShape_();
// 重新連接值積木
for (var i = 0; i < this.paramCount_; i++) {
if (connections[i]) {
this.getInput('PARAM' + i).connection.connect(connections[i]);
}
}
},
// 保存連接
saveConnections: function(containerBlock) {
var paramBlock = containerBlock.getInputTargetBlock('STACK');
var i = 0;
while (paramBlock) {
var input = this.getInput('PARAM' + i);
paramBlock.valueConnection_ = input && input.connection.targetConnection;
paramBlock = paramBlock.nextConnection && paramBlock.nextConnection.targetBlock();
i++;
}
},
// 更新積木形狀
updateShape_: function() {
// 刪除多餘的輸入
for (var i = this.paramCount_; this.getInput('PARAM' + i); i++) {
this.removeInput('PARAM' + i);
}
// 添加新的輸入
for (var i = 0; i < this.paramCount_; i++) {
if (!this.getInput('PARAM' + i)) {
var input = this.appendValueInput('PARAM' + i)
.setCheck(null);
if (i === 0) {
input.appendField("參數");
}
}
}
}
};
// Mutator 對話框中使用的積木
Blockly.Blocks['param_container'] = {
init: function() {
this.appendDummyInput()
.appendField("參數");
this.appendStatementInput('STACK');
this.setColour(290);
}
};
Blockly.Blocks['param_item'] = {
init: function() {
this.appendDummyInput()
.appendField("參數");
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setColour(290);
this.contextMenu = false;
}
};
在本章中,我們探討了如何創建自定義積木,包括基本積木定義、輸入和字段類型、積木類型與形狀、使用 Blockly 開發者工具以及進階積木功能。通過創建自定義積木,您可以擴展 Blockly 的功能,使其更好地適應特定的應用需求。
在下一章中,我們將學習如何生成程式碼,將視覺化的積木轉換為 JavaScript 和 Python 等程式語言的程式碼。