diff --git a/surface/GasVentField.gd b/surface/GasVentField.gd index e2476ce..4f5b381 100644 --- a/surface/GasVentField.gd +++ b/surface/GasVentField.gd @@ -9,7 +9,23 @@ const RESOURCE_PICKUP_VFX = preload("res://vfx/ResourcePickupVFX.gd") @export var fleet_path: NodePath = NodePath("../Fleet") @onready var fleet: Node2D = get_node(fleet_path) +const SOURCE_COLOUR: Color = Color(0.85, 0.52, 1.0) +const CORE_COLOUR: Color = Color(0.15, 0.03, 0.22) +# Offsets from a vent's centre, and each bubble's radius. +const SOURCE_BUBBLES: Array = [ + [Vector2(-20.0, 8.0), 20.0], + [Vector2(18.0, 14.0), 27.0], + [Vector2(17.0, -22.0), 15.0], +] + +const BUBBLES_PER_VENT: int = 4 +const BUBBLE_MIN_RADIUS: float = 5.0 +const BUBBLE_MAX_RADIUS: float = 14.0 +const EMISSION_COLOUR: Color = Color(0.92, 0.68, 1.0) + var vents: Array[Vector2] = [] +# One row of rising-bubble pieces per vent. +var bubbles: Array = [] var progress: float = 0.0 var emission_phase: float = 0.0 func _ready() -> void: @@ -24,10 +40,73 @@ func _ready() -> void: var rng := RandomNumberGenerator.new() rng.seed = hash(GameState.planet_id if GameState.planet_id != "" else GameState.planet_name) for i in range(6): vents.append(Vector2(rng.randf_range(-800,800), rng.randf_range(-650,650))) - queue_redraw() + _build_sources() + _build_bubbles() + _place_bubbles() + + +# The sources never move, never change colour and never fade - they were +# simply being re-tessellated every frame alongside the bubbles rising out of +# them, because one _draw() covered both. Eighteen glowing polygons a frame, +# each of which NeonDraw expands into four antialiased polylines. Rasterised +# once into a piece each now, per the rule issues #6 and #10 set. +func _build_sources() -> void: + for vent in vents: + var piece: NeonPiece = NeonPiece.create(self, vent, SOURCE_COLOUR) + piece.core_color = CORE_COLOUR + piece.z_index = -1 + for bubble in SOURCE_BUBBLES: + var ring := PackedVector2Array() + for i in range(12): + var angle: float = TAU * float(i) / 12.0 + ring.append(Vector2(bubble[0]) + Vector2(cos(angle), sin(angle)) * float(bubble[1])) + piece.add_polygon(ring, 0.9, 0.6) + + +# One piece per rising bubble, built as a UNIT circle and thereafter only +# moved, scaled and faded. The bubble genuinely changes size and opacity as +# it climbs, which is why this used to redraw - but size is scale and opacity +# is modulate, and neither needs the geometry rebuilding. +func _build_bubbles() -> void: + for vent in vents: + var row: Array = [] + for i in range(BUBBLES_PER_VENT): + var piece: NeonPiece = NeonPiece.create(self, vent, EMISSION_COLOUR) + piece.core_color = CORE_COLOUR + piece.z_index = -1 + # Built at the LARGEST size a bubble reaches and scaled DOWN, + # never up. Scaling a piece scales its stroke with it, and a unit + # circle blown up to radius 14 took NeonDraw's widths with it - + # a hundred pixels of glow around a fourteen pixel bubble, which + # rendered as a bright blob rather than a bubble. Downward, the + # stroke only ever thins, which is what a smaller bubble wants. + var ring := PackedVector2Array() + for point in range(10): + var angle: float = TAU * float(point) / 10.0 + ring.append(Vector2(cos(angle), sin(angle)) * BUBBLE_MAX_RADIUS) + piece.add_polygon(ring, 0.75, 0.45) + row.append(piece) + bubbles.append(row) + + +# Where one bubble sits, how big it is and how faded, for a point in its +# climb. Unchanged arithmetic - only the thing it is applied to changed. +func _place_bubbles() -> void: + for v in range(vents.size()): + for i in range(BUBBLES_PER_VENT): + var travel: float = fmod(emission_phase + float(i) * 0.25, 1.0) + var piece: NeonPiece = bubbles[v][i] + piece.position = vents[v] + Vector2( + sin((travel + float(i)) * TAU) * 11.0, + -48.0 - travel * 92.0) + var radius: float = lerpf(BUBBLE_MIN_RADIUS, BUBBLE_MAX_RADIUS, travel) + var k: float = radius / BUBBLE_MAX_RADIUS + piece.scale = Vector2(k, k) + piece.modulate.a = 1.0 - travel * 0.72 func _process(delta: float) -> void: emission_phase = fmod(emission_phase + delta * 0.55, 1.0) - queue_redraw() + # Node properties only. Nothing is rasterised after the first frame. + _place_bubbles() for vent in vents: if fleet.get_centroid().distance_to(vent) < 90.0: progress += delta @@ -42,43 +121,3 @@ func _process(delta: float) -> void: to_global(vent), GameState.RESOURCE_GAS, taken) return progress = 0.0 -func _draw() -> void: - for vent in vents: - _draw_gas_source(vent) - _draw_emission(vent) - - -func _draw_gas_source(centre: Vector2) -> void: - var colour := Color(0.85, 0.52, 1.0) - var core := Color(0.15, 0.03, 0.22) - var bubbles: Array = [ - [centre + Vector2(-20.0, 8.0), 20.0], - [centre + Vector2(18.0, 14.0), 27.0], - [centre + Vector2(17.0, -22.0), 15.0], - ] - for bubble in bubbles: - var ring := PackedVector2Array() - for i in range(12): - var angle: float = TAU * float(i) / 12.0 - ring.append(bubble[0] + Vector2(cos(angle), sin(angle)) * float(bubble[1])) - NeonDraw.draw_glowing_polygon(self, ring, colour, 0.9, core, 0.6) - - -func _draw_emission(centre: Vector2) -> void: - # Small bubbles continuously rise out of every source. This remains - # animated even when the fleet is not collecting, so the vent reads as - # an active gas generator rather than a static decoration. - var core := Color(0.15, 0.03, 0.22) - for i in range(4): - var travel: float = fmod(emission_phase + float(i) * 0.25, 1.0) - var bubble_centre := centre + Vector2( - sin((travel + float(i)) * TAU) * 11.0, - -48.0 - travel * 92.0 - ) - var radius: float = lerpf(5.0, 14.0, travel) - var colour := Color(0.92, 0.68, 1.0, 1.0 - travel * 0.72) - var ring := PackedVector2Array() - for point in range(10): - var angle: float = TAU * float(point) / 10.0 - ring.append(bubble_centre + Vector2(cos(angle), sin(angle)) * radius) - NeonDraw.draw_glowing_polygon(self, ring, colour, 0.75, core, 0.45) diff --git a/vfx/NeonPiece.gd b/vfx/NeonPiece.gd index 906a510..25e00ba 100644 --- a/vfx/NeonPiece.gd +++ b/vfx/NeonPiece.gd @@ -34,8 +34,9 @@ static func create(parent: Node, at: Vector2, colour: Color, alpha: float = 1.0) return piece -func add_polygon(points: PackedVector2Array, piece_pulse: float = 1.0) -> void: - polygons.append({"points": points, "pulse": piece_pulse}) +func add_polygon(points: PackedVector2Array, piece_pulse: float = 1.0, + width: float = 1.0) -> void: + polygons.append({"points": points, "pulse": piece_pulse, "width": width}) func add_path(points: PackedVector2Array, piece_pulse: float = 1.0, @@ -49,7 +50,8 @@ func add_line(from: Vector2, to: Vector2, width: float, piece_pulse: float = 1.0 func _draw() -> void: for poly in polygons: - NeonDraw.draw_glowing_polygon(self, poly.points, glow_color, poly.pulse * pulse, core_color) + NeonDraw.draw_glowing_polygon(self, poly.points, glow_color, + poly.pulse * pulse, core_color, float(poly.get("width", 1.0))) for path in paths: NeonDraw.draw_glowing_path(self, path.points, glow_color, path.pulse * pulse, path.width, core_color)