拼图插件参考 - Soft8Soft
Description: Verge3D拼图编辑器的自定义插件系统。

# 插件 | Plugins

Verge3D拼图编辑器提供 了加载您自己自定义拼图的能力， 从而使您可以用您一直想要的功能 来扩展编辑器的功能。

## 内容

- 安装插件
- 插件文件概述
  - init.plug File Format
    - Category
      - 工具箱中的条目名称
      - 工具箱中的条目颜色
      - 可用的拼图
      - 拼图顺序
      - 文本标签
      - 分隔符
      - 在 "init" 初始化标签中可用的拼图模块
      - 默认的输入和字段值
      - 工具箱内的子类目
    - Script
  - .block File Format
    - Block Template
      - Block的颜色
      - Block的工具提示
      - Block的帮助URL
      - 添加输入插槽
      - 输入的排列
      - 添加字段
      - 字段排列
      - 模块的连接
      - 输入/输出类型检查
    - Code Function
      - 基本的代码生成
      - 缓解代码臃肿问题
      - Using PzLib API
      - 访问输入和字段
- 插件和图模块的错误
  - BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "This page contains the following errors:error on line ..."
  - BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "ReferenceError (SyntaxError, TypeError, etc...) ..."
  - BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Child block does not have output or previous statement."
  - BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "Error: Connection checks failed. Output/Previous/Next Connection of "PLUGIN_NAME/BLOCK_NAME" block (id="BLOCK_ID") expected TYPE_CHILD, found TYPE_PARENT"
  - BlockError(PLUGIN_NAME/null): validation error - "TypeError: Unknown block type: PLUGIN_NAME/null"
  - BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Unknown block type: PLUGIN_NAME/BLOCK_NAME"
  - BlockError(PLUGIN_NAME/BLOCK_NAME): error calling template() function ...
  - BlockError(PLUGIN_NAME/BLOCK_NAME): error calling code() function ...
  - PluginError(PLUGIN_NAME): error parsing init.plug file - "This page contains the following errors:error on line ..."
  - PluginError(PLUGIN_NAME): error parsing init.plug file - "ReferenceError (SyntaxError, TypeError, etc...) ..."
  - PluginError(PLUGIN_NAME): error calling code() function ...
  - Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.
- 分享您的插件

#

## 安装插件

插件是包含一堆与插件相关的文件的目录。 插件应该放在Verge3D的 puzzles/plugins 文件夹中， 以便被拼图编辑器识别。一切就绪~ 重新加载编辑器页面后，所有已安装的插件就会出现 在编辑器工具箱的底部，紧随所有标准拼图类目之后。

工具箱中的插件类目。

提供给插件开发者：
这里有一个实用的参考插件实例，包含了一些典型的 拼图模块：ExamplePlugin.zip。 您可以将其解压到 puzzles/plugins 文件夹中， 然后在工具箱中查看 "Example Plugin " 的拼图类目。

#

## 插件文件概述

一个典型的插件是一个包含init.plug文件的目录，其中包含了插件的 通用设置，还有一堆 *.block 文件，每个文件都定义一个拼图 模块。如果您使用的文本编辑器支持语法高亮， 那么请在开启这两种文件格式时使用HTML模式。

#

### init.plug File Format

init.plug是一个强制性的插件文件，它用于 指定常规的插件设置。在该文件中，您可以定义 工具箱的条目是什么样子的。您也可以在那里为 拼图添加一些初步的javascript代码。下面这个简单的 示例列出了您将在init.plug文件中看到的内容：

`<category name="My Awesome Plugin" color="green"> <label text="My Awesome Plugin v1.0"></label> <block type="myPuzzle"></block> <block type="doSomethingCool"></block> </category> <script> function code() { return `console.log('Powered by My Awesome Plugin!');`; } </script>`

#

#### Category

category部分是一个XML树，它定义了插件及其 拼图模块在拼图编辑器工具箱中的显示方式。虽然 它是可选的，但如果 init.plug 文件中没有 category ， 那么此插件不会被加载到拼图编辑器中。
您可以通过category设置以下几个选项：

#

##### 工具箱中的条目名称

通过name属性指定。 `<category name="My Awesome Plugin"></category>`

#

##### 工具箱中的条目颜色

通过color属性指定。 `<category name="My Awesome Plugin" color="green"></category>` 可以用以下格式来定义颜色：

- 十六进制三联体，如： #f61 或 #f0562f
- 在0°-360°范围内的色相值，例如： 140 （在HSV颜色 模型的范围内，S和V分别固定为45%和65%）。
- 颜色关键词，如： aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, purple, red, silver, teal, white 和 yellow

#

##### 可用的拼图

要使一个拼图模块在插件的工具箱类目中可用， 应该通过 block 元素和其 type 属性来指定。 type属性可以参考以下拼图：

- 由插件自己定义的拼图——在这种情况下， type 应该根据您的插件目录中相应的 .block 文件的名称来设置（例如，myPuzzle 表示block文 件 my_awesome_plugin/myPuzzle.block ) `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> </category>`
- 库存拼图模块 - type应该等于 库存拼图的类型（例如：math_number）。 这样就可以直接在您的插件的工具箱类目中添加 库存拼图。 `<category name="My Awesome Plugin" color="green"> <block type="math_number"></block> </category>`
- 由其他插件定义的拼图—— type 应该 是 PLUGIN_DIRECTORY_NAME/PUZZLE_TYPE 的 形式。例如，标准的 电子 商务 插件的拼图可以这样引用： `<category name="My Awesome Plugin" color="green"> <block type="E-Commerce/placeOrder"></block> </category>`

