extends Node2D const WORLD_SIZE := Vector2(5200.0, 3600.0) const BACKGROUND_PATHS := [ "res://assets/background/space/deep_space.png", "res://assets/background/space/teal_veil.png", "res://assets/background/space/crimson_rift.png", "res://assets/background/space/amber_expanse.png", "res://assets/background/space/violet_spiral.png", "res://assets/background/space/frozen_void.png", ] const DUST_TEXTURE := preload("res://assets/background/space/nebula_dust.png") const STAR_TILE := Vector2(2048.0, 1536.0) var _far_sprite: Sprite2D func _ready() -> void: # Let the engine synchronize parallax with the interpolated camera transform. # No camera sampling or per-frame rebuilding of draw commands is needed. texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS var distant := _make_layer("DistantGalaxies", 0.04) _far_sprite = Sprite2D.new() # Only the artwork changes between systems; parallax layers stay identical. var background_index := posmod(GameState.system_level - 1, BACKGROUND_PATHS.size()) _far_sprite.texture = load(BACKGROUND_PATHS[background_index]) as Texture2D _far_sprite.modulate = Color(0.8, 0.85, 0.95) distant.add_child(_far_sprite) _resize_background() get_viewport().size_changed.connect(_resize_background) var dust := _make_layer("NebulaDust", 0.14) dust.repeat_size = Vector2(DUST_TEXTURE.get_size()) dust.repeat_times = 3 var dust_sprite := Sprite2D.new() dust_sprite.texture = DUST_TEXTURE dust_sprite.centered = false dust_sprite.modulate = Color(0.65, 0.75, 0.9, 0.22) dust.add_child(dust_sprite) _make_stars("DistantStars", 0.18, 210, 0) _make_stars("NearStars", 0.42, 90, 1) func _make_layer(layer_name: String, speed: float) -> Parallax2D: var layer := Parallax2D.new() layer.name = layer_name layer.scroll_scale = Vector2.ONE * speed add_child(layer) return layer func _resize_background() -> void: var view_size := get_viewport_rect().size var camera := get_viewport().get_camera_2d() if is_instance_valid(camera): view_size /= camera.zoom # Native resolution at 720p; retain aspect ratio and cover camera travel. _far_sprite.position = view_size * 0.5 var required := view_size + WORLD_SIZE * 0.04 + Vector2(32.0, 32.0) var ratio := required / _far_sprite.texture.get_size() _far_sprite.scale = Vector2.ONE * maxf(1.0, maxf(ratio.x, ratio.y)) func _make_stars(layer_name: String, speed: float, count: int, seed_offset: int) -> void: var rng := RandomNumberGenerator.new() rng.seed = GameState.system_seed + seed_offset var svg := '' for index in count: var point := Vector2(rng.randf_range(4.0, STAR_TILE.x - 4.0), rng.randf_range(4.0, STAR_TILE.y - 4.0)) svg += '' % [point.x, point.y, rng.randf_range(0.5, 1.4), rng.randf_range(0.18, 0.65)] svg += '' var image := Image.new() if image.load_svg_from_string(svg) != OK: return image.generate_mipmaps() var layer := _make_layer(layer_name, speed) layer.repeat_size = STAR_TILE layer.repeat_times = 3 var sprite := Sprite2D.new() sprite.texture = ImageTexture.create_from_image(image) sprite.centered = false layer.add_child(sprite) func _draw() -> void: # The map boundary remains in world space. draw_rect(Rect2(-WORLD_SIZE * 0.5, WORLD_SIZE), Color(0.12, 0.52, 0.68, 0.48), false, 3.0)