diff --git a/spacewar/menu/main_menu.gd b/spacewar/menu/main_menu.gd index 013150c..61a692b 100644 --- a/spacewar/menu/main_menu.gd +++ b/spacewar/menu/main_menu.gd @@ -55,18 +55,12 @@ func _build_ui() -> void: func _add_bg() -> void: - var bg := TextureRect.new() - bg.texture = load("res://assets/images/background/skybox/2.png") - bg.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL - bg.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED + var bg := ColorRect.new() + bg.material = ShaderMaterial.new() + bg.material.shader = load("res://world/starfield.gdshader") bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(bg) - var ov := ColorRect.new() - ov.color = Color(0.0, 0.0, 0.0, 0.55) - ov.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - add_child(ov) - func _add_logo() -> void: var title := Label.new() diff --git a/spacewar/world/maps/map_01.tscn b/spacewar/world/maps/map_01.tscn index 7edb4d8..a6d520e 100644 --- a/spacewar/world/maps/map_01.tscn +++ b/spacewar/world/maps/map_01.tscn @@ -1,27 +1,26 @@ [gd_scene format=4 uid="uid://cecn8fghm8h3j"] [ext_resource type="TileSet" uid="uid://cufng7112p6a7" path="res://world/world_tileset.tres" id="1_tileset"] -[ext_resource type="Texture2D" uid="uid://d0i6jwqh131b1" path="res://assets/images/background/skybox/1.png" id="2_skybox"] +[ext_resource type="Shader" path="res://world/starfield.gdshader" id="2_starfield"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_starfield"] +shader = ExtResource("2_starfield") [node name="Map01" type="Node2D" unique_id=1843060857] -[node name="Background" type="TextureRect" parent="." unique_id=1935208901] -texture_repeat = 2 +[node name="Background" type="ColorRect" parent="." unique_id=1935208901] +material = SubResource("ShaderMaterial_starfield") offset_left = -7008.0 offset_top = -3000.0 offset_right = 7008.0 offset_bottom = 3000.0 -texture = ExtResource("2_skybox") -expand_mode = 3 [node name="Walls" type="TileMapLayer" parent="." unique_id=1965607014] scale = Vector2(0.15, 0.15) -tile_map_data = PackedByteArray("AAAFACYAAAABAAAAAAAFABkAAAABAAAAAAAFAA0AAAABAAAAAAAFAAAAAAABAAAAAAAFAPT/AAABAAAAAAARABEAAAABAAEAAAA=") tile_set = ExtResource("1_tileset") [node name="Asteroids" type="TileMapLayer" parent="." unique_id=1745417803] scale = Vector2(0.15, 0.15) -tile_map_data = PackedByteArray("AAAMAAEAAQAAAAMAAAALAPz/AQAAAAMAAAD6////AQAAAAMAAAD6/wgAAQAAAAMAAAAOAAgAAQAAAAMAAAA=") tile_set = ExtResource("1_tileset") [node name="SpawnPoints" type="Node2D" parent="." unique_id=1668900368] diff --git a/spacewar/world/starfield.gdshader b/spacewar/world/starfield.gdshader new file mode 100644 index 0000000..e8166fc --- /dev/null +++ b/spacewar/world/starfield.gdshader @@ -0,0 +1,108 @@ +shader_type canvas_item; + +// Procedural starfield: draws directly over the whole background rect in one +// pass, so there's no repeating tile and therefore no seams (unlike the old +// approach of tiling a single non-seamless skybox render). Star size/glow use +// smoothstep, not texture sampling, so they stay pixel-crisp at any zoom +// instead of getting minified/blurred by texture filtering. + +uniform vec4 sky_color: source_color = vec4(0.008, 0.01, 0.025, 1.0); +uniform vec4 nebula_color: source_color = vec4(0.05, 0.08, 0.16, 1.0); +uniform float nebula_scale = 0.0025; +uniform float nebula_strength = 0.5; + +uniform float cell_size_far = 56.0; +uniform float density_far = 0.10; +uniform float size_far = 0.7; +uniform float brightness_far = 0.35; + +uniform float cell_size_mid = 100.0; +uniform float density_mid = 0.14; +uniform float size_mid = 1.0; +uniform float brightness_mid = 0.5; + +uniform float cell_size_near = 200.0; +uniform float density_near = 0.18; +uniform float size_near = 1.4; +uniform float brightness_near = 0.7; + +varying vec2 world_pos; + +void vertex() { + world_pos = VERTEX; +} + +float hash11(float p) { + p = fract(p * 0.1031); + p *= p + 33.33; + p *= p + p; + return fract(p); +} + +float hash21(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * 0.1031); + p3 += dot(p3, p3.yzx + 33.33); + return fract((p3.x + p3.y) * p3.z); +} + +vec2 hash22(vec2 p) { + return vec2(hash21(p), hash21(p + 19.19)); +} + +float value_noise(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + float a = hash21(i); + float b = hash21(i + vec2(1.0, 0.0)); + float c = hash21(i + vec2(0.0, 1.0)); + float d = hash21(i + vec2(1.0, 1.0)); + vec2 u = f * f * (3.0 - 2.0 * f); + return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); +} + +float fbm(vec2 p) { + float value = 0.0; + float amp = 0.5; + for (int i = 0; i < 4; i++) { + value += amp * value_noise(p); + p *= 2.0; + amp *= 0.5; + } + return value; +} + +// One depth layer of stars on an independent grid, so far/mid/near layers +// never line up with each other. +vec3 star_layer(vec2 pos, float cell_size, float density, float size, float brightness) { + vec2 cell = floor(pos / cell_size); + float roll = hash21(cell); + float has_star = step(1.0 - density, roll); + + vec2 jitter = hash22(cell + 0.5); + vec2 star_pos = (cell + jitter) * cell_size; + float d = length(pos - star_pos); + + float core = smoothstep(size, 0.0, d); + float halo = smoothstep(size * 3.5, 0.0, d) * 0.15; + float glow = (core + halo) * has_star * brightness; + + float tint_roll = hash21(cell + 7.7); + vec3 tint = mix(vec3(0.75, 0.82, 1.0), vec3(1.0, 0.92, 0.75), step(0.5, tint_roll)); + tint = mix(vec3(1.0), tint, 0.5); + + return tint * glow; +} + +void fragment() { + vec2 pos = world_pos; + + float nebula = fbm(pos * nebula_scale); + vec3 color = mix(sky_color.rgb, nebula_color.rgb, clamp(nebula * nebula_strength, 0.0, 1.0)); + + vec3 stars = vec3(0.0); + stars += star_layer(pos, cell_size_far, density_far, size_far, brightness_far); + stars += star_layer(pos + vec2(500.0, 0.0), cell_size_mid, density_mid, size_mid, brightness_mid); + stars += star_layer(pos + vec2(0.0, 900.0), cell_size_near, density_near, size_near, brightness_near); + + COLOR = vec4(color + stars, 1.0); +} diff --git a/spacewar/world/world.gd b/spacewar/world/world.gd index 7227784..1c0b2e5 100644 --- a/spacewar/world/world.gd +++ b/spacewar/world/world.gd @@ -20,7 +20,7 @@ var _offered_race_ids: Array = [] func _ready() -> void: var map := MAP_SCENE.instantiate() _map_container.add_child(map) - var background := map.get_node_or_null("Background") as TextureRect + var background := map.get_node_or_null("Background") as Control if background: GameConfig.world_bounds = Rect2(background.position, background.size) _build_tile_collisions(map)