diff --git a/assets/background/space/README.md b/assets/background/space/README.md new file mode 100644 index 0000000..3db8507 --- /dev/null +++ b/assets/background/space/README.md @@ -0,0 +1,13 @@ +# Space parallax artwork + +Generated with the built-in imagegen tool on 2026-10-15. + +Files: deep_space.png (far background), nebula_dust.png (transparent cloud overlay). +Consumer: scripts/game/space_level_background.gd. +Camera-relative speeds: distant painting 0.04, dust 0.14, stars 0.18 / 0.42. + +Prompt — deep_space.png: +Use case: stylized-concept. Create a final production game background texture, landscape 3:2, high resolution. Realistic painterly deep outer space, vast distant indigo and muted teal nebula filaments, faint violet dust, two small very distant spiral galaxies with delicate warm cores, sparse fine distant stars. Elegant astronomical realism with painterly detail, deep near-black navy negative space across the center for a top-down spaceship game. Low-key lighting, restrained contrast, no huge bright blobs, no planets, no ships, no text, no interface, no border. Clouds soft and diffuse at all four edges, edges almost black so repeated tiles remain subtle. This is the far background parallax layer. + +Prompt — nebula_dust.png: +Use case: stylized-concept. Production transparent overlay texture for a realistic painterly outer-space game parallax foreground, landscape 3:2. Only a few wispy small nebula clouds of muted blue teal and violet interstellar dust, translucent feathery irregular filaments, widely separated with large empty transparent areas, off-center composition. A handful of tiny soft starlight points embedded in clouds. Realistic astronomical painting, subtle dim luminosity, intricate fine texture. True alpha transparent background around and between clouds, soft alpha cloud edges. All four image edges fully transparent with generous margin. No solid background, no checkerboard, no planets, no spacecraft, no text, no frame. diff --git a/assets/background/space/deep_space.png b/assets/background/space/deep_space.png new file mode 100644 index 0000000..63bce51 Binary files /dev/null and b/assets/background/space/deep_space.png differ diff --git a/assets/background/space/deep_space.png.import b/assets/background/space/deep_space.png.import new file mode 100644 index 0000000..d86db41 --- /dev/null +++ b/assets/background/space/deep_space.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tuqco3onauc4" +path="res://.godot/imported/deep_space.png-59d6499dcec6c1a3f0421dcca270f4f6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/background/space/deep_space.png" +dest_files=["res://.godot/imported/deep_space.png-59d6499dcec6c1a3f0421dcca270f4f6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/background/space/nebula_dust.png b/assets/background/space/nebula_dust.png new file mode 100644 index 0000000..aab8967 Binary files /dev/null and b/assets/background/space/nebula_dust.png differ diff --git a/assets/background/space/nebula_dust.png.import b/assets/background/space/nebula_dust.png.import new file mode 100644 index 0000000..fa9d280 --- /dev/null +++ b/assets/background/space/nebula_dust.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://chi05d1fd3vit" +path="res://.godot/imported/nebula_dust.png-0de2098893f30c0962ea0a8cb5ba1aeb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/background/space/nebula_dust.png" +dest_files=["res://.godot/imported/nebula_dust.png-0de2098893f30c0962ea0a8cb5ba1aeb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scripts/game/space_level_background.gd b/scripts/game/space_level_background.gd index f0b2507..7282092 100644 --- a/scripts/game/space_level_background.gd +++ b/scripts/game/space_level_background.gd @@ -1,18 +1,74 @@ extends Node2D const WORLD_SIZE := Vector2(5200.0, 3600.0) +const FAR_TEXTURE := preload("res://assets/background/space/deep_space.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() + _far_sprite.texture = FAR_TEXTURE + _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_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: - var half_size := WORLD_SIZE * 0.5 - draw_rect(Rect2(-half_size, WORLD_SIZE), Color("050b18"), true) - var rng := RandomNumberGenerator.new() - rng.seed = GameState.system_seed - for index in 420: - var point := Vector2( - rng.randf_range(-half_size.x + 35.0, half_size.x - 35.0), - rng.randf_range(-half_size.y + 35.0, half_size.y - 35.0) - ) - var radius := rng.randf_range(0.7, 2.0) - var color := Color(0.58, 0.76, 1.0, rng.randf_range(0.2, 0.72)) - draw_circle(point, radius, color) - draw_rect(Rect2(-half_size, WORLD_SIZE), Color(0.12, 0.52, 0.68, 0.48), false, 3.0) \ No newline at end of file + # 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) +