From 4d7445a2c2c2b8c1fac02f44be487b9f9e986ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Mon, 17 Jun 2024 14:09:16 -0300 Subject: [PATCH] Decouple block type from block variant And use Variant.Type enum for the Value block variant. This means we stick to GDScript variants for the values represented by Value blocks: - A constant like "viewport width" - A property like "rotation" - A parameter like A in "A + B" - The resulting value of "A + B" The slots that can contain Value blocks also have a matching variant type now. Unfortunately there is no way to convert the native Variant.Type enum to string, so still we need dictionaries to go back and forth the string formatting. Previously there was a custom NODE type, although unused. This is now replaced by NODE_PATH, which is the same thing that Godot does in the PackedScene resource when exporting a property of type Node. Also: improve readability of drag manager and use a constant for the minimum slot distance. --- .../block_code/drag_manager/drag_manager.gd | 61 +++++++++-------- addons/block_code/types/types.gd | 51 +++++++++----- .../ui/blocks/entry_block/entry_block.gd | 1 + .../ui/blocks/entry_block/entry_block.tscn | 5 +- .../blocks/parameter_block/parameter_block.gd | 2 + .../blocks/statement_block/statement_block.gd | 6 +- .../parameter_input/parameter_input.gd | 12 ++-- .../blocks/utilities/snap_point/snap_point.gd | 3 + addons/block_code/ui/constants.gd | 1 + .../ui/picker/categories/category_factory.gd | 66 +++++++------------ test_game/test_game.tscn | 6 +- 11 files changed, 114 insertions(+), 100 deletions(-) diff --git a/addons/block_code/drag_manager/drag_manager.gd b/addons/block_code/drag_manager/drag_manager.gd index fdc21cf9..7738d547 100644 --- a/addons/block_code/drag_manager/drag_manager.gd +++ b/addons/block_code/drag_manager/drag_manager.gd @@ -8,6 +8,8 @@ signal block_modified @export var picker_path: NodePath @export var block_canvas_path: NodePath +const Constants = preload("res://addons/block_code/ui/constants.gd") + var drag_offset: Vector2 var dragging: Block = null @@ -39,38 +41,41 @@ func _process(_delta): var closest_snap_point: SnapPoint = null var closest_dist: float = INF var snap_points: Array[Node] = get_tree().get_nodes_in_group("snap_point") - for n in snap_points: - if n is SnapPoint: - var snap_point: SnapPoint = n as SnapPoint - if snap_point.block == null: - push_error("Warning: a snap point does not reference it's parent block.") - continue - if snap_point.block.on_canvas: - if Types.can_cast(dragging.block_type, snap_point.block_type): - var snap_global_pos: Vector2 = snap_point.get_global_rect().position - var temp_dist: float = dragging_global_pos.distance_to(snap_global_pos) - if temp_dist < closest_dist: - # Check if any parent node is this node - var is_child: bool = false - var parent = snap_point - while parent is SnapPoint: - if parent.block == dragging: - is_child = true - - parent = parent.block.get_parent() - - if not is_child: - closest_dist = temp_dist - closest_snap_point = snap_point - - if closest_dist > 80.0: - closest_snap_point = null + for snap_point in snap_points: + if not snap_point is SnapPoint: + push_error('Warning: a node in group "snap_point"snap is not of class SnapPoint.') + continue + if snap_point.block == null: + push_error("Warning: a snap point does not reference it's parent block.") + continue + if not snap_point.block.on_canvas: + # We only snap to blocks on the canvas: + continue + if dragging.block_type != snap_point.block_type: + # We only snap to the same block type: + continue + if dragging.block_type == Types.BlockType.VALUE and not Types.can_cast(dragging.variant_type, snap_point.variant_type): + # We only snap Value blocks to snaps that can cast to same variant: + continue + var snap_global_pos: Vector2 = snap_point.get_global_rect().position + var temp_dist: float = dragging_global_pos.distance_to(snap_global_pos) + if temp_dist <= Constants.MINIMUM_SNAP_DISTANCE and temp_dist < closest_dist: + # Check if any parent node is this node + var is_child: bool = false + var parent = snap_point + while parent is SnapPoint: + if parent.block == dragging: + is_child = true + + parent = parent.block.get_parent() + + if not is_child: + closest_dist = temp_dist + closest_snap_point = snap_point if closest_snap_point != previewing_snap_point: _update_preview(closest_snap_point) - # TODO: make sure dragging.block_type is the same as snap_point type - func _update_preview(snap_point: SnapPoint): previewing_snap_point = snap_point diff --git a/addons/block_code/types/types.gd b/addons/block_code/types/types.gd index 8f8b03e6..75162777 100644 --- a/addons/block_code/types/types.gd +++ b/addons/block_code/types/types.gd @@ -3,23 +3,38 @@ extends Node enum BlockType { NONE, - EXECUTE, ENTRY, - # Parameters - STRING, - INT, - FLOAT, - VECTOR2, - BOOL, - COLOR, - NODE + EXECUTE, + VALUE, +} + +const VARIANT_TYPE_TO_STRING: Dictionary = { + TYPE_STRING: "STRING", + TYPE_INT: "INT", + TYPE_FLOAT: "FLOAT", + TYPE_BOOL: "BOOL", + TYPE_VECTOR2: "VECTOR2", + TYPE_COLOR: "COLOR", + TYPE_NODE_PATH: "NODE_PATH", + TYPE_NIL: "NIL", +} + +const STRING_TO_VARIANT_TYPE: Dictionary = { + "STRING": TYPE_STRING, + "INT": TYPE_INT, + "FLOAT": TYPE_FLOAT, + "BOOL": TYPE_BOOL, + "VECTOR2": TYPE_VECTOR2, + "COLOR": TYPE_COLOR, + "NODE_PATH": TYPE_NODE_PATH, + "NIL": TYPE_NIL, } const cast_relationships = [ - [BlockType.INT, BlockType.FLOAT, "float(%s)"], - [BlockType.FLOAT, BlockType.INT, "int(%s)"], - [BlockType.INT, BlockType.STRING, "str(%s)"], - [BlockType.FLOAT, BlockType.STRING, "str(%s)"], + [TYPE_INT, TYPE_FLOAT, "float(%s)"], + [TYPE_FLOAT, TYPE_INT, "int(%s)"], + [TYPE_INT, TYPE_STRING, "str(%s)"], + [TYPE_FLOAT, TYPE_STRING, "str(%s)"], ] # Directed graph, edges are CastGraphEdge @@ -27,10 +42,10 @@ static var cast_graph: Dictionary class CastGraphEdge: - var to: BlockType + var to: Variant.Type var cast_format: String - func _init(p_to: BlockType, p_cast_format: String): + func _init(p_to: Variant.Type, p_cast_format: String): to = p_to cast_format = p_cast_format @@ -56,7 +71,7 @@ static var dist: Dictionary const INT_MAX: int = 1000000000 -static func dijkstra(source: BlockType): +static func dijkstra(source: Variant.Type): prev = {} dist = {} @@ -86,7 +101,7 @@ static func dijkstra(source: BlockType): queue.update_priority(v, alt) -static func can_cast(type: BlockType, parent_type: BlockType) -> bool: +static func can_cast(type: Variant.Type, parent_type: Variant.Type) -> bool: if type == parent_type: return true @@ -96,7 +111,7 @@ static func can_cast(type: BlockType, parent_type: BlockType) -> bool: return false -static func cast(val: String, type: BlockType, parent_type: BlockType): +static func cast(val: String, type: Variant.Type, parent_type: Variant.Type): if type == parent_type: return val diff --git a/addons/block_code/ui/blocks/entry_block/entry_block.gd b/addons/block_code/ui/blocks/entry_block/entry_block.gd index fca02b87..fc8b1d91 100644 --- a/addons/block_code/ui/blocks/entry_block/entry_block.gd +++ b/addons/block_code/ui/blocks/entry_block/entry_block.gd @@ -4,6 +4,7 @@ extends StatementBlock func _ready(): + block_type = Types.BlockType.ENTRY super() diff --git a/addons/block_code/ui/blocks/entry_block/entry_block.tscn b/addons/block_code/ui/blocks/entry_block/entry_block.tscn index 5e22e55b..e1351721 100644 --- a/addons/block_code/ui/blocks/entry_block/entry_block.tscn +++ b/addons/block_code/ui/blocks/entry_block/entry_block.tscn @@ -7,4 +7,7 @@ script = ExtResource("2_3ik8h") block_name = "entry_block" label = "EntryBlock" -block_type = 2 +block_type = 1 + +[node name="Background" parent="VBoxContainer/TopMarginContainer" index="0"] +show_top = false diff --git a/addons/block_code/ui/blocks/parameter_block/parameter_block.gd b/addons/block_code/ui/blocks/parameter_block/parameter_block.gd index fedae5fb..76b2be75 100644 --- a/addons/block_code/ui/blocks/parameter_block/parameter_block.gd +++ b/addons/block_code/ui/blocks/parameter_block/parameter_block.gd @@ -4,6 +4,7 @@ extends Block @export var block_format: String = "" @export var statement: String = "" +@export var variant_type: Variant.Type @onready var _panel := $Panel @onready var _hbox := %HBoxContainer @@ -15,6 +16,7 @@ var param_input_strings: Dictionary # Only loaded from serialized func _ready(): super() + block_type = Types.BlockType.VALUE var new_panel = _panel.get_theme_stylebox("panel").duplicate() new_panel.bg_color = color new_panel.border_color = color.darkened(0.2) diff --git a/addons/block_code/ui/blocks/statement_block/statement_block.gd b/addons/block_code/ui/blocks/statement_block/statement_block.gd index 1cf7525a..3f7dff8b 100644 --- a/addons/block_code/ui/blocks/statement_block/statement_block.gd +++ b/addons/block_code/ui/blocks/statement_block/statement_block.gd @@ -100,12 +100,12 @@ static func format_string(parent_block: Block, attach_to: Node, string: String) var split := param.split(": ") var param_name := split[0] var param_type_str := split[1] - var param_type := Types.BlockType.get(param_type_str) + var param_type: Variant.Type = Types.STRING_TO_VARIANT_TYPE[param_type_str] var param_input: ParameterInput = preload("res://addons/block_code/ui/blocks/utilities/parameter_input/parameter_input.tscn").instantiate() param_input.name = "ParameterInput%d" % start # Unique path param_input.placeholder = param_name - param_input.block_type = param_type + param_input.variant_type = param_type param_input.block = parent_block param_input.text_modified.connect(func(): parent_block.modified.emit()) attach_to.add_child(param_input) @@ -115,7 +115,7 @@ static func format_string(parent_block: Block, attach_to: Node, string: String) var new_block: Block = preload("res://addons/block_code/ui/blocks/parameter_block/parameter_block.tscn").instantiate() new_block.block_format = param_name new_block.statement = param_name - new_block.block_type = param_type + new_block.variant_type = param_type new_block.color = parent_block.color param_input.block_type = Types.BlockType.NONE param_input.snap_point.block_type = Types.BlockType.NONE # Necessary because already called ready diff --git a/addons/block_code/ui/blocks/utilities/parameter_input/parameter_input.gd b/addons/block_code/ui/blocks/utilities/parameter_input/parameter_input.gd index 1d5fac1d..8f846c76 100644 --- a/addons/block_code/ui/blocks/utilities/parameter_input/parameter_input.gd +++ b/addons/block_code/ui/blocks/utilities/parameter_input/parameter_input.gd @@ -9,7 +9,8 @@ signal text_modified @export var block_path: NodePath -@export var block_type: Types.BlockType = Types.BlockType.STRING +@export var variant_type: Variant.Type = TYPE_STRING +@export var block_type: Types.BlockType = Types.BlockType.VALUE var block: Block @@ -41,6 +42,7 @@ func _ready(): block = get_node_or_null(block_path) snap_point.block = block snap_point.block_type = block_type + snap_point.variant_type = variant_type # Do something with block_type to restrict input @@ -50,16 +52,16 @@ func get_snapped_block() -> Block: func get_string() -> String: - var snapped_block: Block = get_snapped_block() + var snapped_block: ParameterBlock = get_snapped_block() as ParameterBlock if snapped_block: var generated_string = snapped_block.get_parameter_string() - return Types.cast(generated_string, snapped_block.block_type, block_type) + return Types.cast(generated_string, snapped_block.variant_type, variant_type) var text: String = get_plain_text() - if block_type == Types.BlockType.STRING: + if variant_type == TYPE_STRING: text = "'%s'" % text.replace("\\", "\\\\").replace("'", "\\'") - if block_type == Types.BlockType.VECTOR2: + elif variant_type == TYPE_VECTOR2: text = "Vector2(%s)" % text return text diff --git a/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd b/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd index 42a01e8a..f32f5916 100644 --- a/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd +++ b/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd @@ -6,6 +6,9 @@ extends MarginContainer @export var block_type: Types.BlockType = Types.BlockType.EXECUTE +## Only used when block_type is a [enum Types.BlockType.VALUE]. +@export var variant_type: Variant.Type + var block: Block diff --git a/addons/block_code/ui/constants.gd b/addons/block_code/ui/constants.gd index 1d7e98a9..d36d45aa 100644 --- a/addons/block_code/ui/constants.gd +++ b/addons/block_code/ui/constants.gd @@ -6,3 +6,4 @@ const KNOB_H = 5.0 const KNOB_Z = 5.0 const CONTROL_MARGIN = 20.0 const OUTLINE_WIDTH = 3.0 +const MINIMUM_SNAP_DISTANCE = 80.0 diff --git a/addons/block_code/ui/picker/categories/category_factory.gd b/addons/block_code/ui/picker/categories/category_factory.gd index 08161af7..62eee529 100644 --- a/addons/block_code/ui/picker/categories/category_factory.gd +++ b/addons/block_code/ui/picker/categories/category_factory.gd @@ -84,7 +84,7 @@ static func get_general_categories() -> Array[BlockCategory]: test_list.append(b) b = BLOCKS["entry_block"].instantiate() - b.block_format = "On body enter [body: NODE]" + b.block_format = "On body enter [body: NODE_PATH]" b.statement = "func _on_body_enter(body):" test_list.append(b) @@ -96,7 +96,7 @@ static func get_general_categories() -> Array[BlockCategory]: b = BLOCKS["entry_block"].instantiate() # HACK: make signals work with new entry nodes. NONE instead of STRING type allows # plain text input for function name. Should revamp signals later - b.block_format = "On signal {signal: NONE}" + b.block_format = "On signal {signal: NIL}" b.statement = "func signal_{signal}():" signal_list.append(b) @@ -111,8 +111,8 @@ static func get_general_categories() -> Array[BlockCategory]: signal_list.append(b) b = BLOCKS["statement_block"].instantiate() - b.block_format = "Add {node: NODE} to group {group: STRING}" - b.statement = "{node}.add_to_group({group})" + b.block_format = "Add {node: NODE_PATH} to group {group: STRING}" + b.statement = "get_node({node}).add_to_group({group})" signal_list.append(b) b = BLOCKS["statement_block"].instantiate() @@ -121,20 +121,20 @@ static func get_general_categories() -> Array[BlockCategory]: signal_list.append(b) b = BLOCKS["statement_block"].instantiate() - b.block_format = "Remove {node: NODE} from group {group: STRING}" - b.statement = "{node}.remove_from_group({group})" + b.block_format = "Remove {node: NODE_PATH} from group {group: STRING}" + b.statement = "get_node({node}).remove_from_group({group})" signal_list.append(b) b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.BOOL + b.variant_type = TYPE_BOOL b.block_format = "Is in group {group: STRING}" b.statement = "is_in_group({group})" signal_list.append(b) b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.BOOL - b.block_format = "Is {node: NODE} in group {group: STRING}" - b.statement = "{node}.is_in_group({group})" + b.variant_type = TYPE_BOOL + b.block_format = "Is {node: NODE_PATH} in group {group: STRING}" + b.statement = "get_node({node}).is_in_group({group})" signal_list.append(b) var signal_category: BlockCategory = BlockCategory.new("Signal", signal_list, Color("f0c300")) @@ -158,7 +158,7 @@ static func get_general_categories() -> Array[BlockCategory]: variable_list.append(b) b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.INT + b.variant_type = TYPE_INT b.block_format = "Get Int {var: STRING}" b.statement = "VAR_DICT[{var}]" variable_list.append(b) @@ -174,31 +174,31 @@ static func get_general_categories() -> Array[BlockCategory]: var math_list: Array[Block] = [] b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.INT + b.variant_type = TYPE_INT b.block_format = "{a: INT} + {b: INT}" b.statement = "({a} + {b})" math_list.append(b) b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.INT + b.variant_type = TYPE_INT b.block_format = "{a: INT} - {b: INT}" b.statement = "({a} - {b})" math_list.append(b) b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.INT + b.variant_type = TYPE_INT b.block_format = "{a: INT} * {b: INT}" b.statement = "({a} * {b})" math_list.append(b) b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.INT + b.variant_type = TYPE_INT b.block_format = "{a: INT} / {b: INT}" b.statement = "({a} / {b})" math_list.append(b) b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.INT + b.variant_type = TYPE_INT b.block_format = "{base: INT} ^ {exp: INT}" b.statement = "(pow({base}, {exp}))" math_list.append(b) @@ -211,20 +211,20 @@ static func get_general_categories() -> Array[BlockCategory]: for op in ["==", ">", "<", ">=", "<=", "!="]: b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.BOOL + b.variant_type = TYPE_BOOL b.block_format = "{int1: INT} %s {int2: INT}" % op b.statement = "({int1} %s {int2})" % op logic_list.append(b) for op in ["and", "or"]: b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.BOOL + b.variant_type = TYPE_BOOL b.block_format = "{bool1: BOOL} %s {bool2: BOOL}" % op b.statement = "({bool1} %s {bool2})" % op logic_list.append(b) b = BLOCKS["parameter_block"].instantiate() - b.block_type = Types.BlockType.BOOL + b.variant_type = TYPE_BOOL b.block_format = "Not {bool: BOOL}" b.statement = "(!{bool})" logic_list.append(b) @@ -264,31 +264,13 @@ static func add_to_categories(main: Array[BlockCategory], addition: Array[BlockC return main -static func built_in_type_to_block_type(type: Variant.Type): - match type: - TYPE_BOOL: - return Types.BlockType.BOOL - TYPE_INT: - return Types.BlockType.INT - TYPE_FLOAT: - return Types.BlockType.FLOAT - TYPE_STRING: - return Types.BlockType.STRING - TYPE_VECTOR2: - return Types.BlockType.VECTOR2 - TYPE_COLOR: - return Types.BlockType.COLOR - - return null - - static func property_to_blocklist(property: Dictionary) -> Array[Block]: var block_list: Array[Block] = [] - var block_type = built_in_type_to_block_type(property.type) + var block_type = property.type if block_type: - var type_string: String = Types.BlockType.find_key(block_type) + var type_string: String = Types.VARIANT_TYPE_TO_STRING[block_type] var b = BLOCKS["statement_block"].instantiate() b.block_format = "Set %s to {value: %s}" % [property.name.capitalize(), type_string] @@ -376,19 +358,19 @@ static func _get_input_blocks() -> Array[Block]: for action: StringName in InputMap.get_actions(): var block: Block = BLOCKS["parameter_block"].instantiate() - block.block_type = Types.BlockType.BOOL + block.variant_type = TYPE_BOOL block.block_format = "Is action %s pressed" % action block.statement = 'Input.is_action_pressed("%s")' % action block_list.append(block) block = BLOCKS["parameter_block"].instantiate() - block.block_type = Types.BlockType.BOOL + block.variant_type = TYPE_BOOL block.block_format = "Is action %s just pressed" % action block.statement = 'Input.is_action_just_pressed("%s")' % action block_list.append(block) block = BLOCKS["parameter_block"].instantiate() - block.block_type = Types.BlockType.BOOL + block.variant_type = TYPE_BOOL block.block_format = "Is action %s just released" % action block.statement = 'Input.is_action_just_released("%s")' % action block_list.append(block) diff --git a/test_game/test_game.tscn b/test_game/test_game.tscn index cea07b82..30440c27 100644 --- a/test_game/test_game.tscn +++ b/test_game/test_game.tscn @@ -241,7 +241,7 @@ path_child_pairs = [] [sub_resource type="Resource" id="Resource_7f05b"] script = ExtResource("3_dpt5n") block_class = &"ParameterBlock" -serialized_props = [["block_name", "parameter_block"], ["label", "Param"], ["color", Color(0.941176, 0.764706, 0, 1)], ["block_type", 6], ["position", Vector2(0, 0)], ["block_format", "Is {node: NODE} in group {group: STRING}"], ["statement", "{node}.is_in_group({group})"], ["param_input_strings", { +serialized_props = [["block_name", "parameter_block"], ["label", "Param"], ["color", Color(0.941176, 0.764706, 0, 1)], ["block_type", 6], ["position", Vector2(0, 0)], ["block_format", "Is {node: NODE_PATH} in group {group: STRING}"], ["statement", "get_node({node}).is_in_group({group})"], ["param_input_strings", { "group": "Enemy", "node": "" }]] @@ -278,7 +278,7 @@ path_child_pairs = [[NodePath("VBoxContainer/MarginContainer/Rows/Row0/RowHBoxCo [sub_resource type="Resource" id="Resource_um7l6"] script = ExtResource("3_dpt5n") block_class = &"EntryBlock" -serialized_props = [["block_name", "entry_block"], ["label", "EntryBlock"], ["color", Color(0.6, 0.537255, 0.87451, 1)], ["block_type", 2], ["position", Vector2(472, 469)], ["block_format", "On body enter [body: NODE]"], ["statement", "func _on_body_enter(body):"], ["param_input_strings", { +serialized_props = [["block_name", "entry_block"], ["label", "EntryBlock"], ["color", Color(0.6, 0.537255, 0.87451, 1)], ["block_type", 2], ["position", Vector2(472, 469)], ["block_format", "On body enter [body: NODE_PATH]"], ["statement", "func _on_body_enter(body):"], ["param_input_strings", { "body": "" }]] @@ -302,7 +302,7 @@ path_child_pairs = [] [sub_resource type="Resource" id="Resource_g4l32"] script = ExtResource("3_dpt5n") block_class = &"EntryBlock" -serialized_props = [["block_name", "entry_block"], ["label", "EntryBlock"], ["color", Color(0.941176, 0.764706, 0, 1)], ["block_type", 2], ["position", Vector2(537, 268)], ["block_format", "On signal {signal: NONE}"], ["statement", "func signal_{signal}():"], ["param_input_strings", { +serialized_props = [["block_name", "entry_block"], ["label", "EntryBlock"], ["color", Color(0.941176, 0.764706, 0, 1)], ["block_type", 2], ["position", Vector2(537, 268)], ["block_format", "On signal {signal: NIL}"], ["statement", "func signal_{signal}():"], ["param_input_strings", { "signal": "will_hi" }]]