From cb1ed419a37a874a3df9e08f74d43690560d42fc Mon Sep 17 00:00:00 2001 From: 3002102 <3002102@stud.hs-mannheim.de> Date: Wed, 15 Apr 2026 17:33:47 +0200 Subject: [PATCH] add a new wave spawning logic defined by a spawn_stages.json in the data folder --- data/spawn_stages.json | 26 +++++++++ scenes/game.tscn | 22 ------- scripts/SpawnStage.gd | 6 ++ scripts/SpawnStage.gd.uid | 1 + scripts/StageEntry.gd | 7 +++ scripts/StageEntry.gd.uid | 1 + scripts/spawn_control.gd | 120 +++++++++++++++++++++++++------------- 7 files changed, 121 insertions(+), 62 deletions(-) create mode 100644 data/spawn_stages.json create mode 100644 scripts/SpawnStage.gd create mode 100644 scripts/SpawnStage.gd.uid create mode 100644 scripts/StageEntry.gd create mode 100644 scripts/StageEntry.gd.uid diff --git a/data/spawn_stages.json b/data/spawn_stages.json new file mode 100644 index 0000000..fc4350e --- /dev/null +++ b/data/spawn_stages.json @@ -0,0 +1,26 @@ +[ + { + "time_start": 0, + "time_end": 60, + "entries": [ + { "enemy": "res://scenes/slime.tscn", "count_at_start": 0, "count_at_end": 15, "min_interval": 0.5 } + ] + }, + { + "time_start": 60, + "time_end": 180, + "entries": [ + { "enemy": "res://scenes/slime.tscn", "count_at_start": 15, "count_at_end": 40, "min_interval": 0.3 }, + { "enemy": "res://scenes/blue_slime.tscn", "count_at_start": 0, "count_at_end": 10, "min_interval": 0.8 } + ] + }, + { + "time_start": 180, + "time_end": -1, + "entries": [ + { "enemy": "res://scenes/slime.tscn", "count_at_start": 40, "count_at_end": 100, "min_interval": 0.2 }, + { "enemy": "res://scenes/blue_slime.tscn", "count_at_start": 10, "count_at_end": 60, "min_interval": 0.5 }, + { "enemy": "res://scenes/fire_slime.tscn", "count_at_start": 0, "count_at_end": 40, "min_interval": 0.6 } + ] + } +] diff --git a/scenes/game.tscn b/scenes/game.tscn index bd0326f..604f5c1 100644 --- a/scenes/game.tscn +++ b/scenes/game.tscn @@ -4,13 +4,10 @@ [ext_resource type="Script" uid="uid://cphrdy0xexx30" path="res://scenes/game.gd" id="1_vtaks"] [ext_resource type="PackedScene" uid="uid://7vohdw0xop0g" path="res://scenes/worldborder.tscn" id="2_dinhu"] [ext_resource type="Script" uid="uid://coplu13jpw4xq" path="res://scripts/camera_2d.gd" id="3_kvpfn"] -[ext_resource type="Script" uid="uid://b4jrogrq54c8f" path="res://scripts/SpawnEntry.gd" id="6_ir15t"] [ext_resource type="Script" uid="uid://dovkm6w8af08x" path="res://scripts/spawn_control.gd" id="6_p57ef"] -[ext_resource type="PackedScene" uid="uid://ccotbw7gepsge" path="res://scenes/slime.tscn" id="7_ca42v"] [ext_resource type="Texture2D" uid="uid://c4i3fnr6gpjp" path="res://assets/tileset/Tiled_files/details.png" id="7_gee14"] [ext_resource type="PackedScene" uid="uid://b4v0ntaukg2je" path="res://scenes/witch.tscn" id="7_u5sy4"] [ext_resource type="Texture2D" uid="uid://0xu8ohipv2mj" path="res://assets/tileset/Tiled_files/Objects.png" id="8_0tnpc"] -[ext_resource type="PackedScene" uid="uid://cj83ht5o2l8c1" path="res://scenes/blue_slime.tscn" id="8_rysoc"] [ext_resource type="PackedScene" uid="uid://cgu7w2jd42f3a" path="res://scenes/tile_map_layer(background).tscn" id="8_vtaks"] [ext_resource type="PackedScene" uid="uid://bgpsc6dvsn7ak" path="res://scenes/tile_map_layer(objects).tscn" id="9_kvpfn"] [ext_resource type="PackedScene" uid="uid://co8t1fr3b3kub" path="res://scenes/tile_map_layer(overlay).tscn" id="10_dinhu"] @@ -21,19 +18,6 @@ [ext_resource type="FontFile" uid="uid://8v71dcws4q6o" path="res://assets/fonts/slkscre.ttf" id="19_1kice"] [ext_resource type="Script" uid="uid://586y330mhx8" path="res://scripts/options_menu_ingame.gd" id="20_1kice"] -[sub_resource type="Resource" id="Resource_ssvqc"] -script = ExtResource("6_ir15t") -weight = 1.0 -enemy = ExtResource("7_ca42v") -metadata/_custom_type_script = "uid://b4jrogrq54c8f" - -[sub_resource type="Resource" id="Resource_264po"] -script = ExtResource("6_ir15t") -min_time = 10.0 -weight = 0.5 -enemy = ExtResource("8_rysoc") -metadata/_custom_type_script = "uid://b4jrogrq54c8f" - [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_vtaks"] texture = ExtResource("7_gee14") 1:1/0 = 0 @@ -2383,11 +2367,6 @@ anchors_preset = 0 offset_right = 40.0 offset_bottom = 40.0 script = ExtResource("6_p57ef") -spawn_entries = Array[ExtResource("6_ir15t")]([SubResource("Resource_ssvqc"), SubResource("Resource_264po")]) - -[node name="SpawnTimer" type="Timer" parent="." unique_id=1852920556] -wait_time = 0.2 -autostart = true [node name="Witch" parent="." unique_id=1188927311 instance=ExtResource("7_u5sy4")] position = Vector2(304, 164) @@ -2575,7 +2554,6 @@ theme_override_fonts/font = ExtResource("19_1kice") theme_override_font_sizes/font_size = 32 text = "Back" -[connection signal="timeout" from="SpawnTimer" to="SpawnControl" method="_on_spawn_timer_timeout"] [connection signal="pressed" from="PauseMenu/VBoxContainer/ContinueButton" to="PauseMenu" method="_on_continue_button_pressed"] [connection signal="pressed" from="PauseMenu/VBoxContainer/OptionsButton" to="PauseMenu" method="_on_options_button_pressed"] [connection signal="pressed" from="PauseMenu/VBoxContainer/QuitButton" to="PauseMenu" method="_on_quit_button_pressed"] diff --git a/scripts/SpawnStage.gd b/scripts/SpawnStage.gd new file mode 100644 index 0000000..186b8f7 --- /dev/null +++ b/scripts/SpawnStage.gd @@ -0,0 +1,6 @@ +extends Resource +class_name SpawnStage + +@export var time_start: float = 0.0 +@export var time_end: float = -1.0 # -1 = forever +@export var entries: Array[StageEntry] diff --git a/scripts/SpawnStage.gd.uid b/scripts/SpawnStage.gd.uid new file mode 100644 index 0000000..e89885a --- /dev/null +++ b/scripts/SpawnStage.gd.uid @@ -0,0 +1 @@ +uid://ca7n7kd1ki2is diff --git a/scripts/StageEntry.gd b/scripts/StageEntry.gd new file mode 100644 index 0000000..4ecffca --- /dev/null +++ b/scripts/StageEntry.gd @@ -0,0 +1,7 @@ +extends Resource +class_name StageEntry + +@export var enemy: PackedScene +@export var count_at_start: int = 0 +@export var count_at_end: int = 20 +@export var min_interval: float = 0.3 diff --git a/scripts/StageEntry.gd.uid b/scripts/StageEntry.gd.uid new file mode 100644 index 0000000..369a82e --- /dev/null +++ b/scripts/StageEntry.gd.uid @@ -0,0 +1 @@ +uid://dc737qsmg74i diff --git a/scripts/spawn_control.gd b/scripts/spawn_control.gd index cd4f46d..a9872c9 100644 --- a/scripts/spawn_control.gd +++ b/scripts/spawn_control.gd @@ -1,72 +1,112 @@ extends Control -var up_left -var down_right -var up_right -var down_left -var viewport_rect -var elapsed_time = 0.0 -@export var spawn_entries: Array[SpawnEntry] +var up_left: Vector2 +var down_right: Vector2 +var up_right: Vector2 +var down_left: Vector2 +var elapsed_time: float = 0.0 + +const STAGES_JSON = "res://data/spawn_stages.json" + +var stages: Array[SpawnStage] = [] + +# _state keys: Vector2i(stage_idx, entry_idx) +# values: { "timer": float, "alive": int } +var _state: Dictionary = {} func _ready() -> void: var camera: Camera2D = get_parent().get_node("Camera2D") var viewport_size = get_viewport_rect().size var world_size = viewport_size / camera.zoom - var world_origin = camera.global_position # anchor_mode = 0 → top-left corner + var world_origin = camera.global_position up_left = world_origin down_right = world_origin + world_size up_right = Vector2(down_right.x, up_left.y) down_left = Vector2(up_left.x, down_right.y) - + + _load_stages(STAGES_JSON) + + for si in stages.size(): + for ei in stages[si].entries.size(): + _state[Vector2i(si, ei)] = { "timer": 0.0, "alive": 0 } + +func _load_stages(path: String) -> void: + var file = FileAccess.open(path, FileAccess.READ) + if file == null: + push_error("spawn_control: cannot open " + path) + return + var data = JSON.parse_string(file.get_as_text()) + if not data is Array: + push_error("spawn_control: invalid JSON in " + path) + return + for sd in data: + var stage = SpawnStage.new() + stage.time_start = float(sd["time_start"]) + stage.time_end = float(sd["time_end"]) + for ed in sd["entries"]: + var entry = StageEntry.new() + entry.enemy = load(ed["enemy"]) + entry.count_at_start = int(ed["count_at_start"]) + entry.count_at_end = int(ed["count_at_end"]) + entry.min_interval = float(ed["min_interval"]) + stage.entries.append(entry) + stages.append(stage) func get_spawn_position() -> Vector2: var side = randi() % 4 - var spawn_x - var spawn_y + var spawn_x: float + var spawn_y: float if side == 0: - # oben spawn_x = randf_range(up_left.x, up_right.x) spawn_y = up_left.y elif side == 1: - # rechts spawn_x = up_right.x spawn_y = randf_range(up_right.y, down_right.y) elif side == 2: - #unten spawn_x = randf_range(up_left.x, up_right.x) spawn_y = down_left.y - elif side == 3: - #links + else: spawn_x = up_left.x spawn_y = randf_range(up_right.y, down_right.y) return Vector2(spawn_x, spawn_y) -# Called every frame. 'delta' is the elapsed time since the previous frame. + func _process(delta: float) -> void: elapsed_time += delta - pass -func spawn_enemy() -> void: - var sum_weight = 0.0 - var available = [] - for entry in spawn_entries: - if entry.min_time <= elapsed_time: - available.append(entry) - sum_weight += entry.weight - var roll = randf() * sum_weight - var winner = null - for entry in available: - roll -= entry.weight - if roll <= 0: - winner = entry - break - if winner == null: - return - var enemy = winner.enemy.instantiate() + for si in stages.size(): + var stage: SpawnStage = stages[si] + if elapsed_time < stage.time_start: + continue + if stage.time_end != -1.0 and elapsed_time > stage.time_end: + continue + + var t: float + if stage.time_end == -1.0: + t = 1.0 + else: + t = clamp( + (elapsed_time - stage.time_start) / (stage.time_end - stage.time_start), + 0.0, 1.0 + ) + + for ei in stage.entries.size(): + var entry: StageEntry = stage.entries[ei] + var state: Dictionary = _state[Vector2i(si, ei)] + + var target: int = roundi(lerpf(float(entry.count_at_start), float(entry.count_at_end), t)) + var deficit: int = target - state["alive"] + if deficit <= 0: + continue + + state["timer"] -= delta + if state["timer"] <= 0.0: + state["timer"] = max(entry.min_interval, 1.0 / float(deficit)) + _spawn_one(entry, state) + +func _spawn_one(entry: StageEntry, state: Dictionary) -> void: + var enemy = entry.enemy.instantiate() enemy.global_position = get_spawn_position() + state["alive"] += 1 + enemy.tree_exited.connect(func(): state["alive"] -= 1) add_child(enemy) - - -func _on_spawn_timer_timeout() -> void: - spawn_enemy() - pass # Replace with function body. -- 2.43.0