extends Node2D # Spawns slow-drifting asteroids inside ASTEROID_FIELD biome chunks near # the fleet, and despawns them once the fleet has moved far enough away - # keeps the scene from accumulating unbounded nodes over a long session # while still populating newly-entered asteroid regions on the fly. @export var check_interval: float = 1.0 @export var spawn_radius_chunks: int = 2 # No asteroid may be created within this distance of the fleet. At cruise # speed this is well over a second of warning, so anything that reaches the # fleet was visible on its way in. @export var safe_spawn_radius: float = 420.0 @export var despawn_radius_chunks: int = 4 @export var asteroids_per_chunk: int = 6 @export var min_speed: float = 12.0 @export var max_speed: float = 35.0 @onready var fleet: Node2D = get_node("../Fleet") var asteroid_scene: PackedScene = preload("res://Asteroid.tscn") var populated_chunks: Dictionary = {} var timer: float = 0.0 func _ready() -> void: randomize() func _process(delta: float) -> void: timer += delta if timer < check_interval: return timer = 0.0 update_chunks() func update_chunks() -> void: # Public (not just called from _process's own timer): also invoked # once synchronously right after the fleet is positioned on a fresh # Main.tscn load, so nearby asteroids exist immediately instead of # only appearing ~check_interval seconds later - most noticeable as a # jarring pop-in right after returning from a planet's surface. var center_chunk: Vector2i = WorldGen.chunk_coords(fleet.get_centroid()) for dx in range(-spawn_radius_chunks, spawn_radius_chunks + 1): for dy in range(-spawn_radius_chunks, spawn_radius_chunks + 1): var chunk: Vector2i = center_chunk + Vector2i(dx, dy) if populated_chunks.has(chunk): continue if WorldGen.biome_at_chunk(chunk) != WorldGen.Biome.ASTEROID_FIELD: continue _spawn_chunk_asteroids(chunk) var to_remove: Array = [] for chunk in populated_chunks.keys(): if _chebyshev_chunk_distance(chunk, center_chunk) > despawn_radius_chunks: for asteroid in populated_chunks[chunk]: if is_instance_valid(asteroid): asteroid.queue_free() to_remove.append(chunk) for chunk in to_remove: populated_chunks.erase(chunk) func _chebyshev_chunk_distance(a: Vector2i, b: Vector2i) -> int: return max(abs(a.x - b.x), abs(a.y - b.y)) func _safe_position(candidate: Vector2, centre: Vector2) -> Vector2: # Nothing may materialise on top of the fleet. Chunks are populated around # wherever the fleet happens to be - including the chunk it is standing in # - so without this an asteroid could appear directly on a ship and # destroy it with no chance to react. That is worst on the frame a scene # loads: arriving back from a planet could cost a ship instantly. if candidate.distance_to(centre) >= safe_spawn_radius: return candidate # Push it out to the edge of the bubble rather than dropping it, so the # field keeps its density near the fleet instead of thinning out. var away: Vector2 = candidate - centre if away.length() < 0.001: away = Vector2.RIGHT.rotated(randf() * TAU) return centre + away.normalized() * safe_spawn_radius func _spawn_chunk_asteroids(chunk: Vector2i) -> void: var origin: Vector2 = WorldGen.chunk_origin(chunk) var spawned: Array = [] var fleet_centre: Vector2 = fleet.get_centroid() for i in range(asteroids_per_chunk): var local_pos: Vector2 = Vector2(randf() * WorldGen.CHUNK_SIZE, randf() * WorldGen.CHUNK_SIZE) var asteroid: Node2D = asteroid_scene.instantiate() asteroid.position = _safe_position(origin + local_pos, fleet_centre) var angle: float = randf() * TAU var speed: float = randf_range(min_speed, max_speed) asteroid.velocity = Vector2(cos(angle), sin(angle)) * speed asteroid.rotation_speed = randf_range(-0.4, 0.4) # Deferred: update_chunks() can run synchronously from inside a # parent's own _ready() chain (forced once right after the fleet # is positioned, to avoid a pop-in on scene load) - Godot refuses # a direct add_child() while the tree is still "busy setting up # children" in that situation, so this must be deferred either way. get_parent().add_child.call_deferred(asteroid) spawned.append(asteroid) populated_chunks[chunk] = spawned