diff --git a/addons/block_code/examples/spawner/ball.tscn b/addons/block_code/examples/spawner/ball.tscn new file mode 100644 index 00000000..4ef4a36d --- /dev/null +++ b/addons/block_code/examples/spawner/ball.tscn @@ -0,0 +1,29 @@ +[gd_scene load_steps=4 format=3 uid="uid://c7l70grmkauij"] + +[ext_resource type="Texture2D" uid="uid://bcgr5amsq3jfl" path="res://addons/block_code/examples/pong_game/assets/ball.png" id="2_xkrmm"] + +[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_c3m63"] +friction = 0.0 +bounce = 1.0 + +[sub_resource type="CircleShape2D" id="CircleShape2D_sntrn"] +radius = 64.0 + +[node name="Ball" type="RigidBody2D" groups=["balls"]] +collision_layer = 2 +collision_mask = 15 +physics_material_override = SubResource("PhysicsMaterial_c3m63") +continuous_cd = 1 +contact_monitor = true +max_contacts_reported = 1 +linear_velocity = Vector2(353.553, 353.553) +linear_damp_mode = 1 +angular_damp_mode = 1 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +unique_name_in_owner = true +shape = SubResource("CircleShape2D_sntrn") + +[node name="Sprite2D" type="Sprite2D" parent="."] +unique_name_in_owner = true +texture = ExtResource("2_xkrmm") diff --git a/addons/block_code/examples/spawner/spawner.tscn b/addons/block_code/examples/spawner/spawner.tscn new file mode 100644 index 00000000..1ec58202 --- /dev/null +++ b/addons/block_code/examples/spawner/spawner.tscn @@ -0,0 +1,269 @@ +[gd_scene load_steps=37 format=3 uid="uid://cbgxxdewi6gld"] + +[ext_resource type="Script" path="res://addons/block_code/simple_spawner/simple_spawner.gd" id="1_g2l2s"] +[ext_resource type="PackedScene" uid="uid://c7l70grmkauij" path="res://addons/block_code/examples/spawner/ball.tscn" id="2_d0h86"] +[ext_resource type="PackedScene" uid="uid://c8hrliwojohal" path="res://addons/block_code/examples/spawner/volatile_ball.tscn" id="3_tt12o"] +[ext_resource type="Script" path="res://addons/block_code/block_code_node/block_code.gd" id="4_e0fbh"] +[ext_resource type="Script" path="res://addons/block_code/serialization/block_serialization_tree.gd" id="5_g4h7g"] +[ext_resource type="Script" path="res://addons/block_code/serialization/block_serialization.gd" id="6_dv2kl"] +[ext_resource type="Script" path="res://addons/block_code/serialization/value_block_serialization.gd" id="7_cykhe"] +[ext_resource type="Script" path="res://addons/block_code/serialization/block_script_serialization.gd" id="8_tovvd"] +[ext_resource type="Script" path="res://addons/block_code/code_generation/variable_definition.gd" id="9_m8g1j"] + +[sub_resource type="Resource" id="Resource_1x252"] +script = ExtResource("6_dv2kl") +name = &"simplespawner_start_spawning" +children = Array[ExtResource("6_dv2kl")]([]) +arguments = {} + +[sub_resource type="Resource" id="Resource_1j04f"] +script = ExtResource("6_dv2kl") +name = &"ready" +children = Array[ExtResource("6_dv2kl")]([SubResource("Resource_1x252")]) +arguments = {} + +[sub_resource type="Resource" id="Resource_jiosv"] +script = ExtResource("5_g4h7g") +root = SubResource("Resource_1j04f") +canvas_position = Vector2(54, 47) + +[sub_resource type="Resource" id="Resource_uan44"] +script = ExtResource("7_cykhe") +name = &"is_input_actioned" +arguments = { +"action": "just_pressed", +"action_name": &"ui_right" +} + +[sub_resource type="Resource" id="Resource_uv0fo"] +script = ExtResource("7_cykhe") +name = &"simplespawner_get_spawn_frequency" +arguments = {} + +[sub_resource type="Resource" id="Resource_l45hk"] +script = ExtResource("7_cykhe") +name = &"subtract" +arguments = { +"a": SubResource("Resource_uv0fo"), +"b": 0.1 +} + +[sub_resource type="Resource" id="Resource_6g0ng"] +script = ExtResource("6_dv2kl") +name = &"simplespawner_set_spawn_frequency" +children = Array[ExtResource("6_dv2kl")]([]) +arguments = { +"new_frequency": SubResource("Resource_l45hk") +} + +[sub_resource type="Resource" id="Resource_ke4bk"] +script = ExtResource("6_dv2kl") +name = &"if" +children = Array[ExtResource("6_dv2kl")]([SubResource("Resource_6g0ng")]) +arguments = { +"condition": SubResource("Resource_uan44") +} + +[sub_resource type="Resource" id="Resource_67w0v"] +script = ExtResource("7_cykhe") +name = &"is_input_actioned" +arguments = { +"action": "just_pressed", +"action_name": &"ui_left" +} + +[sub_resource type="Resource" id="Resource_ih8lj"] +script = ExtResource("7_cykhe") +name = &"simplespawner_get_spawn_frequency" +arguments = {} + +[sub_resource type="Resource" id="Resource_rfxul"] +script = ExtResource("7_cykhe") +name = &"add" +arguments = { +"a": SubResource("Resource_ih8lj"), +"b": 0.1 +} + +[sub_resource type="Resource" id="Resource_2rqfa"] +script = ExtResource("6_dv2kl") +name = &"simplespawner_set_spawn_frequency" +children = Array[ExtResource("6_dv2kl")]([]) +arguments = { +"new_frequency": SubResource("Resource_rfxul") +} + +[sub_resource type="Resource" id="Resource_movu5"] +script = ExtResource("6_dv2kl") +name = &"else_if" +children = Array[ExtResource("6_dv2kl")]([SubResource("Resource_2rqfa")]) +arguments = { +"condition": SubResource("Resource_67w0v") +} + +[sub_resource type="Resource" id="Resource_aoehr"] +script = ExtResource("7_cykhe") +name = &"is_input_actioned" +arguments = { +"action": "just_pressed", +"action_name": &"ui_up" +} + +[sub_resource type="Resource" id="Resource_tuny8"] +script = ExtResource("7_cykhe") +name = &"simplespawner_is_spawning" +arguments = {} + +[sub_resource type="Resource" id="Resource_v1c1o"] +script = ExtResource("6_dv2kl") +name = &"simplespawner_stop_spawning" +children = Array[ExtResource("6_dv2kl")]([]) +arguments = {} + +[sub_resource type="Resource" id="Resource_6dkv5"] +script = ExtResource("6_dv2kl") +name = &"if" +children = Array[ExtResource("6_dv2kl")]([SubResource("Resource_v1c1o")]) +arguments = { +"condition": SubResource("Resource_tuny8") +} + +[sub_resource type="Resource" id="Resource_xtxo1"] +script = ExtResource("6_dv2kl") +name = &"simplespawner_start_spawning" +children = Array[ExtResource("6_dv2kl")]([]) +arguments = {} + +[sub_resource type="Resource" id="Resource_0f8d8"] +script = ExtResource("6_dv2kl") +name = &"else" +children = Array[ExtResource("6_dv2kl")]([SubResource("Resource_xtxo1")]) +arguments = {} + +[sub_resource type="Resource" id="Resource_ecrx1"] +script = ExtResource("6_dv2kl") +name = &"else_if" +children = Array[ExtResource("6_dv2kl")]([SubResource("Resource_6dkv5"), SubResource("Resource_0f8d8")]) +arguments = { +"condition": SubResource("Resource_aoehr") +} + +[sub_resource type="Resource" id="Resource_5s4db"] +script = ExtResource("7_cykhe") +name = &"is_input_actioned" +arguments = { +"action": "just_pressed", +"action_name": &"ui_down" +} + +[sub_resource type="Resource" id="Resource_umo5i"] +script = ExtResource("6_dv2kl") +name = &"simplespawner_spawn_once" +children = Array[ExtResource("6_dv2kl")]([]) +arguments = {} + +[sub_resource type="Resource" id="Resource_xar12"] +script = ExtResource("6_dv2kl") +name = &"else_if" +children = Array[ExtResource("6_dv2kl")]([SubResource("Resource_umo5i")]) +arguments = { +"condition": SubResource("Resource_5s4db") +} + +[sub_resource type="Resource" id="Resource_psdqc"] +script = ExtResource("6_dv2kl") +name = &"process" +children = Array[ExtResource("6_dv2kl")]([SubResource("Resource_ke4bk"), SubResource("Resource_movu5"), SubResource("Resource_ecrx1"), SubResource("Resource_xar12")]) +arguments = {} + +[sub_resource type="Resource" id="Resource_0lc7n"] +script = ExtResource("5_g4h7g") +root = SubResource("Resource_psdqc") +canvas_position = Vector2(50, 200) + +[sub_resource type="Resource" id="Resource_trjwk"] +script = ExtResource("8_tovvd") +script_inherits = "SimpleSpawner" +block_serialization_trees = Array[ExtResource("5_g4h7g")]([SubResource("Resource_jiosv"), SubResource("Resource_0lc7n")]) +variables = Array[ExtResource("9_m8g1j")]([]) +generated_script = "extends SimpleSpawner + + +func _ready(): + spawn_start() + +func _process(delta): + if (Input.is_action_just_pressed('ui_right')): + do_set_spawn_frequency(((spawn_frequency) - 0.1)) + elif (Input.is_action_just_pressed('ui_left')): + do_set_spawn_frequency(((spawn_frequency) + 0.1)) + elif (Input.is_action_just_pressed('ui_up')): + if (is_spawning()): + spawn_stop() + else: + spawn_start() + elif (Input.is_action_just_pressed('ui_down')): + spawn_once() + +" +version = 0 + +[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_c5n1h"] + +[node name="Node2D" type="Node2D"] + +[node name="SimpleSpawner" type="Node2D" parent="."] +position = Vector2(103, 128) +script = ExtResource("1_g2l2s") +scenes = Array[PackedScene]([ExtResource("2_d0h86"), ExtResource("3_tt12o")]) +spawn_frequency = 0.6 +spawn_limit = 5 + +[node name="BlockCode" type="Node" parent="SimpleSpawner"] +script = ExtResource("4_e0fbh") +block_script = SubResource("Resource_trjwk") + +[node name="StaticBody2D" type="StaticBody2D" parent="."] + +[node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"] +position = Vector2(64, 648) +shape = SubResource("WorldBoundaryShape2D_c5n1h") + +[node name="CollisionShape2D2" type="CollisionShape2D" parent="StaticBody2D"] +position = Vector2(24, 0) +rotation = -3.14159 +shape = SubResource("WorldBoundaryShape2D_c5n1h") + +[node name="CollisionShape2D3" type="CollisionShape2D" parent="StaticBody2D"] +position = Vector2(0, 128) +rotation = -4.71238 +shape = SubResource("WorldBoundaryShape2D_c5n1h") + +[node name="CollisionShape2D4" type="CollisionShape2D" parent="StaticBody2D"] +position = Vector2(1152, 320) +rotation = -7.85397 +shape = SubResource("WorldBoundaryShape2D_c5n1h") + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = -781.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 + +[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/MarginContainer"] +layout_mode = 2 +bbcode_enabled = true +text = "[b]Up arrow:[/b] Start/stop spawning. +[b]Down arrow:[/b] Spawn once. +[b]Left arrow:[/b] Reduce spawn frequency. +[b]Right arrow:[/b] Increase spawn frequency. + +From the Inspector: Try changing spawn limit and spawn limit behavior. + +Color balls remove themselves after 3 seconds. While uncolored balls are only removed by the SimpleSpawner node according to the limit setings." diff --git a/addons/block_code/examples/spawner/volatile_ball.tscn b/addons/block_code/examples/spawner/volatile_ball.tscn new file mode 100644 index 00000000..c7f87ead --- /dev/null +++ b/addons/block_code/examples/spawner/volatile_ball.tscn @@ -0,0 +1,135 @@ +[gd_scene load_steps=7 format=3 uid="uid://c8hrliwojohal"] + +[ext_resource type="Texture2D" uid="uid://bcgr5amsq3jfl" path="res://addons/block_code/examples/pong_game/assets/ball.png" id="1_8te7j"] + +[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_c3m63"] +friction = 0.0 +bounce = 1.0 + +[sub_resource type="CircleShape2D" id="CircleShape2D_sntrn"] +radius = 64.0 + +[sub_resource type="Animation" id="Animation_ns1nj"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Color(0.14351, 0.463194, 1, 1)] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath("Sprite2D:scale") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(1, 1)] +} +tracks/2/type = "value" +tracks/2/imported = false +tracks/2/enabled = true +tracks/2/path = NodePath("Sprite2D:modulate") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Color(1, 1, 1, 1)] +} + +[sub_resource type="Animation" id="Animation_a6uuf"] +resource_name = "new_animation" +length = 3.0 +tracks/0/type = "method" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(3), +"transitions": PackedFloat32Array(1), +"values": [{ +"args": [], +"method": &"queue_free" +}] +} +tracks/1/type = "value" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:modulate") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0, 1.13333, 1.7, 2.63333, 3), +"transitions": PackedFloat32Array(1, 1, 1, 1, 1), +"update": 0, +"values": [Color(0.616405, 0.834547, 1, 1), Color(0.634942, 0.868182, 0.624881, 1), Color(1, 0.643546, 0.423438, 1), Color(0.919535, 0, 0.297312, 1), Color(0.562397, 0.00248586, 0.821534, 1)] +} +tracks/2/type = "value" +tracks/2/imported = false +tracks/2/enabled = true +tracks/2/path = NodePath("Sprite2D:scale") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/keys = { +"times": PackedFloat32Array(2.6, 3), +"transitions": PackedFloat32Array(2, 2), +"update": 0, +"values": [Vector2(1, 1), Vector2(1.86698, 1.86698)] +} +tracks/3/type = "value" +tracks/3/imported = false +tracks/3/enabled = true +tracks/3/path = NodePath("Sprite2D:modulate") +tracks/3/interp = 1 +tracks/3/loop_wrap = true +tracks/3/keys = { +"times": PackedFloat32Array(2.6, 3), +"transitions": PackedFloat32Array(1, 1), +"update": 0, +"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0.207843)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_4ng7g"] +_data = { +"RESET": SubResource("Animation_ns1nj"), +"new_animation": SubResource("Animation_a6uuf") +} + +[node name="Ball" type="RigidBody2D" groups=["balls"]] +modulate = Color(0.14351, 0.463194, 1, 1) +collision_layer = 2 +collision_mask = 15 +physics_material_override = SubResource("PhysicsMaterial_c3m63") +continuous_cd = 1 +contact_monitor = true +max_contacts_reported = 1 +linear_velocity = Vector2(353.553, 353.553) +linear_damp_mode = 1 +angular_damp_mode = 1 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +unique_name_in_owner = true +shape = SubResource("CircleShape2D_sntrn") + +[node name="Sprite2D" type="Sprite2D" parent="."] +unique_name_in_owner = true +texture = ExtResource("1_8te7j") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +"": SubResource("AnimationLibrary_4ng7g") +} +autoplay = "new_animation" diff --git a/addons/block_code/simple_spawner/simple_spawner.gd b/addons/block_code/simple_spawner/simple_spawner.gd new file mode 100644 index 00000000..0412f82e --- /dev/null +++ b/addons/block_code/simple_spawner/simple_spawner.gd @@ -0,0 +1,160 @@ +@tool +class_name SimpleSpawner +extends Node2D + +const BlockDefinition = preload("res://addons/block_code/code_generation/block_definition.gd") +const BlocksCatalog = preload("res://addons/block_code/code_generation/blocks_catalog.gd") +const OptionData = preload("res://addons/block_code/code_generation/option_data.gd") +const Types = preload("res://addons/block_code/types/types.gd") + +enum LimitBehavior { REPLACE, NO_SPAWN } + +## The scenes to spawn. If more than one are provided, they will be picked randomly. +@export var scenes: Array[PackedScene] = [] + +## The period of time in seconds to spawn another component. If zero, they won't spawn +## automatically. Use the "Spawn" block. +@export_range(0.0, 10.0, 0.1, "or_greater") var spawn_frequency: float = 0.0: + set = _set_spawn_fraquency + +## How many spawned scenes are allowed. If zero, there is no limit. +@export_range(0, 50, 0.1, "or_greater") var spawn_limit: int = 50 + +## What happens when the limit is reached and a new spawn is attempted: +## - Replace: Remove the oldest spawned scene and spawn a new one. +## - No Spawn: No spawn happens until any spawned scene is removed by other means. +@export var limit_behavior: LimitBehavior + +var _timer: Timer +var _spawned_scenes: Array[Node] + + +func _ready() -> void: + set_process(false) + set_physics_process(false) + + +func get_custom_class(): + return "SimpleSpawner" + + +func _remove_oldest_spawned(): + var spawned = _spawned_scenes.pop_front() + if is_instance_valid(spawned): + remove_child(spawned) + + +func _set_spawn_fraquency(new_frequency: float): + spawn_frequency = new_frequency + if not _timer or not is_instance_valid(_timer): + return + _timer.wait_time = spawn_frequency + + +func spawn_start(): + if spawn_frequency == 0.0: + return + if not _timer or not is_instance_valid(_timer): + _timer = Timer.new() + add_child(_timer) + _timer.wait_time = spawn_frequency + _timer.timeout.connect(spawn_once) + _timer.start() + spawn_once() + + +func spawn_stop(): + if not _timer or not is_instance_valid(_timer): + return + _timer.stop() + + +func is_spawning(): + if not _timer or not is_instance_valid(_timer): + return false + return not _timer.is_stopped() + + +func spawn_once(): + if scenes.size() == 0: + return + + _spawned_scenes = _spawned_scenes.filter(is_instance_valid) + + if spawn_limit != 0 and _spawned_scenes.size() >= spawn_limit: + if limit_behavior == LimitBehavior.NO_SPAWN: + return + else: + _remove_oldest_spawned() + + var scene: PackedScene = scenes.pick_random() + var spawned = scene.instantiate() + _spawned_scenes.push_back(spawned) + add_child(spawned) + + +func do_set_spawn_frequency(new_frequency: float): + _set_spawn_fraquency(new_frequency) + + +static func setup_custom_blocks(): + var _class_name = "SimpleSpawner" + var block_list: Array[BlockDefinition] = [] + + var block_definition: BlockDefinition = BlockDefinition.new() + block_definition.name = &"simplespawner_spawn_once" + block_definition.target_node_class = _class_name + block_definition.category = "Lifecycle | Spawn" + block_definition.type = Types.BlockType.STATEMENT + block_definition.display_template = "Spawn once" + block_definition.code_template = "spawn_once()" + block_list.append(block_definition) + + block_definition = BlockDefinition.new() + block_definition.name = &"simplespawner_start_spawning" + block_definition.target_node_class = _class_name + block_definition.category = "Lifecycle | Spawn" + block_definition.type = Types.BlockType.STATEMENT + block_definition.display_template = "Start spawning" + block_definition.code_template = "spawn_start()" + block_list.append(block_definition) + + block_definition = BlockDefinition.new() + block_definition.name = &"simplespawner_stop_spawning" + block_definition.target_node_class = _class_name + block_definition.category = "Lifecycle | Spawn" + block_definition.type = Types.BlockType.STATEMENT + block_definition.display_template = "Stop spawning" + block_definition.code_template = "spawn_stop()" + block_list.append(block_definition) + + block_definition = BlockDefinition.new() + block_definition.name = &"simplespawner_is_spawning" + block_definition.target_node_class = _class_name + block_definition.category = "Lifecycle | Spawn" + block_definition.type = Types.BlockType.VALUE + block_definition.variant_type = TYPE_BOOL + block_definition.display_template = "Is spawning" + block_definition.code_template = "is_spawning()" + block_list.append(block_definition) + + block_definition = BlockDefinition.new() + block_definition.name = &"simplespawner_set_spawn_frequency" + block_definition.target_node_class = _class_name + block_definition.category = "Lifecycle | Spawn" + block_definition.type = Types.BlockType.STATEMENT + block_definition.display_template = "Set spawn frequency to {new_frequency: FLOAT}" + block_definition.code_template = "do_set_spawn_frequency({new_frequency})" + block_list.append(block_definition) + + block_definition = BlockDefinition.new() + block_definition.name = &"simplespawner_get_spawn_frequency" + block_definition.target_node_class = _class_name + block_definition.category = "Lifecycle | Spawn" + block_definition.type = Types.BlockType.VALUE + block_definition.variant_type = TYPE_FLOAT + block_definition.display_template = "Spawn frequency" + block_definition.code_template = "spawn_frequency" + block_list.append(block_definition) + + BlocksCatalog.add_custom_blocks(_class_name, block_list, [], {}) diff --git a/addons/block_code/ui/constants.gd b/addons/block_code/ui/constants.gd index 6e89de1b..04e86325 100644 --- a/addons/block_code/ui/constants.gd +++ b/addons/block_code/ui/constants.gd @@ -21,6 +21,11 @@ const BUILTIN_CATEGORIES_PROPS: Dictionary = { "color": Color("ec3b59"), "order": 10, }, + "Lifecycle | Spawn": + { + "color": Color("ec3b59"), + "order": 15, + }, "Transform | Position": { "color": Color("4b6584"),