为查找出某个拼图模块的类型，您可以使用拼图上下文 菜单中的 "Print Puzzle XML Tree(打印拼图XML树)" 选项：

该菜单选项可将拼图的XML树打印到 浏览器控制台。您可以在其中找到拼图类型 以及整个XML结构，可用于在设置 默认的 输入和字段值 时参考。

"Print Puzzle XML Tree(打印拼图XML树)" 菜单选项 提供了一个简单的方法，可以一次性将一整组拼图模块添 加到您的插件中。这样便于您创建某种由相连的拼图模块 组成的 "代码片段(snippets)" 并将其放入插件中。这使 得它有点类似于 拼图库 。

下面的示例解释了如何执行这一操作：

- 在拼图编辑器中创建一些拼图模块：
- 用鼠标右键点击最上面或最外部的模块， 选择 "Print Puzzle XML Tree(打印拼图XML树)" ：
- 进入浏览器控制台，复制打印出来的 内容（下面是在谷歌Chrome浏览器中的截图）：
- 把它粘贴到init.plug文件中的 "category" 元素内， 并保存该文件：
- 然后重新加载拼图编辑器，您所复制的模块 就会出现在插件的类目中。

#

##### 拼图顺序

拼图模块按照您在init.plug文件中 定义的顺序在工具箱中排列。 `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> <block type="doSomethingCool"></block> <!-- <block type="testPuzzle"></block> --> <block type="anotherPuzzle"></block> </category>`
注意，被注释掉的拼图模块 "testPuzzle" 并没有显示在 工具箱中。

#

##### 文本标签

您可以通过label元素将文本标签添加到工具箱类目中：

`<category name="My Awesome Plugin" color="green"> <label text="My Awesome Plugin v1.0"></label> <label text="Main Puzzles:"></label> <block type="myPuzzle"></block> <block type="doSomethingCool"></block> <label text="Other:"></label> <block type="anotherPuzzle"></block> </category>`

label元素不适合显示多行文本， 即不会换行。

label元素也可以在一定程度上定义样式。 它们支持 web-class 属性，可用于为 label 元素 分配自定义的CSS类。该类的CSS规则可以 在 init.plug 的 script 部分定义。 下方示例描述了实现方式：

