Skip to content

Commit af353e1

Browse files
authored
Merge pull request #19 from endlessm/godotcon
Update plugin & add GodotCon demo
2 parents b496173 + 0f6fb85 commit af353e1

File tree

193 files changed

+9528
-8625
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

193 files changed

+9528
-8625
lines changed

addons/block_code/README.md

+35-8
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@ Our aim is to reduce the learning curve faced by learners who are on the early p
1111

1212
With this project, we aim to reduce the height of the mountain that such learners have to climb. Specifically, we aim to eliminate the requirement of learners having to simultaneously learn to code while building their first games. Instead of writing GDScript to implement games, this plugin enables learners use block coding. Tools like [Scratch](https://scratch.mit.edu/), [Blockly](https://developers.google.com/blockly), and [MakeCode](https://www.microsoft.com/en-us/makecode) have demonstrated that block coding can be much more accessible and intuitive to beginners than textual programming—we are bringing those concepts into Godot to help learners become familiar with some aspects of Godot itself while simplifying the creation of their first games.
1313

14-
### Constraints
15-
16-
In order to be learner-friendly, we have to implement blocks at a suitable level of abstraction. For example, in GDScript you would typically move a sprite around the screen by examining input events and adjusting a sprite's movement vector accordingly—but we do not wish to express this level of detail in blocks. Instead, we lean much more towards the kinds of blocks you can find in MakeCode Arcade, such as having a single block for "move mySprite with buttons".
17-
18-
Expressing an appropriate layer of abstraction is perhaps the most challenging aspect of this project, and will likely place limits upon what can be achieved with this tool. We do not aim to express the full power of Godot & GDScript with this block coding plugin, but rather, our objective is to provide a gentler introduction to Godot for learners, such that they can get familiar with other aspects of the Godot Editor and learn programming concepts while creating basic games. We envision that learners would use block coding as a stepping stone and then later progress onto learning GDScript.
14+
In order to be learner-friendly, we implement blocks at a suitable level of abstraction. For example, we have blocks that allow the user to trivially connect keyboard input to the movement of a particular game element, and to make the score show up on-screen. That abstraction does place limits on what can be achieved with this tool, while still allowing us to provide a gentler introduction to Godot for learners, such that they can get familiar with other aspects of the Godot Editor and learn programming concepts while creating basic games. We envision that learners would use block coding as a stepping stone and then later progress onto learning GDScript.
1915

2016
That said, we are in no way opposed to having this project grow to be able to create more complex games, as long as it does not negatively affect the experience for early stage learners.
2117

18+
See our [pedagogy and audience documentation](docs/PEDAGOGY.md) for more info.
19+
2220
## Getting Started
2321

2422
1. Install the plugin through the Godot AssetLib searching for the name
@@ -32,11 +30,11 @@ That said, we are in no way opposed to having this project grow to be able to cr
3230

3331
2. Make sure to enable the plugin in **Project****Project Settings****Plugins**.
3432

35-
3. You're ready to get started! Open a scene, and add a **BlockCode** child node to any node in the scene using the **Add Child Node** menu dialog.
33+
3. You're ready to get started! Open a scene, select a node, and observe that there's a **Block Code** section within the lower central pane of the Godot editor, where you usually find debugging, animation and shader functionality. Click **Block Code** and then use the **Add Block Code** button to create a block canvas.
3634

37-
4. The **Block Code** editor will open in a new tab. Drag blocks from the picker and snap them together to create a script. You can switch to other Block Code scripts by selecting the respective BlockCode node in the scene tree.
35+
4. Drag blocks from the picker and snap them together to create a script. You can switch to other Block Code scripts by selecting the respective node from the scene tree.
3836

39-
5. **Run** the scene to see your Block Code scripts in action. Block Code scripts are attached to the BlockCode node's parent, and are saved to the scene.
37+
5. **Run** the scene to see your Block Code scripts in action. Block Code scripts are saved within the scene.
4038

4139
If you clone the plugin's git repository and open it in Godot, you will be presented with a block-built Pong game as an example.
4240

@@ -50,6 +48,20 @@ We will now seek feedback from learners, educators and game makers, as well as r
5048
- Should this be a plugin or an extension?
5149
- Should blocks generate GDScript or be dynamically executed?
5250

51+
There is no language or data format stability implemented or expected in these early stages. If you upgrade the block coding plugin within an existing project, expect any existing block scripts to stop working and need reimplementing from scratch. For now, you probably want to avoid updating the plugin within your project if it's meeting your needs, or only doing that very sporadically. We will consider offering stability guarantees in future stages of development.
52+
53+
## General user guidance
54+
55+
Block scripts run against the node where you created them. The "Queue Free" block is going to free that node, not any other.
56+
57+
The selection of available blocks varies based on the node type. For example, create a block script on an `Area2D` and you will notice that you have an `On body entered` signal handling block available. Create a node script on an `AnimationPlayer` node and you will observe blocks for starting and stopping animations.
58+
59+
If you wish to switch context to another node, you need to define a function in that other node, and then call it. Once execution jumps into that function, blocks will now act against that other node, and you'll have access to type-specific blocks belonging to that other node. You'll need do this kind of thing if you want to trigger the freeing of another node, or trigger an animation to start playing. This is both strong in conveying the concepts of objects and encapsulation, while also a bit tedious - we may revisit in future!
60+
61+
We have some high level blocks for simplifying common game elements. Add a SimpleCharacter node to get a game element that can be connected to keyboard input with just one type-specific block. Add a SimpleScoring node to display a score on-screen, accompanied by simple blocks for adjusting that score.
62+
63+
Lean into animations! Godot's animations functionality goes beyond just simple animations of graphics. You can do so much by combining block coding with Godot's powerful animations editor.
64+
5365
## Feedback
5466

5567
Please share feedback in the [Godot Forum Block Coding thread](https://forum.godotengine.org/t/block-coding-high-level-block-based-visual-programming/68941).
@@ -90,3 +102,18 @@ There are several other GUT command line options for running specific tests.
90102
For example, `-gtest=path/to/test_script_1.gd,path/to/test_script_2.gd` can be
91103
used to run specific test scripts. A specific test function can be specified
92104
with `-gunit_test_name=test_to_run`.
105+
106+
### Using the Development Version of the Plugin
107+
108+
1. If your project already has the BlockCode plugin installed:
109+
1. Ensure you have committed your project to Git, including the `addons/block_code` directory.
110+
At this stage of development, **block code programs written for an older plugin version will
111+
likely not work with a newer version of the plugin**, so it is essential that you take a
112+
snapshot of your project before changing the plugin version.
113+
2. Under *Project**Project Settings…**Plugins*, disable the BlockCode plugin
114+
3. In the *FileSystem* sidebar, delete the `res://addons/block_code` directory
115+
2. Download
116+
[a development snapshot](https://github.com/endlessm/godot-block-coding/archive/main.zip)
117+
3. Under *AssetLib*, click *Import…*, and browse to the `main.zip` file you just downloaded
118+
4. Check the *☑ Ignore assert root* option, and click *Install*
119+
5. Under *Project**Project Settings…**Plugins*, enable the BlockCode plugin

addons/block_code/block_code_plugin.gd

+10-36
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@tool
22
extends EditorPlugin
3+
34
const MainPanelScene := preload("res://addons/block_code/ui/main_panel.tscn")
45
const MainPanel = preload("res://addons/block_code/ui/main_panel.gd")
56
const Types = preload("res://addons/block_code/types/types.gd")
@@ -12,7 +13,6 @@ const BlockInspectorPlugin := preload("res://addons/block_code/inspector_plugin/
1213
var block_inspector_plugin: BlockInspectorPlugin
1314

1415
var editor_inspector: EditorInspector
15-
var editor_selection: EditorSelection
1616

1717
var _selected_block_code: BlockCode
1818

@@ -24,8 +24,6 @@ const DISABLED_CLASSES := [
2424
"ParameterBlock",
2525
"StatementBlock",
2626
"SnapPoint",
27-
"BlockSerialization",
28-
"BlockSerializedProperties",
2927
"BlockScriptSerialization",
3028
"CategoryFactory",
3129
]
@@ -35,7 +33,6 @@ func _enter_tree():
3533
Types.init_cast_graph()
3634

3735
editor_inspector = EditorInterface.get_inspector()
38-
editor_selection = EditorInterface.get_selection()
3936

4037
main_panel = MainPanelScene.instantiate()
4138
main_panel.script_window_requested.connect(script_window_requested)
@@ -95,43 +92,20 @@ func _exit_tree():
9592

9693

9794
func _ready():
98-
connect("scene_changed", _on_scene_changed)
9995
editor_inspector.connect("edited_object_changed", _on_editor_inspector_edited_object_changed)
100-
_on_scene_changed(EditorInterface.get_edited_scene_root())
10196
_on_editor_inspector_edited_object_changed()
10297

10398

104-
func _on_scene_changed(scene_root: Node):
105-
main_panel.switch_scene(scene_root)
106-
107-
10899
func _on_editor_inspector_edited_object_changed():
109100
var edited_object = editor_inspector.get_edited_object()
110-
#var edited_node = edited_object as Node
111-
var selected_nodes = editor_selection.get_selected_nodes()
112-
113-
if edited_object is BlockCode:
101+
var block_code_node = edited_object as BlockCode
102+
if block_code_node:
103+
# If a block code node was explicitly selected, activate the
104+
# Block Code panel.
114105
make_bottom_panel_item_visible(main_panel)
115-
116-
if edited_object is BlockCode and selected_nodes.size() == 1 and edited_object.owner and edited_object != _selected_block_code:
117-
# If a BlockCode node is being edited, and it was explicitly selected
118-
# (as opposed to edited in the Inspector alone), select its parent node
119-
# as well. This provides a clearer indication of what is being edited.
120-
# Changing the selection will cause edited_object_changed to fire again,
121-
# so we will return early to avoid duplicate work.
122-
var parent_node = edited_object.get_parent()
123-
if parent_node:
124-
editor_selection.add_node.call_deferred(parent_node)
125-
return
126-
127-
if edited_object and edited_object.get_class() == "MultiNodeEdit":
128-
# If multiple nodes are shown in the inspector, we will find the first
129-
# BlockCode node in the list of selected nodes and use that. This
130-
# occurs when the user selects multiple items in the Scene panel, or
131-
# when we select the parent of a BlockCode node.
132-
edited_object = selected_nodes.filter(func(node): return node is BlockCode).pop_front()
133-
134-
var block_code_node = list_block_code_nodes_for_node(edited_object as Node).pop_front()
106+
else:
107+
# Find the first block code child.
108+
block_code_node = list_block_code_nodes_for_node(edited_object as Node).pop_front()
135109
select_block_code_node(block_code_node)
136110

137111

@@ -145,15 +119,15 @@ func select_block_code_node(block_code: BlockCode):
145119
if not is_block_code_editable(block_code):
146120
block_code = null
147121

148-
if _selected_block_code:
122+
if is_instance_valid(_selected_block_code):
149123
_selected_block_code.tree_entered.disconnect(_on_selected_block_code_changed)
150124
_selected_block_code.tree_exited.disconnect(_on_selected_block_code_changed)
151125
_selected_block_code.property_list_changed.disconnect(_on_selected_block_code_changed)
152126
editor_inspector.property_edited.disconnect(_on_editor_inspector_property_edited)
153127

154128
_selected_block_code = block_code
155129

156-
if _selected_block_code:
130+
if is_instance_valid(_selected_block_code):
157131
_selected_block_code.tree_entered.connect(_on_selected_block_code_changed)
158132
_selected_block_code.tree_exited.connect(_on_selected_block_code_changed)
159133
_selected_block_code.property_list_changed.connect(_on_selected_block_code_changed)
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1-
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://bpvefei72nh3a"]
1+
[gd_resource type="Resource" load_steps=5 format=3 uid="uid://bpvefei72nh3a"]
22

33
[ext_resource type="Script" path="res://addons/block_code/code_generation/block_definition.gd" id="1_5qal7"]
4+
[ext_resource type="Script" path="res://addons/block_code/code_generation/option_data.gd" id="1_auf06"]
5+
[ext_resource type="Script" path="res://addons/block_code/blocks/communication/groups.gd" id="1_p83c7"]
6+
7+
[sub_resource type="Resource" id="Resource_sus0f"]
8+
script = ExtResource("1_auf06")
9+
selected = 0
10+
items = []
411

512
[resource]
613
script = ExtResource("1_5qal7")
714
name = &"add_node_to_group"
15+
target_node_class = ""
816
description = "Add the node into the group"
917
category = "Communication | Groups"
1018
type = 2
1119
variant_type = 0
12-
display_template = "Add {node: OBJECT} to group {group: STRING}"
20+
display_template = "add {node: OBJECT} to group {group: STRING}"
1321
code_template = "{node}.add_to_group({group})"
14-
defaults = {}
22+
defaults = {
23+
"group": SubResource("Resource_sus0f")
24+
}
1525
signal_name = ""
1626
scope = ""
27+
extension_script = ExtResource("1_p83c7")
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://bvrmau8atjx1x"]
1+
[gd_resource type="Resource" load_steps=5 format=3 uid="uid://bvrmau8atjx1x"]
22

3+
[ext_resource type="Script" path="res://addons/block_code/code_generation/option_data.gd" id="1_aom4j"]
34
[ext_resource type="Script" path="res://addons/block_code/code_generation/block_definition.gd" id="1_bcm71"]
5+
[ext_resource type="Script" path="res://addons/block_code/blocks/communication/groups.gd" id="2_42ixf"]
6+
7+
[sub_resource type="Resource" id="Resource_fk0wa"]
8+
script = ExtResource("1_aom4j")
9+
selected = 0
10+
items = []
411

512
[resource]
613
script = ExtResource("1_bcm71")
@@ -10,8 +17,11 @@ description = "Add this node into the group"
1017
category = "Communication | Groups"
1118
type = 2
1219
variant_type = 0
13-
display_template = "Add to group {group: STRING}"
20+
display_template = "add to group {group: STRING}"
1421
code_template = "add_to_group({group})"
15-
defaults = {}
22+
defaults = {
23+
"group": SubResource("Resource_fk0wa")
24+
}
1625
signal_name = ""
1726
scope = ""
27+
extension_script = ExtResource("2_42ixf")

addons/block_code/blocks/communication/area2d_on_entered.tres

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ description = ""
1010
category = "Communication | Methods"
1111
type = 1
1212
variant_type = 0
13-
display_template = "On [body: OBJECT] entered"
14-
code_template = "func _on_body_entered(body: Node2D):
13+
display_template = "when this node collides with [something: OBJECT]"
14+
code_template = "func _on_body_entered(something: Node2D):
1515
"
1616
defaults = {}
1717
signal_name = "body_entered"

addons/block_code/blocks/communication/area2d_on_exited.tres

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ description = ""
1010
category = "Communication | Methods"
1111
type = 1
1212
variant_type = 0
13-
display_template = "On [body: OBJECT] exited"
14-
code_template = "func _on_body_exited(body: Node2D):
13+
display_template = "when this node stops colliding with [something: OBJECT]"
14+
code_template = "func _on_body_exited(something: Node2D):
1515
"
1616
defaults = {}
1717
signal_name = "body_exited"
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1-
[gd_resource type="Resource" load_steps=2 format=3 uid="uid://c15vtdfihdxb8"]
1+
[gd_resource type="Resource" load_steps=5 format=3 uid="uid://c15vtdfihdxb8"]
22

3+
[ext_resource type="Script" path="res://addons/block_code/code_generation/option_data.gd" id="1_3nuts"]
34
[ext_resource type="Script" path="res://addons/block_code/code_generation/block_definition.gd" id="1_mlm68"]
5+
[ext_resource type="Script" path="res://addons/block_code/blocks/communication/groups.gd" id="1_of577"]
6+
7+
[sub_resource type="Resource" id="Resource_f4ctg"]
8+
script = ExtResource("1_3nuts")
9+
selected = 0
10+
items = []
411

512
[resource]
613
script = ExtResource("1_mlm68")
714
name = &"call_method_group"
15+
target_node_class = ""
16+
description = "Calls the method/function on each member of the given group"
17+
category = "Communication | Methods"
818
type = 2
919
variant_type = 0
10-
display_template = "Call method {method_name: STRING} in group {group: STRING}"
20+
display_template = "call method {method_name: STRING} in group {group: STRING}"
1121
code_template = "get_tree().call_group({group}, {method_name})"
12-
description = "Calls the method/function on each member of the given group"
13-
category = "Communication | Methods"
14-
defaults = {}
22+
defaults = {
23+
"group": SubResource("Resource_f4ctg")
24+
}
1525
signal_name = ""
26+
scope = ""
27+
extension_script = ExtResource("1_of577")

addons/block_code/blocks/communication/call_method_node.tres

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
[resource]
66
script = ExtResource("1_pg363")
77
name = &"call_method_node"
8+
target_node_class = ""
89
description = "Calls the method/function of the given node"
910
category = "Communication | Methods"
1011
type = 2
1112
variant_type = 0
12-
display_template = "Call method {method_name: STRING} in node {node: OBJECT}"
13+
display_template = "call method {method_name: STRING} on node {node: OBJECT}"
1314
code_template = "{node}.call({method_name})"
1415
defaults = {}
1516
signal_name = ""

addons/block_code/blocks/communication/define_method.tres

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
[resource]
66
script = ExtResource("1_6e473")
77
name = &"define_method"
8+
target_node_class = ""
89
description = "Define a method/function with following statements"
910
category = "Communication | Methods"
1011
type = 1
1112
variant_type = 0
12-
display_template = "Define method {method_name: NIL}"
13+
display_template = "define method {method_name: STRING_NAME}"
1314
code_template = "func {method_name}():"
1415
defaults = {}
1516
signal_name = ""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@tool
2+
extends BlockExtension
3+
4+
const OptionData = preload("res://addons/block_code/code_generation/option_data.gd")
5+
const Util = preload("res://addons/block_code/ui/util.gd")
6+
7+
8+
func _find_paths(paths: Array[NodePath], node: Node, path_root: Node, block_parent: Node):
9+
# Add any non-BlockCode nodes that aren't the parent of the current
10+
# BlockCode node.
11+
if not node is BlockCode:
12+
var node_path: NodePath = Util.node_scene_path(node, block_parent, path_root)
13+
if not node_path in [^"", ^"."]:
14+
paths.append(node_path)
15+
16+
for child in node.get_children():
17+
_find_paths(paths, child, path_root, block_parent)
18+
19+
20+
func get_defaults_for_node(context_node: Node) -> Dictionary:
21+
# The default paths are only needed in the editor.
22+
if not Engine.is_editor_hint():
23+
return {}
24+
25+
var scene_root: Node = EditorInterface.get_edited_scene_root()
26+
var path_root: Node = scene_root.get_parent()
27+
var paths: Array[NodePath]
28+
_find_paths(paths, scene_root, path_root, context_node)
29+
30+
if not paths:
31+
return {}
32+
33+
return {"path": OptionData.new(paths)}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[gd_resource type="Resource" load_steps=5 format=3 uid="uid://canpdkahokjqs"]
2+
3+
[ext_resource type="Script" path="res://addons/block_code/code_generation/option_data.gd" id="1_bk47y"]
4+
[ext_resource type="Script" path="res://addons/block_code/code_generation/block_definition.gd" id="1_d60g7"]
5+
[ext_resource type="Script" path="res://addons/block_code/blocks/communication/get_node.gd" id="1_we5wl"]
6+
7+
[sub_resource type="Resource" id="Resource_esr4a"]
8+
script = ExtResource("1_bk47y")
9+
selected = 0
10+
items = []
11+
12+
[resource]
13+
script = ExtResource("1_d60g7")
14+
name = &"get_node"
15+
target_node_class = ""
16+
description = "Get the node at the given path"
17+
category = "Communication | Nodes"
18+
type = 3
19+
variant_type = 24
20+
display_template = "{path: NIL}"
21+
code_template = "get_node(\"{path}\")"
22+
defaults = {
23+
"path": SubResource("Resource_esr4a")
24+
}
25+
signal_name = ""
26+
scope = ""
27+
extension_script = ExtResource("1_we5wl")

0 commit comments

Comments
 (0)