Plugins
Verge3D Puzzles Editor provides the ability to load your own custom puzzles, thus making it possible to extend the Editor's functionality with features you've always wanted.
Contents
- Installing Plugins
- Plugin Files Overview
- Plugin and Block Errors
- 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.
- Sharing your Plugin
Installing Plugins
Plugins are directories with a bunch of plugin-relevant files. Plugins should be placed inside Verge3D's puzzles/plugins folder in order to be recognized by the Puzzles Editor. And that's it! After reloading the Editor's page all installed plugins should appear at the bottom of the Editor's toolbox right after all the standard puzzle categories.
For plugin developers
This helpful plugin contains examples of some typical puzzle blocks:
ExamplePlugin.zip. Just
unzip it into the puzzles/plugins folder and then check out the
"Example Plugin" puzzle category.
Plugin Files Overview
A typical plugin is just a directory with an init.plug file containing general plugin settings and a bunch of *.block files each defining a single puzzle block. If your text editor supports syntax highlighting, then the HTML mode should work well for both file formats.init.plug File Format
init.plug is a mandatory plugin file, which is used for specifying general plugin settings. In that file you can define what the toolbox entry looks like. Also you can add there some preliminary javascript code for your puzzles. Here's a simple example of what you can see in an init.plug file:
<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
The category part is an XML tree that defines how the plugin and its
puzzle blocks are displayed inside the Puzzles Editor's toolbox. Although
it is optional, still if you don't add it into the init.plug file then
the plugin won't be loaded at all.
There are several options that you can set up via category:
Toolbox Entry Name
Specified via the name attribute:
<category name="My Awesome Plugin"></category>
Toolbox Entry Color
Specified via the color attribute:
<category name="My Awesome Plugin" color="green"></category>
The color can be defined in one of the following formats:
- hex triplet, e.g. #f61 or #f0562f
- hue value in range 0°-360° (within the scope of HSV color model with S and V fixed to 45% and 65% respectively), e.g. 140
- color keywords such as aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, purple, red, silver, teal, white and yellow
Available Puzzles
To make a puzzle block available in the plugin's toolbox category it should be specified via the block element and its type attribute. The type attribute can reference such puzzles as:
-
puzzles defined by the plugin itself - in this case type
should be set according to the name of the corresponding
.block file inside your plugin's directory (e.g.
myPuzzle for a block file my_awesome_plugin/myPuzzle.block)
<category name="My Awesome Plugin" color="green"> <block type="myPuzzle"></block> </category>
-
stock puzzle blocks - type should be equal to the
type of a stock puzzle (e.g. math_number). This allows
adding stock puzzles directly into your plugin's toolbox
category
<category name="My Awesome Plugin" color="green"> <block type="math_number"></block> </category>
-
puzzles defined by other plugins - type should be in the
form PLUGIN_DIRECTORY_NAME/PUZZLE_TYPE. For example,
puzzles from the standard E-Commerce
plugin can be referenced like this:
<category name="My Awesome Plugin" color="green"> <block type="E-Commerce/placeOrder"></block> </category>
To find out the type of a certain puzzle block you can use the "Print Puzzle XML Tree" option from the puzzle context menu:
That menu option prints the puzzle XML tree into the browser console. You can find there the puzzle type as well as the whole XML structure, which might come in handy when setting the default input and field values.
The "Print Puzzle XML Tree" menu option provides an easy way of adding a whole group of puzzle blocks to your plugin at once. This is useful for creating some sort of "snippets" consisting of connected puzzle blocks and putting them into your plugin, which makes it a bit similar to the Puzzles Library.
The following example explains how to do that:
-
create some puzzle blocks in the Puzzles Editor:
-
use right mouse click on the top/outmost block and choose
"Print Puzzle XML Tree":
-
go to the browser console and copy the printed element (this
is what it looks like in Google Chrome):
-
paste it into the init.plug file inside the <category>
element and save the file:
- then after reloading the Puzzles Editor the copied block should appear inside the plugin's category
Puzzles Order
Puzzle blocks appear in the toolbox in the order you define in the
init.plug file:
<category name="My Awesome Plugin" color="green">
<block type="myPuzzle"></block>
<block type="doSomethingCool"></block>
<!-- <block type="testPuzzle"></block> -->
<block type="anotherPuzzle"></block>
</category>
Note how the commented out puzzle block "testPuzzle" isn't shown in the
toolbox.
Text Labels
You can add text labels into the toolbox category via the label element:
<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 elements are not suitable for displaying multiline text, i.e. no line breaks.
label elements can also be stylized to some extent. They support a web-class attribute, which is intended for assigning a custom CSS class to the label element. The CSS rules for that class can be defined in init.plug's script section. This approach is illustrated in the following example:
<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>
And here's the result of applying the custom CSS rules:
When deciding what CSS class name to put into the web-class attribute it's recommended to consider some sort of CSS scoping, for example by using a prefix unique to your puzzles plugin, i.e. in the example above it's the "example-plugin" part of the "example-plugin__label" class. That way you less likely accidentally break any CSS classes already used on the page.
Separators
Separators can be used to change the distance between puzzle blocks. You
can add separators into the toolbox category via the sep element. The
gap attribute specifies the gap width in pixels. The default distance
between blocks (when not using sep) equals to 24 pixels.
<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>
Puzzle Blocks Available in the "init" Tab
By default puzzle blocks only available in the main and
user-created tabs, but not in init. That's because the code generated by the init tab is
executed before a Verge3D application is loaded and fully
initialized. This means that puzzles intended to work with a
3d-scene, 3d-objects, materials, etc... are not suitable for
using inside init and can crash the application. However,
puzzles that don't require a 3d-scene to be available (e.g.
those that preload resources or set up the UI) shouldn't have such
problems and can be allowed in init.
In order to make a puzzle appear in the init tab's toolbox you
need to set the allow-init attribute to true in the puzzle's block
element (this also works with label and sep elements):
<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>
Note how the block and label elements without allow-init are not
displayed in the toolbox.
Default Input and Field Values
If a puzzle block has block inputs (slots for connecting other
puzzle blocks) and/or field inputs (non-block UI elements such
as selectors, checkboxes, text fields, etc...), then you can
specify their placeholder blocks and/or default values. This
feature serves two purposes: it provides a hint for users on
what can be plugged into an input slot, and it also makes using
a puzzle block a bit more convenient.
Let's say that your puzzle block has an input named "myNumber".
Here's how you can add a placeholder block of type math_number
plugged into that slot:
<category name="My Awesome Plugin" color="green">
<block type="myPuzzle">
<value name="myNumber">
<block type="math_number"></block>
</value>
</block>
</category>
And here's what it will look like:
A placeholder block plugged into an input slot can also be a shadow
block. Shadow blocks are basically the same as ordinary blocks
except they are automatically replaced with a block that you
insert into the corresponding input slot and they automatically
appear back when you remove that block from the slot. This makes
shadow blocks a bit easier to use than ordinary placeholder
blocks.
Shadow blocks are defined almost the same way as ordinary placeholder
blocks, the only difference is that the block element is replaced with
the similar shadow element:
<category name="My Awesome Plugin" color="green">
<block type="myPuzzle">
<value name="myNumber">
<shadow type="math_number"></shadow>
</value>
</block>
</category>
And it will look like this:
Puzzle blocks can have statement inputs, which are inputs that usually
wrap a series of child puzzle blocks. Let's say that your puzzle block
has a statement input called "myStatement". Then you can add a couple
of placeholder blocks into that input as follows:
<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>
The statement element used here references the "myStatement" input
via the name attribute. And it also has some placeholder blocks added
into the input. Also, the next element is used here for chaining a
series of placeholder blocks. The result of that setup is shown below:
If your puzzle block has a checkbox field named "myCheckbox",
then you can define its default state (true - enabled,
false - disabled) like this:
<category name="My Awesome Plugin" color="green">
<block type="myPuzzle">
<field name="myCheckbox">true</field>
</block>
</category>
And here's the result:
Through using placeholder blocks and default field values you
can define complex compound puzzles akin to what you can add into
the Puzzles Library:
Code in init.plug for the complex puzzle setup on the picture above
can look like this:
<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>
Check out the "Print Puzzle XML Tree" context menu option. It helps in finding out the XML structure (the configuration of inputs and fields) of a puzzle block of interest.
Toolbox Subcategories
A category in the Editor's toolbox can have subcategories, which
in their turn can also have subcategories, and so on... This
feature is useful if you want to organize your plugin's puzzles
into a tree-like structure.
This can be achieved by using nested category elements:
<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>
Any category can contain both category and block elements at the same
time (although it's not mandatory). That way it acts like a parent for
subcategories and also provides a selection of puzzle blocks.
Script
The <script> element is an optional part of init.plug. It can be used to add some initialization code for your puzzles. Sometimes you might need to do heavy calculations and cache some data before any of your puzzles can be used - that's where <script> comes to rescue.
If you define a code() function inside <script> it will be used to
generate the code that is executed once before any puzzles. The
code() function should return a string containing javascript code.
<script>
function code() {
// this line will be executed before any puzzles
return `console.log('Powered by My Awesome Plugin!');`;
}
</script>
The initialization code returned by the code() function is added into the generated logic file only if the plugin's puzzles are actually used in the application (added on a workspace and not being disabled).
.block File Format
Plugin files with the .block extension are used to define single
puzzle blocks, specifically, what a block can look like and the code
that it should generate if added to a workspace area. A plugin can have no
.block files at all. That can be useful if you want to create a
toolbox category with just stock
puzzle blocks (even including more complex block setups).
The name of a .block file is used to indicate which puzzle blocks
should be included in the plugin's toolbox category.
Here's a minimal example of a .block file:
<template color="green">
<dummy>
<label>myPuzzle</label>
</dummy>
</template>
<script>
function code(block) {
return `console.log('This is my first puzzle!');`;
}
</script>
Here we have a <template> element which defines the appearance
of the puzzle block. And also there is a <script> element
with a code() function inside it. The code() function returns a
string containing the code that will be generated in place of this
puzzle.
So, based on the example above we can expect our simple puzzle block to
be green and to have a text label saying "myPuzzle":
And if added to a workspace it should print the following message into
the browser console:
Block Template
The appearance of a puzzle block can be defined in two ways: via the <template> XML element and via the template() function. The former variant is more simple and easy to use. For example, this is what <template> of a typical puzzle can look like:
<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>
The other approach is to use the template() function. It's a function
that you can define inside the <script> element. It can
also be used to configure the puzzle appearance but this time through
the Blockly JavaScript API
rather than XML elements and attributes. It receives a block parameter,
which is an instance of Blockly.BlockSvg.
The same block as in the example above can be rewritten by using the
template() function as follows:
<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>
This approach is more flexible but requires knowledge of the corresponding APIs. It's especially useful if you need to do some non-trivial setup that can't be achieved via the <template> element. Moreover, you can use both <template> and template() simultaneously.
This section provides examples for both <template> (XML) and template() (JS) variants.
Please, note that this section is just a brief overview of how to create a custom puzzle block. For more detailed information on general customization check out the Google Blockly documentation about custom blocks and fields.
Block Color
You can set block color to give your puzzles distinct appearance:
<template color="green"></template>
<script>
function template(block) {
block.setColor('green');
}
</script>
Colors have to be in one of the formats described here: color formats.
Block Tooltip
You can add a tooltip that appears when hovering above a block. The tooltip is useful for providing a user with a simple description of what the puzzle is intended for, how it works, what are the usage tips, etc...
<template tooltip="This is my first puzzle!"></template>
<script>
function template(block) {
block.setTooltip('This is my first puzzle!');
}
</script>
Block Help URL
If the tooltip is not enough for documenting your puzzle you may also add a link to a website with more thorough documentation. This link will be used for the Help entry in the puzzle context menu (right click on a puzzle):
<template help="https://www.soft8soft.com/"></template>
<script>
function template(block) {
block.setHelpUrl('https://www.soft8soft.com/');
}
</script>
Adding Inputs
Puzzle blocks can contain input slots for plugging in other blocks. Also they can have non-block UI elements such as checkboxes or text fields. There are 3 different types of inputs: value inputs, statement inputs and dummy inputs.
- value inputs - are input slots that can be used for plugging in blocks having a return value (an output connection), e.g. an input with some math blocks inside it - the math blocks calculate and return some value, which can be accessed by the parent block (the block containing the input slot)
- statement inputs - are inputs that can hold a group of blocks representing a series of consecutive actions, e.g. the if-else puzzle has exactly that type of input for grouping puzzle blocks under the "if" or "else" conditions
- dummy inputs - these are inputs suitable only for adding non-block UI elements like checkboxes, text fields, images, etc... See adding fields for more info.
You can see the difference between those types of inputs on the picture below:
<template>
<value name="myInput"></value>
<statement name="myStatement"></statement>
<dummy>
<checkbox name="myCheckbox">true</checkbox>
</dummy>
</template>
<script>
function template(block) {
block.appendValueInput('myValue');
block.appendStatementInput('myStatement');
block.appendDummyInput()
.appendField(new Blockly.FieldCheckbox(true), 'myCheckbox');
}
</script>
Arrangement of Inputs
Block inputs can be arranged either vertically (default) or horizontally.
<template inline="true"></template>
<script>
function template(block) {
block.setInputsInline(true);
}
</script>
Adding Fields
You can add such UI elements as text labels, checkboxes, drop-down lists, text inputs and more into your puzzles. Those UI elements are called "fields". They can be added to inputs of any type, but if you don't want to additionally create input slots for puzzle blocks you should stick to using dummy inputs.
There are a couple of things that all fields have in common:
- The ability to specify an optional name parameter, which is often used for referencing the field in the code() function in order to obtain the field's value.
- The ability to provide an optional default value. Still, a default value specified in init.plug's category always has a higher priority than the one defined in the puzzle template.
Let's see how to add various fields to a puzzle.
-
label - a non-editable text field
Can be added via the <label> element nested inside a dummy, value or statement input.<template> <dummy> <label>a text label</label> </dummy> </template>
Can be added via the appendField method and Blockly.FieldLabel to a dummy, value or statement input.<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldLabel('a text label')); } </script>
-
text - an editable text field
Can be added via the <text> element nested inside a dummy, value or statement input.<template> <dummy> <text name="myText">default text</text> </dummy> </template>
Can be added via the appendField method and Blockly.FieldTextInput to a dummy, value or statement input.<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldTextInput('default text'), 'myText'); } </script>
-
multiline text - an editable multiline text field
Can be added via the <multiline> element nested inside a dummy, value or statement input.<template> <dummy> <multiline name="myTextMultiline">This is a \n multiline \n text</multiline> </dummy> </template>
Can be added via the appendField method and Blockly.FieldMultilineInput to a dummy, value or statement input.<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldMultilineInput('This is a \n multiline \n text'), 'myTextMultiline'); } </script>
-
number - an editable field for numeric values
Can be added via the <number> element nested inside a dummy, value or statement input.<template> <dummy> <number name="myNumber">3</number> </dummy> </template>
Can be added via the appendField method and Blockly.FieldNumber to a dummy, value or statement input.<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldNumber(3), 'myNumber'); } </script>
-
angle - an editable field for numeric values representing angles (in degrees)
Can be added via the <angle> element nested inside a dummy, value or statement input.<template> <dummy> <angle name="myAngle">15</angle> </dummy> </template>
Can be added via the appendField method and Blockly.FieldAngle to a dummy, value or statement input.<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldAngle(15), 'myAngle'); } </script>
-
checkbox - a typical checkbox field
Can be added via the <checkbox> element nested inside a dummy, value or statement input. The default checkbox state should be set to either true or false.<template> <dummy> <checkbox name="myCheckbox">true</checkbox> </dummy> </template>
Can be added via the appendField method and Blockly.FieldCheckbox to a dummy, value or statement input. The default checkbox state is passed as a parameter to Blockly.FieldCheckbox and can be either true or false.<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldCheckbox(true), 'myCheckbox'); } </script>
-
dropdown - a list of values to select from
Can be added via the <dropdown> element nested inside a dummy, value or statement input.
To create the options list add <option> elements inside <dropdown>. All <option> elements must have their name attribute set to a unique identifier (something that would be more suitable for using in the code, e.g. COLOR_GREEN). The text contained between the starting and closing <option> tags is used as a label displayed in the dropdown widget for that option.
The default selected option can be set by setting an option's default attribute to 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>
Can be added via the appendField method and Blockly.FieldDropdown to a dummy, value or statement input.
The first parameter of Blockly.FieldDropdown can be an array of available options. Each option in its turn is also an array of 2 elements: a text for displaying in the UI (human friendly text denoting what the option is about) and a unique identifier (something that would be more suitable for using in the code, e.g. COLOR_GREEN).
The default selected option can be set by calling the setValue method. To specify which should be the default one simply pass the option's unique identifier as a parameter into 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 - a color picker widget
Can be added via the <color> element nested inside a dummy, value or statement input.<template> <dummy> <color name="myColor">#350af5</color> </dummy> </template>
Can be added via the appendField method and Blockly.FieldColor to a dummy, value or statement input.<script> function template(block) { block.appendDummyInput() .appendField(new Blockly.FieldColor('#350af5'), 'myColor'); } </script>
Field Alignment
Field elements in a puzzle block always belong to a particular input slot. Be it a single field or multiple fields per input, they are always rendered following a certain layout. One thing that can be changed about that is how fields are positioned inside an input, specifically which side they are aligned. You can make them align left (default), right and center.
<template>
<dummy align="left"></dummy>
<value align="center" name="myValueInput"></value>
<statement align="right" name="myStatementInput"></statement>
</template>
<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>
Block Connections
Puzzle blocks can have input, statement and output connections. Input connections are added automatically for each input slot created and serve the purpose of plugging in child blocks. See more information on creating inputs here: adding inputs. Statement (previous and next) and output connections are used for connecting blocks to a parent block or sibling blocks.
Previous and next add connections at the top and at the bottom of a puzzle block respectively, so the block can be connected from below/above to other blocks that have a matching connection.
Output adds a connection at the left side of a block - this allows the block to be plugged into an input slot of a parent block. The output connection typically used for puzzle blocks that return a value, e.g. the result of some math calculations.
By default a puzzle block doesn't have any connections at all. You can add a single connection of any type to a puzzle block. You can add even 2 statement/output connections to a block, but only the following combinations are allowed: previous + next or next + output.
Here's how all those connections can be added to a block:
<template prev="true"></template>
<script>
function template(block) {
block.setPreviousStatement(true);
}
</script>
<template next="true"></template>
<script>
function template(block) {
block.setNextStatement(true);
}
</script>
<template output=""></template>
<script>
function template(block) {
block.setOutput(true);
}
</script>
Input/Output Type Checking
By default all puzzle blocks that have suitable inputs/outputs can be connected with each other. Yet, that doesn't mean all blocks should be compatible. Let's say that we have a puzzle block returning an array of coordinates, while some other block has an input that expects an animation name. If we try to plug the first block into the second one then things might not work as expected. The code generated from those puzzles can be invalid and even lead to a crash.
Fortunately, there's a way to resolve that situation. Every input and output can be assigned a type and only those blocks that have matching types can be connected with each other.
For general information about output and other connections see block connections.
<!-- 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>
<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>
In the example above the block has an input that can only accept blocks of the type "Number" or of an unspecified type (if no type was set via setOutput). The block also has the "String" output type, which means that it can be only plugged into an input having the "String" or an unspecified type (if no type was set via setCheck).
Inputs and outputs can also have more than one type:
<!-- 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>
<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>
Standard Verge3D puzzles use several certain input/output types, which you may borrow for your puzzles as well:
- for javascript types and built-in objects there are: Number, String, Boolean, Dictionary (for js objects), Array, Promise, Procedure (for procedures defined in Puzzles)
- for scene entities: Object3D, Material, Animation
- other types: Canvas, Sound, Video
You are not limited with the types described above and it's even encouraged to come up with your own input/output types that suit your puzzles better.
Code Function
The code() function is used to provide javascript code that should be generated for the puzzle if it's added to a workspace. Generally, this is the place where you define the puzzle's logic and where you implement most of the puzzle's features.
The function is expected to return a string containing js code. The way it works is similar to how init.plug's code function works, except that in this case the code will be added and used as many times as how many puzzle blocks are added to a workspace. The code() function receives a block parameter - it's the same instance of Blockly.BlockSvg that's used in template().
Let's look at what you can do with the code() function.
Basic Code Generation
The very simple thing that code() can do is to return a string with a
couple of lines of javascript code, which will be added into the
resulting visual_logic.js file.
For example, the following code opens the standard browser alert dialog:
function code(block) {
return `alert('Test');`;
}
And here we just return a value of 1:
function code(block) {
return `1`;
}
- this example doesn't make much sense unless the block has the output
connection. In that case the returned value can be accessed from a
parent block that has this block plugged into one of the inputs.
And now for a bit more advanced example:
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})();`;
}
- here all objects get a new material with a randomly generated color.
Mitigating Code Bloat
By default, a puzzle's code is copied into the generated visual_logic.js
file each time the puzzle is used on a workspace. It's not a problem if
you have just a couple of lines of code. But if the code is bulky,
complex and split into several functions that you might want to declare
only once, then the default approach becomes inefficient and causes the
resulting visual_logic.js file to bloat.
To deal with that you can utilize a special method available inside the
code() function. It is called Plug.provide(). Let's demonstrate how
to use it with the following example:
function code(block) {
const fun = Plug.provide('myFunction', function(a, b, c) {
console.log(a, b, c);
});
return `${fun}(1, 2, 3);`;
}
Here we have a function "myFunction" defined via Plug.provide(),
which means that no matter how many times the puzzle is used on a
workspace, "myFunction" will be copied to visual_logic.js just once. Also,
the value that is actualy returned from code() is just ${fun}(1, 2, 3);,
which is basically the function call "myFunction(1, 2, 3);" that
will be inserted into visual_logic.js for every such puzzle used on a
workspace. And this is what we exactly would want from our puzzle,
because "myFunction" only needs to be declared just once, and after that
it can be called multiple times.
The first parameter in Plug.provide() should be a unique function
identifier. The returned variable fun is the name of the provided
function (it's usually almost the same as the value passed in the first
parameter, but can be different because the Puzzles Editor needs to
ensure that the name is valid and there's no collisions with other
functions/variables used on a workspace). That name (instead of the
original "myFunction") should be used to call the provided function -
that is how it's done in the part under the return statement.
Using PzLib API
There is an extensive API available for plugin developers called PzLib. This API can be used like this:
function code(block) {
// first declare required methods like this
Plug.pzlib('getAllObjectNames');
Plug.pzlib('getObjectByName');
Plug.pzlib('generateUniqueName');
// or like this
Plug.pzlib('getAllObjectNames', 'getObjectByName', 'generateUniqueName');
// ...
// then use them as follows
const fun = Plug.provide('myFunction', function() {
const objNames = PzLib.getAllObjectNames();
const cubeObj = PzLib.getObjectByName('Cube');
const uniqueName = PzLib.generateUniqueName('Cube');
});
return `${fun}();`;
}
Check out more examples and complete PzLib API reference here.
Accessing Inputs and Fields
If a puzzle block has fields
or input slots
defined in the template() function,
then you would most likely want them to affect what is generated inside
the code() function. For example, a
checkbox
can enable or disable one of your puzzle's features.
The API methods used to access value inputs,
statement inputs
and fields
are namely: Blockly.JavaScript.valueToCode,
Blockly.JavaScript.statementToCode
and block.getFieldValue.
Let's make a puzzle block that has both inputs and fields. Here's the
full content of the .block file:
<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>
In this example the block defines a value input called "myValue", a
statement input "myStatement" and a checkbox field "myCheckbox". We obtain
their values via the API described above, but before we pass them into
"myFunction" they undergo some noteworthy changes:
var myInput = Blockly.JavaScript.valueToCode(block, 'myValue', Blockly.JavaScript.ORDER_NONE) || `''`;
- an input slot might have no blocks plugged into it, so we just ensure
that in such a case we get an empty string by adding the *|| ''
* part
at the end.
function wrapFn(contents) {
return `function() {${contents}}`;
}
...
var myStatement = wrapFn(Blockly.JavaScript.statementToCode(block, 'myStatement'));
- a statement input slot usually contains a group of statements. It is
convenient to wrap them in a function (see what wrapFn does) in order
to pass that function object as a parameter and then treat it as a
callback.
var myCheckbox = block.getFieldValue('myCheckbox') === 'TRUE';
- here the checkbox value is just compared against "TRUE" to produce a
boolean result.
In the end, all of the values can be passed into "myFunction" as follows:
return `${fun}(${myInput}, ${myStatement}, ${myCheckbox});`;
So, now you are able to use them however you want:
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);
});
Plugin and Block Errors
When developing or using plugins you can experience different errors related to a certain puzzle block or even a whole plugin. This section describes typical plugin and block errors and how to deal with them.
If something goes wrong during loading a plugin or initializing its puzzle blocks, then the Puzzles Editor prints a corresponding error message in the browser console. Usually, such error looks like one of the following:
PluginError(PLUGIN_NAME) ...
BlockError(PLUGIN_NAME/BLOCK_NAME) ...
Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.
- they refer to a specific plugin and a specific block that caused the error.
In case of a plugin error the whole plugin's category will most likely disappear from the toolbox. In case of a block error the affected blocks will be marked as invalid and will have a distinct look:
Here's the list of the most common plugin and block errors:
BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "This page contains the following errors:error on line ..."
This means that there are XML errors in the corresponding .block
file preventing it from being parsed. For example, the missing ending
<script> tag leads to such error:
<script>
function template(block) {}
function code(block) {}
BlockError(PLUGIN_NAME/BLOCK_NAME): error parsing .block file - "ReferenceError (SyntaxError, TypeError, etc...) ..."
The error occurs in case if the code in the corresponding .block file's <script> element contains a JavaScript error of the kind specified in the error message.
BlockError(PLUGIN_NAME/BLOCK_NAME): validation error - "TypeError: Child block does not have output or previous statement."
The block this error refers to has a child block plugged into it. However, the child block doesn't even have an output or a previous connection and therefore can not be used that way. This situation may happen either with puzzle blocks inside the plugin's toolbox category due to how they are configured in init.plug, and also this may happen with blocks actually used on a workspace.
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"
The block this error refers to is connected to a parent block but the parent's input slot and this block's output/previous/next connection have incompatible types. This situation may happen either with puzzle blocks inside the plugin's toolbox category due to how they are configured in init.plug, and also this may happen with blocks actually used on a workspace.
BlockError(PLUGIN_NAME/null): validation error - "TypeError: Unknown block type: PLUGIN_NAME/null"
This error means that the plugin's init.plug file refers to a block
with no type attribute
specified, which is not allowed. For example, this won't work:
<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"
This error message usually appears after one of the "error parsing .block file" errors and simply indicates that the mentioned puzzle block wasn't properly loaded and initialized because of the original error.
BlockError(PLUGIN_NAME/BLOCK_NAME): error calling template() function ...
The corresponding .block file either has an incorrectly defined <template> element or contains JavaScript errors inside its template() function.
-
BlockError(PLUGIN_NAME/BLOCK_NAME): error calling template() function - "TypeError: Found invalid FieldDropdown options."
This particular error means that one of the puzzle's dropdown fields has an unnamed option. See dropdown field for more information.
BlockError(PLUGIN_NAME/BLOCK_NAME): error calling code() function ...
The corresponding .block file contains JavaScript errors inside its code() function.
PluginError(PLUGIN_NAME): error parsing init.plug file - "This page contains the following errors:error on line ..."
This means that there are XML errors in the plugin's init.plug file
preventing it from being parsed. For example, the missing
ending <category> tag leads to such error:
<category name="MyAwesomePlugin" color="green">
<block type="myPuzzle"></block>
PluginError(PLUGIN_NAME): error parsing init.plug file - "ReferenceError (SyntaxError, TypeError, etc...) ..."
The error occurs in case if the code in the corresponding init.plug file's <script> element contains a JavaScript error of the kind specified in the error message.
PluginError(PLUGIN_NAME): error calling code() function ...
The plugin's init.plug file contains JavaScript errors inside its code() function.
Puzzle "PLUGIN_NAME/BLOCK_NAME" is not defined properly. Replaced with a dummy block.
This error message usually appears after one of BlockError and/or PluginError messages and simply indicates that the mentioned puzzle block wasn't properly loaded and initialized due to the original errors. In order to still be able to load puzzles and maintain their operability to some degree, such blocks (both in the plugin's toolbox category and on a workspace) are replaced with special dummy blocks. The example of what a dummy block looks like can be seen on this picture.
Sharing your Plugin
Once you finished with your plugin, feel free to share it by:
- Posting the link on the Verge3D Forums.
- Writing a wiki article and/or getting mentioned in the List of Verge3D Plugins directory.
- Sharing on Twitter/Facebook/LinkedIn by using the #verge3d hashtag (we'll surely repost!).
- Sharing on Reddit. There are subreddits we moderate, so you will never get banned: r/RealVerge3D, r/3dcommerce, and 3dconfigurators.
- Selling on Gumroad or similar resources.
Having Troubles with Puzzles?
Seek help on the forums!