`<category name="Example Plugin" color="#a52a2a"> <label text="Example Plugin v1.0 by Soft8Soft" web-class="example-plugin__label"></label> </category> <script> const styleElem = document.createElement('style'); styleElem.innerHTML = ` .example-plugin__label .blocklyFlyoutLabelText { fill: #a52a2a; font-style: italic; font-weight: bold; text-decoration: underline; } `; document.head.appendChild(styleElem); </script>`

这是应用自定义CSS规则后的样子：

在为web-class属性规划CSS类名时， 建议考虑限定CSS范围。例如使用拼图插 件名作为专有前缀：在上面的例子中使用 了 "example-plugin__label" 类的 "example-plugin" 部 分作为前缀。这样，意外破坏页面中已经 使用的CSS类的可能性就很小了。

#

##### 分隔符

分隔符可以用来改变拼图模块之间的距离。 您可以通过sep元素在工具箱类目中添加分隔符。 gap属性指定了间隙的宽度，单位为像素。 不使用sep时，模块之间的默认距离等于24像素。 `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> <sep gap="0"></sep> <block type="doSomethingCool"></block> <sep gap="80"></sep> <block type="anotherPuzzle"></block> </category>`

#

##### 在 "init" 初始化标签中可用的拼图模块

默认情况下，拼图模块 只在main和用户创建的选项卡中可用， 在init(初始化)选项卡中不可用。这是 因为init选项卡所产生的代码是在Verge3D应用 被加载与完全初始化之前执行的。这意味着那些 预期用于3D场景、3D对象、材质的拼图并不适合 在init中使用，如果使用会导致应用崩溃。 但是，对于不作用于3D场景的拼图应该不会有这样的问题。 例如，可以在init中使用预加载资源拼图 或设置用户界面的拼图。
如果需要让拼图模块出现在init标签的工具箱中， 您需要在拼图的block元素中设置allow-init属性 为true（这也适用于label和sep元素）。 `<category name="My Awesome Plugin" color="green"> <label text="My Awesome Plugin v1.0" allow-init="true"></label> <label text="Main Puzzles:"></label> <block type="myPuzzle" allow-init="true"></block> <block type="doSomethingCool"></block> <label text="Other:"></label> <block type="anotherPuzzle"></block> </category>`
注意，没有allow-init的block和label元素是 不会显示在工具箱中的。

#

##### 默认的输入和字段值

如果一个拼图模块有模块输入和/或字段输入， 那么您可以指定它们的占位模块和/或默认值。 模块输入即连接其他拼图模块的插槽，字段输入 即不以拼图模块为形态的UI元素，如选择器、复 选框、文本字段等.这个功能有两个目的：为用户 提供提示，告诉他们可以插入哪些类型的模块到 输入插槽中，同时也可快速调用拼图模块。
比方说，您的拼图模块有一个名为 "myNumber" 的 输入。这里您可以添加一个math_number类型的 占位符模块插入该槽。 `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <value name="myNumber"> <block type="math_number"></block> </value> </block> </category>` 其外观如下：
插入输入插槽的占位符模块也可以 是一个shadow(影子)模块。shadow模 块与普通模块基本相同，但它们可被您插入 到相应输入插槽中的模块自动替换，当您从 插槽中移除该模块时，它们又会自动出现。 这使得shadow模块比普通的占位符模块 更容易使用。
shadow模块的 定义方式与普通占位符 模块几乎相同，唯一 的区别是block元素 被类似的shadow元素 所取代。 `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <value name="myNumber"> <shadow type="math_number"></shadow> </value> </block> </category>` 它看起来会是这样的：
拼图模块可以有语句输入，这些输入通常 包裹着一系列的子拼图模块。比方说，您 的拼图模块有一个名为 "myStatement" 的 语句输入，那么您可以在这个输入中添加几个占位符模块，如下所示： `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <statement name="myStatement"> <block type="addHTMLElement"> <next> <block type="setHTMLElemAttribute"></block> </next> </block> </statement> </block> </category>` 这里使用的statement元素通过name属性 引用 "myStatement" 输入，并在输入中添加 了一些占位符模块。另外，这里还使用了next元素， 用于链接一系列的占位符模块。这个设置的结果如下所示：
如果您的拼图模块有一个名为 "myCheckbox" 的复选框字段， 那么您可以像这样定义它的默认状态（true - 启用， false - 禁用）： `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <field name="myCheckbox">true</field> </block> </category>` 结果如下：
通过使用占位符模块和默认字段值，您可 以定义 复杂的复合拼图， 类似于可以 添加 到 拼图库中的内容：
上图中复杂拼图设置的init.plug的代码 示例如下： `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"> <statement name="STATEMENT_0"> <block type="whenClicked"> <value name="VALUE"> <block type="objectList"> <field name="FIELDNAME">Cube</field> </block> </value> </block> </statement> <statement name="STATEMENT_1"> <block type="loadScene"> <value name="URL"> <block type="text"> <field name="TEXT">my_scene.gltf</field> </block> </value> </block> </statement> <statement name="STATEMENT_2"> <block type="show"> <value name="VALUE"> <block type="objectList"> <field name="FIELDNAME">something</field> </block> </value> </block> </statement> </block> </category>`

查看 "Print Puzzle XML Tree(打印拼图XML树)" 上下文菜单选项。它有助于您找出感兴趣的 拼图模块的XML结构（输入和字段的配置）。

#

##### 工具箱内的子类目

在编辑器的工具箱中，一个 类目可以有子类目，而子类目 也可以有子类目，以此类推... 如果您想把插件拼图组织成一个树状结构， 可以使用此功能。
通过 嵌套category元素来实现： `<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> <category name="1" color="red"> <category name="1.1" color="silver"> <block type="anotherPuzzle"></block> </category> </category> <category name="2" color="blue"> <block type="doSomethingCool"></block> </category> </category>`
任何category都可以同时包含 category 和 block 元素 （但这不是强制性的）。这样在一个父级类目中可 同时包含拼图模块和子类目。

#

#### Script

<script> 元素是 init.plug 的一个可选的部分。 它可以用来为拼图模块添加一些初始化代码。 有时候，在您的拼图被使用之前，您可能需要 进行繁重的计算和缓存一些数据——这 就是 <script> 的作用。

如果您在 <script> 中定义了一个 code() 函数， 它将被用来生成代码，并在所有拼图之前执行一次。这个 code() 函数 会返回一个包含javascript代码的字符串。 `<script> function code() { // this line will be executed before any puzzles return `console.log('Powered by My Awesome Plugin!');`; } </script>`

由 code() 函数返回的初始化代码 只有在该插件的拼图在应用中被实际使用时， 才会被添加到生成的逻辑文件中（被添加到工作区且未被禁用）。

#

### .block File Format

以 .block 为扩展名的插件文件是用来定义 单个拼图模块的，特别是定义一个拼图模块的外观以 及它在添加到工作区时应该产生的代码。一个插件可以 没有 .block 文件。如果您想创建一个只有 库存 拼图 模块的工具箱类目（可以包含 更复杂的模块设置）， 可使用此方法。
一个 .block 文件的名称被用来指示哪些拼图模块 应该被 包含在插件的工具箱类目 中。

下面是一个 .block 文件的最小化实例。 `<template color="green"> <dummy> <label>myPuzzle</label> </dummy> </template> <script> function code(block) { return `console.log('This is my first puzzle!');`; } </script>` 这里我们有一个 <template> 元素， 定义了拼图模块的外观。还有一个 <script> 元素， 里面有一个 code() 函数。这个 code() 函数返回 一个字符串，其中包含将生成的代码， 以代替这个拼图。
因此，根据上面的实例，我们期望得到的 简单拼图模块是绿色的，并且有一个 "myPuzzle" 文本标签。
如果将其添加到工作区，它将会在浏览器 控制台中打印出以下信息：

#

#### Block Template

拼图模块的外观可以通过两种方式定义： 通过 <template> XML元素和通过 template() 函数。 前者更简单易用。例如，一个典型 拼图的 <template> 可以是这样的：

`<template color="green" inline="true" output="Dictionary" tooltip="This is my first puzzle!" help="https://soft8soft.com" > <dummy name="myDummyInput"> <label>enable</label> <checkbox name="myCheckbox">true</checkbox> </dummy> <value name="myValueInput"> <label>input value</label> </value> </template> <script> function code(block) { return `console.log('This is my first puzzle!');`; } </script>`

另一种方法是使用 template() 函数。 这是一个您可以在 <script> 元素内定义的函数。 它也可以用来配置拼图的外观， 但这次是通过 Blockly JavaScript API 而 非XML元素与属性配置的。 它会接收 Blockly.BlockSvg 实例型的block参数。
可以通过使用 template() 函数来重写上面实例中的模块， 如下所示：

`<script> function template(block) { block.setColor('green'); block.setInputsInline(true); block.setOutput(true, 'Dictionary'); block.setTooltip('This is a test puzzle!'); block.setHelpUrl('https://soft8soft.com'); block.appendDummyInput('myDummyInput') .appendField('enable') .appendField(new Blockly.FieldCheckbox(true), 'myCheckbox'); block.appendValueInput('myValueInput') .appendField('input value'); } function code(block) { return `console.log('This is my first puzzle!');`; } </script>`

这种方法更加灵活，但需要对相应的API有所了解。 如果您需要做一些无法通过 <template> 元素 实现的设置，可使用这种方法。 此外，您可以同时 使用 <template> 和 template() 。

本节提供了 <template> （XML） 和 template()(JS)两种变体的示例。

请注意，本节只是简要介绍了如何创建一个 自定义拼图模块。 关于进行常规定制的详细介绍， 请查看谷歌Blockly关于 自定义模块 和 字段 的文档。

#

##### Block的颜色

您可以设置拼图模块的颜色，使其外观更加独特。

XML
可以通过 color 属性来设置。 `<template color="green"></template>`
JS
可以通过 block.setColor 来设置。 `<script> function template(block) { block.setColor('green'); } </script>`

颜色必须是这里描述的格式之一： 颜色格式。

#

##### Block的工具提示

您可以添加一个工具提示，当把鼠标 悬停在一个模块上面时就会出现。工具 提示用于向用户提供简单的描述，如拼图 的用途，作用方式的，有哪些使用提示等等。

XML
可以通过 tooltip 属性来设置。 `<template tooltip="This is my first puzzle!"></template>`
JS
可以通过 block.setTooltip 来设置。 `<script> function template(block) { block.setTooltip('This is my first puzzle!'); } </script>`

#

##### Block的帮助URL

如果 工具提示 不足 以记录拼图描述，您也可以为其添加一个链接， 指向一个有更详尽文档说明的网页。此链接 将被用于拼图上下文菜单中的帮助条目（在拼图上点击右键）：

XML
可以通过 help 属性设置： `<template help="https://www.soft8soft.com/"></template>`
JS
可以通过 block.setHelpUrl 来设置。 `<script> function template(block) { block.setHelpUrl('https://www.soft8soft.com/'); } </script>`

#

##### 添加输入插槽

拼图模块可以包含输入插槽来插入其他模块。 它们也可以使用非模块的UI元素， 如复选框或文本字段。 有3种不同类型的输入： value input ， statement inputs 和 dummy inputs 。

- value input - 可用于插入具有 返回值（输出连接）模块的输入插槽， 例如，带有一些数学模块的输入——数学 模块会计算并返回数值，可由父模块 （包含输入插槽的模块）访问。
- statement input - 可用于插入 一组代表一系列连续动作的模块的输入， 例如 if-else 拼图 就有这种类型的输入， 用于在 "if" 或 "else" 条件下将拼图模块分组。
- dummy inputs - 此类输入插槽 只适用于添加非模块式的用户界面元素， 如复选框、文本字段、图片等。 更多信息见 添加字段 。

您可以在下图中看到这些类型的 输入的区别。

XML
使用 <value> ， <statement> 和 <dummy> 等元素 来添加输入。value 和 statement 输入 必须有一个名称（使用它们的 name 属性）。 dummy 输入通常不需要名字， 它们只是 字段 的容器。 `<template> <value name="myInput"></value> <statement name="myStatement"></statement> <dummy> <checkbox name="myCheckbox">true</checkbox> </dummy> </template>`
JS
使用 block.appendValueInput , block.appendStatementInput 和 block.appendDummyInput 方法来添加输入。 value 和 statement 输入 必须（通过name属性）被命名。 dummy 输入通常不需要名字， 它们只是 字段 的容器。 `<script> function template(block) { block.appendValueInput('myValue'); block.appendStatementInput('myStatement'); block.appendDummyInput() .appendField(new Blockly.FieldCheckbox(true), 'myCheckbox'); } </script>`

#

##### 输入的排列

模块状输入可以垂直（默认）或 水平排列。

XML
可以通过 inline 设置为 true 或 false 的属性。 为 false 时使用垂直排列， 为 true 时使用水平排列的变体。 `<template inline="true"></template>`
JS
可以通过 block.setInputsInline 方法设置。该方法可接收 一个为 true 或 false 的参数。为 false 时使用垂直排列， 为 true 时使用水平排列的变体。 `<script> function template(block) { block.setInputsInline(true); } </script>`

#

##### 添加字段

您可以在拼图中添加诸如文本标签、复选框、下拉列表、文本输入等UI元素。 这些UI元素被称为 "fields(字段)" 。 它们可被添加到任何类型的输入中， 如果您不想为拼图模块额外创建输入插槽， 可一致使用 dummy input 输入。

所有字段都有几个共同点：

- 可以指定一个可选的 name 参数， 这通常用于在 code() 函数中 引用该字段， 以获得该字段的值。
- 能够提供一个可选的默认值。 如前所述，在init.plug的 类目 中 指定的默认值 始终比在拼图模板中定义的默认值 具有更高的优先级。

让我们来看看如何在拼图中添加各种字段。

- label - 一个不可编辑的文本字段

  XML
  可以通过 <label> 元素嵌套在 dummy 、 value 或 statement 输入中添加。 `<template> <dummy> <label>a text label</label> </dummy> </template>`
  JS
  可以通过 appendField 方法 和 Blockly.FieldLabel 添加到 dummy ， value 或 statement 输入。 `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldLabel('a text label')); } </script>`

- text - 一个可编辑的文本字段

  XML
  可以通过 <text> 元素嵌套在 dummy ， value 或 statement 输入中添加。 `<template> <dummy> <text name="myText">default text</text> </dummy> </template>`
  JS
  可以通过 appendField 方法和 Blockly.FieldTextInput 添加到 dummy ， value 或 statement 输入。 `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldTextInput('default text'), 'myText'); } </script>`

- multiline - 一个可编辑的多行文本字段

  XML
  可以通过 <multiline> 元素嵌套在 dummy , value 或 statement 输入中添加。 `<template> <dummy> <multiline name="myTextMultiline">This is a \n multiline \n text</multiline> </dummy> </template>`
  JS
  可以通过 appendField 方法和 Blockly.FieldMultilineInput 添加到 dummy , value 或 statement 输入。 `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldMultilineInput('This is a \n multiline \n text'), 'myTextMultiline'); } </script>`

- number - 一个可编辑的数值字段

  XML
  可以通过 <number> 元素嵌套在 dummy , value 或 statement 输入中添加。 `<template> <dummy> <number name="myNumber">3</number> </dummy> </template>`
  JS
  可以通过 appendField 方法和 Blockly.FieldNumber 添加到 dummy , value 或 statement 输入。 `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldNumber(3), 'myNumber'); } </script>`

- angle - 一个可编辑的字段，用于表示角度的数值（单位：度）。

  XML
  可以通过 <angle> 元素嵌套在 dummy , value 或 statement 输入中添加。 `<template> <dummy> <angle name="myAngle">15</angle> </dummy> </template>`
  JS
  可以通过 appendField 方法和 Blockly.FieldAngle 添加到 dummy , value 或 statement 输入。 `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldAngle(15), 'myAngle'); } </script>`

- checkbox - 一个典型的复选框字段

  XML
  可以通过 <checkbox> 元素嵌套在 dummy, value 或 statement 输入中添加。 默认的复选框状态 应该被设置为 true 或 false 。 `<template> <dummy> <checkbox name="myCheckbox">true</checkbox> </dummy> </template>`
  JS
  可以通过 appendField 方法和 Blockly.FieldCheckbox 添加到一个 dummy , value 或 statement 输入。 默认的复选框状态作为参数 传递给 Blockly.FieldCheckbox ，可以是 true 或 false 。 `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldCheckbox(true), 'myCheckbox'); } </script>`

- dropdown - 一个可供选择的值的列表

  XML

  可以通过 <dropdown> 元素嵌套在 dummy, value 或 statement 输入中添加。

  为了创建选项列表，在 <dropdown> 内 添加 <option> 元素。 所有 <option> 元素的 name 属性 必须设置为一个唯一的标识符 （更适合在代码中使用的东西， 例如COLOR_GREEN）。 在开始和结束的 <option> 标签之间 包含的文本被用作该选项的 下拉部件中显示的标签。

  要设置默认选择， 可以将选项的 default 属性设置为 true 。

  `<template> <dummy> <dropdown name="myDropdown"> <option name="COLOR_GREEN">green</option> <option name="COLOR_YELLOW" default="true">yellow</option> <option name="COLOR_RED">red</option> </dropdown> </dummy> </template>`
  JS

  可以通过 appendField 方法和 Blockly.FieldDropdown 添加到 dummy 、 value 或 statement 输入。

  Blockly.FieldDropdown 的第一个参数 可以是一个可用选项的数组。每个选项依次 也是一个包含两个元素的数组：一个用于在 用户界面中显示文本（表示选项内容的友好提示文本） 和一个唯一标识符 （更适合在代码中使用的东西，例如COLOR_GREEN）。

  可以通过调用 setValue 方法 来设置默认选择的选项。 要指定哪个是默认的， 只需将选项的唯一标识符 作为参数传入 setValue 。

  `<script> function template(block) { let field = new Blockly.FieldDropdown([ // [ displayed text, option name ] ['green', 'COLOR_GREEN'], ['yellow', 'COLOR_YELLOW'], ['red', 'COLOR_RED'], ]); // setting default option field.setValue('COLOR_YELLOW'); block.appendDummyInput() .appendField(field, 'myDropdown'); } </script>`

- color picker - 一个颜色选择器部件

  XML
  可以通过 <color> 元素嵌套在 dummy, value 或 statement 输入中添加。 `<template> <dummy> <color name="myColor">#350af5</color> </dummy> </template>`
  JS
  可以通过 appendField 方法 和 Blockly.FieldColor 添加到 dummy , value 或 statement 输入。 `<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldColor('#350af5'), 'myColor'); } </script>`

#

##### 字段排列

拼图模块中的字段元素总是属于一个特定的输入插槽。 无论是单个字段还是每个输入的多个字段，它们总是 按照一定的布局进行渲染。有一件事是可以改变的， 那就是字段在输入框中的位置，特别是它们被排列在 哪一边。您可以让它们向左（默认）、 向右和向中间对齐。

XML
要改变某个输入的字段对齐方式， 请使用相应的 输入元素 上的 align 属性。 这个属性的有效值是： Left 、 center 和 right 。 `<template> <dummy align="left"></dummy> <value align="center" name="myValueInput"></value> <statement align="right" name="myStatementInput"></statement> </template>`
JS
要改变某个输入的字段对齐方式，请使用 setAlign 方法。 它的第一个参数应该是 Blockly.ALIGN_LEFT ， Blockly.ALIGN_CENTER 或 Blockly.ALIGN_RIGHT 。 `<script> function template(block) { block.appendDummyInput() .setAlign(Blockly.ALIGN_LEFT); block.appendValueInput('myValueInput') .setAlign(Blockly.ALIGN_CENTER); block.appendStatementInput('myStatementInput') .setAlign(Blockly.ALIGN_RIGHT); } </script>`

#

##### 模块的连接

拼图模块可以有输入、语句和输出连接。 输入连接是为每个创建的输入插槽自动添加的， 它的作用是插入子模块。 请在这里查看关于创建输入的介绍： 添加输入。 Statement（previous和next）和 output 连接 用于将模块连接到父模块或同级模块。

previous 和 next 分别在拼图模块的顶部和底部 添加连接，因此拼图模块可以从 下面/上面连接到其他有匹配连接的模块。

output 在拼图模块的左侧添加了一个连接——这使得 该模块可以被插入到父模块的输入插槽中。 输出连接通常用于返回数值的拼图模块， 例如，一些数学计算的结果。

默认情况下，一个拼图模块没有任何连接。 您可以在一个拼图模块上添加任何类型的单个连接。 您也可以在一个拼图模块中添加2个 statement 或 output 连接， 但只允许以下组合：previous + next 或 next + output。

如下演示如何将所有这些连接添加到一个模块中：

XML
可以通过设置 prev 属性为 true 来启用 previous statement连接。 `<template prev="true"></template>`
JS
通过调用 block.setPreviousStatement ， 以 true 作为第一个参数， 可以启用 previous statement连接。 `<script> function template(block) { block.setPreviousStatement(true); } </script>`
XML
可以通过将 next 属性 设置为 true 来启用 next statement连接。 `<template next="true"></template>`
JS
可以通过调用 block.setNextStatement ， 以 true 作为第一个参数， 来启用 next statement连接。 `<script> function template(block) { block.setNextStatement(true); } </script>`
XML
可以通过设置 output 属性为空字符串 来启用 output 连接。 `<template output=""></template>`
JS
可以通过调用 block.setOutput 启用 output 连接， 并将 true 作为第一个参数。 `<script> function template(block) { block.setOutput(true); } </script>`

#

##### 输入/输出类型检查

默认情况下，所有具有合适输入/输出的拼图模块都可以相互连接。 但这并不意味着所有的拼图模块都应该是兼容的。 比方说，我们有一个拼图模块返回一个坐标数组， 而另一个拼图模块有一个期望得到动画名称的输入。 如果我们试图将第一个模块插入第二个模块中， 那么事情可能不会按预期执行。 从这些拼图中产生的代码可能是无效的，甚至可能导致页面崩溃。

好在有一种方法可以解决此类情况。 每一个输入和输出都可以被分配一个类型， 只有那些具有匹配类型的模块可以相互连接。

关于输出和其他连接的常用信息，请参见 模块的连接 。

XML
输出类型可以通过 <template> 的 output 属性来指定。 一个特定的输入可以接受的类型 是由输入的 type 属性定义的。 `<!-- the block's output type is 'String' --> <template output="String"> <!-- this input accepts only blocks of type 'Number' --> <value name="myInput" type="Number"></value> </template>`
JS
输出类型可以通过调用 block.setOutput 方法 来分配给一个模块， 同时将 true 作为其第一个参数， 将所需类型作为其第二个参数。 通过调用 setCheck 方法 来设置一个特定输入所能接受的类型。 `<script> function template(block) { // this input accepts only blocks of type 'Number' block.appendValueInput('myInput') .setCheck('Number'); // the block's output type is 'String' block.setOutput(true, 'String'); } </script>`

在上面的例子中，该模块有一个只能接受 "number" 类型 或未指定类型的模块的输入（如果没有通过 setOutput 设置类型）。 该模块也有 "String" 输出类型， 这意味着它只能插入具有 "String" 或 未指定类型的输入（如果没有通过 setCheck 设置类型）。

输入和输出也可以有一个以上的类型。

XML
为了拥有多个输入/输出类型， 请在相应的 type 和 output 属性中 填写多个类型值，并以空格分隔： `<!-- this block's output type is 'String' or 'Animation' --> <template output="String Animation"> <!-- this input accepts only blocks of type 'Number' or 'Object3D' --> <value name="myInput" type="Number Object3D"></value> </template>`
JS
为了拥有多个输入/输出类型， 在 setCheck 和/或 setOutput 中提供类型阵列。 `<script> function template(block) { // this input accepts only blocks of type 'Number' or 'Object3D' block.appendValueInput('myInput') .setCheck(['Number', 'Object3D']); // this block's output type is 'String' or 'Animation' block.setOutput(true, ['String', 'Animation']); } </script>`

标准的Verge3D拼图使用了几种特定的输入/输出类型， 您也可以在您的拼图中借用这些类型。

- 对于Javascript类型和内置对象， 有以下几种：Number, String, Boolean, Dictionary (针对js对象), Array, Promise, Procedure (对于在拼图中定义的过程)
- 对于场景实体：Object3D, Material, Animation 。
- 其他类型：Canvas, Sound, Video

您无需局限于上述类型， 我们鼓励您创造自己的输入/输出类型， 以更好地适应您的拼图。

#

#### Code Function

Code() 函数是用来提供Javascript代码的， 如果拼图被添加到工作区，它就会被生成。 一般来说，这是您定义拼图逻辑的地方， 也是您实现大部分拼图功能的地方。

该函数预期返回一个包含js代码的字符串。 它的工作方式类似于 init.plug的代码函数 的工作方式， 只是在这种情况下，代码被添加和使用的次数 与被添加到工作区的拼图模块的数量相同。 这个 code() 函数接收一个 block 参数—— 它与在 template() 中 使用的 Blockly.BlockSvg 是同一个实例，。

一起来看看您能用 code() 函数做些什么。

#

##### 基本的代码生成

code() 能做的非常简单的事情 就是返回一个带有几行 javascript 代码的字符串， 这些代码将被添加到生成的 visual_logic.js 文件中。
例如，下面的代码打开了标准的浏览器警报对话框。 `function code(block) { return `alert('Test');`; }` 而在这里，我们仅返回一个为1的值： `function code(block) { return `1`; }` - 这个例子并没有什么意义，除非此模块有输出连接。 在这种情况下，返回值可以从一个父模块中访问， 该父模块的输入端之一插入了这个模块。
这里是一个更高级的例子： `function code(block) { const fun = function() { app.scene.traverse(function(obj) { obj.material = new v3d.MeshBasicMaterial({ color: new v3d.Color(Math.random(), Math.random(), Math.random()) }); }); } return `(${fun})();`; }` - 这里所有的对象都得到了一个新的材质，其颜色是随机生成的。

#

##### 缓解代码臃肿问题

默认情况下，每次在工作区使用拼图时， 拼图的代码都会被复制到生成的visual_logic.js文件中。 如果您只有几行代码，这并不是什么问题。 但是，如果代码很笨重、很复杂，并且分成几个你可能只想声明一次的函数， 那么默认的方法就会变得效率低下， 并导致产生的visual_logic.js文件变得臃肿。
为了解决这个问题， 您可以利用 code() 函数中的一个特殊方法——Plug.provide() 。 我们通过下方的示例来演示如何使用： `function code(block) { const fun = Plug.provide('myFunction', function(a, b, c) { console.log(a, b, c); }); return `${fun}(1, 2, 3);`; }` 在这里，我们通过 Plug.provide() 定义了一个函数 "myFunction" ， 这意味着无论拼图在工作区被使用多少次， "myFunction" 都只会被复制到visual_logic.js中一次。 另外，从 code() 返回的值只有 ${fun}(1, 2, 3); ，这基本上是一个函数调用 "myFunction(1, 2, 3);" ， 它将被插入visual_logic.js中， 用于工作区的每个拼图。 而这正是我们的拼图所希望的， 因为 "myFunction" 只需要声明一次， 之后就可以多次调用。
在 Plug.provide() 中的第一个参数 应该是一个唯一的函数标识符。 返回的变量fun是所提供的函数的名称（它通常与第一个参数传递的值几乎相同， 但也可以不同，因为拼图编辑器需要确保 这个名称是有效的，并且与工作区使用的其他函数/变量没有冲突）。 应该使用该名称（而不是原始的 "myFunction" ） 来调用提供的函数 - 这就是它在 return 语句下的 部分中的完成方式。

#

##### Using PzLib API

有一个为插件开发者提供的丰富的API，称为PzLib。此API的使用方式如下：

`function code(block) { // 首先像这样声明所需的方法 Plug.pzlib('getAllObjectNames'); Plug.pzlib('getObjectByName'); Plug.pzlib('generateUniqueName'); // 或者像这样 Plug.pzlib('getAllObjectNames', 'getObjectByName', 'generateUniqueName'); // ... // 然后按如下方式使用 const fun = Plug.provide('myFunction', function() { const objNames = PzLib.getAllObjectNames(); const cubeObj = PzLib.getObjectByName('Cube'); const uniqueName = PzLib.generateUniqueName('Cube'); }); return `${fun}();`; }`

请查看此处获取更多示例和完整的PzLib API参考。

#

##### 访问输入和字段

如果一个拼图模块有 fields 或 input slots 在 template() 函数中定义， 那么您很可能希望它们 影响 code() 函数内生成的内容。 例如， 复选框 可以启用或禁用您的拼图的某个功能。
用于访问 value inputs 、 statement inputs 和 fields 的API方法有： Blockly.JavaScript.valueToCode , Blockly.JavaScript.statementToCode 和 block.getFieldValue 。
让我们做一个既有输入又有字段的拼图模块。 下面是.block文件的全部内容： `<template color="green"> <dummy> <label>myPuzzle</label> </dummy> <value name="myValue"></value> <statement name="myStatement"></statement> <dummy> <checkbox name="myCheckbox">true</checkbox> </dummy> </template> <script> function wrapFn(contents) { return `function() {${contents}}`; } function code(block) { const myInput = Blockly.JavaScript.valueToCode(block, 'myValue', Blockly.JavaScript.ORDER_NONE) || `''`; const myStatement = wrapFn(Blockly.JavaScript.statementToCode(block, 'myStatement')); const myCheckbox = block.getFieldValue('myCheckbox') === 'TRUE'; const fun = Plug.provide('myFunction', function(input, statements, checkbox) { console.log('input value:', input); statements(); // execute puzzles from the myStatement input console.log('checkbox state:', checkbox); }); return `${fun}(${myInput}, ${myStatement}, ${myCheckbox});`; } </script>` 在这个例子中，该模块定义了一个名为 "myValue" 的数值输入， 一个语句输入 "myStatement 和一个复选框字段 "myCheckbox" 。 我们通过上面描述的API获得它们的值， 但在将它们传入 "myFunction" 之前，它们经历了一些值得注意的变化。 `var myInput = Blockly.JavaScript.valueToCode(block, 'myValue', Blockly.JavaScript.ORDER_NONE) || `''`;` - 输入插槽可能会没有插入模块， 所以我们通过在末尾添加*|| `''*部分， 来确保在这种情况下可以得到一个空字符串。 `function wrapFn(contents) { return `function() {${contents}}`; } ... var myStatement = wrapFn(Blockly.JavaScript.statementToCode(block, 'myStatement'));` - Statement输入插槽通常包含一组Statement。 把它们包在一个函数中是很方便的（见 wrapFn 的作用）， 以便把该函数对象作为参数传递，然后把它当作 一个回调使用。 `var myCheckbox = block.getFieldValue('myCheckbox') === 'TRUE';` - 在这里，复选框的值只用于与 "TRUE" 进行比较， 产生一个布尔值的结果。
最后，所有的值都可以传递给"myFunction" ，如下所示： `return `${fun}(${myInput}, ${myStatement}, ${myCheckbox});`;` 现在您能够随心所欲地使用它们了。 `const fun = Plug.provide('myFunction', function(input, statements, checkbox) { console.log('input value:', input); statements(); // execute puzzles from the myStatement input console.log('checkbox state:', checkbox); });`

#

## 插件和图模块的错误

开发或使用插件时，您可能会遇到不同类型的错误， 与某个拼图模块或与整个插件有关。 本节将介绍典型的插件和拼图模块错误，以及如何处理这些问题。

如果在加载插件或初始化拼图模块的过程中出现错误， 那么拼图编辑器会在浏览器控制台中打印出相应的错误信息。 通常情况下， 这些错误会像下面这样：

PluginError(PLUGIN_NAME) ...
BlockError(PLUGIN_NAME/BLOCK_NAME) ...
Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.

- 它们指的是一个特定的插件和 一个特定模块导致的错误。

在插件出错的情况下， 整个插件的类目很可能都不会在工具箱中显示。 如果是拼图模块错误，受影响的拼图模块将被标记为无效，并有一个明显的外观标示：

在工具箱和工作区中的无效拼图

以下是最常见的插件和拼图模块错误的列表：

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "This page contains the following errors:error on line ..."

这意味着在相应的.block文件中存在着XML错误， 使其无法被解析。 例如，缺少结尾的<script>标签会导致这样的错误。 `<script> function template(block) {} function code(block) {}`

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "ReferenceError (SyntaxError, TypeError, etc...) ..."

这代表着相应的.block文件的<script>元素中的 代码包含错误信息中指定的 JavaScript错误。

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Child block does not have output or previous statement."

这个错误所指的模块有一个子模块插入其中。 但是，这个子模块没有 输出或前一个连接 ， 因此不能以这种方式使用。这种情况可能发生在 插件的工具箱 类目 中的拼图块， 因为它们是在init.plug中配置的， 也可能发生在工作区实际使用的块上。

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "Error: Connection checks failed. Output/Previous/Next Connection of "PLUGIN_NAME/BLOCK_NAME" block (id="BLOCK_ID") expected TYPE_CHILD, found TYPE_PARENT"

这个错误所指的模块连接到一个父模块， 但是父模块的输入插槽和子模块的输出连接的 类型 不兼容。 这种情况可能发生在插件的工具箱 类目 中的拼图块， 因为它们是在init.plug中配置的， 也可能发生在工作区 实际使用的块上。

#

##### BlockError(PLUGIN_NAME/null): validation error - "TypeError: Unknown block type: PLUGIN_NAME/null"

这个错误意味着该插件的init.plug文件引用了一个 没有指定 类型属性 的模块， 这是不允许的。例如，这样做是行不通的： `<category name="My Awesome Plugin" color="green"> <block></block> </category>`

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Unknown block type: PLUGIN_NAME/BLOCK_NAME"

这个错误信息通常出现在一个 "错误解析的 .block 文件" 的错误之后， 它表明由于最初的错误， 提到的拼图模块没有被正确加载和初始化。

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): error calling template() function ...

