118 lines
3.3 KiB
GDScript
118 lines
3.3 KiB
GDScript
extends Control
|
|
|
|
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
|
|
|
|
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, "spawned_total": 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"])
|
|
entry.max_spawns = int(ed.get("max_spawns", -1))
|
|
stage.entries.append(entry)
|
|
stages.append(stage)
|
|
|
|
func get_spawn_position() -> Vector2:
|
|
var side = randi() % 4
|
|
var spawn_x: float
|
|
var spawn_y: float
|
|
if side == 0:
|
|
spawn_x = randf_range(up_left.x, up_right.x)
|
|
spawn_y = up_left.y
|
|
elif side == 1:
|
|
spawn_x = up_right.x
|
|
spawn_y = randf_range(up_right.y, down_right.y)
|
|
elif side == 2:
|
|
spawn_x = randf_range(up_left.x, up_right.x)
|
|
spawn_y = down_left.y
|
|
else:
|
|
spawn_x = up_left.x
|
|
spawn_y = randf_range(up_right.y, down_right.y)
|
|
return Vector2(spawn_x, spawn_y)
|
|
|
|
func _process(delta: float) -> void:
|
|
elapsed_time += delta
|
|
|
|
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)]
|
|
|
|
if entry.max_spawns != -1 and state["spawned_total"] >= entry.max_spawns:
|
|
continue
|
|
|
|
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
|
|
state["spawned_total"] += 1
|
|
enemy.tree_exited.connect(func(): state["alive"] -= 1)
|
|
add_child(enemy)
|