相应的.block文件要么有一个定义不正确的 "template" 元素， 要么在其 template() 函数中 包含JavaScript错误。

- BlockError(PLUGIN_NAME/BLOCK_NAME): error calling template() function - "TypeError: Found invalid FieldDropdown options."
  这个特殊的错误意味着拼图中的 一个下拉字段有一个未命名的选项。 参见 dropdown field 以了解更多信息。

#

##### BlockError(PLUGIN_NAME/BLOCK_NAME): error calling code() function ...

相应的.block文件 在其 code() 函数内 含有JavaScript错误。

#

##### PluginError(PLUGIN_NAME): error parsing init.plug file - "This page contains the following errors:error on line ..."

这意味着在插件的init.plug文件中存在着XML错误， 使其无法被解析。 例如，缺少结尾的<category>标签会导致这样的错误。 `<category name="MyAwesomePlugin" color="green"> <block type="myPuzzle"></block>`

#

##### PluginError(PLUGIN_NAME): error parsing init.plug file - "ReferenceError (SyntaxError, TypeError, etc...) ..."

这意味着如果相应的init.plug文件的<script>元素中 的代码包含错误信息中 指定的JavaScript错误。

#

##### PluginError(PLUGIN_NAME): error calling code() function ...

该插件的init.plug文件 在其 code() 函数内 含有JavaScript错误。

#

##### Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.

这条错误信息通常出现在一个BlockError和/或 PluginError错误信息之后， 它表明由于原始错误， 提到的拼图模块没有被正确加载和初始化。 为了仍然能够加载拼图并在一定程度上保持其可操作性 这样的拼图模块（无论是在插件的工具箱类目中还是在工作区中）都会被特殊的dummy模块所取代。 Dummy模块的例子见 这张图片 。

#

## 分享您的插件

当您的插件开发完成时，请随时通过以下方式分享：

- 在 Verge3D论坛上发布链接。
- 写一篇 文章 或在 常用链接 章节中被提及。
- 在社交媒体上分享是使用 #verge3d 标签。
- 在Reddit分享。这里有温和的Verge3D版块，您永远都不会被禁止: r/RealVerge3D, r/3dcommerce, 及 3dconfigurators。
- 在 Gumroad 或类似的资源网站上发布。

## 在使用拼图时遇到困难？

欢迎您随时在 论坛上提问！您还可以加入中文用户社区QQ群(171678760)，在线寻求帮助。