diff --git a/controllers/BuildController.gd b/controllers/BuildController.gd index f4ef654..915fe12 100644 --- a/controllers/BuildController.gd +++ b/controllers/BuildController.gd @@ -160,6 +160,14 @@ func _process(delta: float) -> void: _refresh_lab() if transfer_menu.visible: transfer_menu.refresh() + # Both of these move on their own while they are open - a transporter + # wears down, an office starves, the fleet changes under the bill - so + # they follow the same rule as the menus above: refreshed while visible, + # untouched when not. + if station_menu.visible: + station_menu.refresh() + if upkeep_menu.visible: + upkeep_menu.refresh() if world_map.visible or _menu_is_open(): _clear_detected_context() return @@ -817,8 +825,6 @@ func _layout_hangar() -> void: func _refresh_garrison() -> void: garrison_menu.refresh() - station_menu.refresh() - upkeep_menu.refresh() func _refresh_planning() -> void: diff --git a/planets/BlackHoleVisual.gd b/planets/BlackHoleVisual.gd index 486adee..ab0cb0e 100644 --- a/planets/BlackHoleVisual.gd +++ b/planets/BlackHoleVisual.gd @@ -5,6 +5,13 @@ extends Node2D # core stays genuinely empty (nothing drawn inside CORE_RADIUS), and an # accretion disc of rotating arc segments - not closed rings - stands in # for a surface, brighter and thicker toward the centre. +# +# Each ring is a NeonPiece that is SPUN, not redrawn. Issues #6 and #10 made +# that the rule for the buildings and it applies here more than anywhere: the +# disc was ten glowing paths of twenty-five points re-tessellated every frame +# - forty antialiased polylines - to turn five rings at five different rates. +# Rotation is a node property, so now nothing is rasterised after the first +# frame and the disc turns for free. var disc_phase: float = 0.0 @@ -14,36 +21,54 @@ const RING_STEP: float = 16.0 const DISC_COLOR: Color = Color(0.55, 0.15, 0.85) const DISC_HOT_COLOR: Color = Color(0.9, 0.6, 1.0) +# One per ring, with the rate and direction it turns at. +var _rings: Array = [] + + +func _ready() -> void: + _build() + func configure(_shape_seed: int) -> void: - queue_redraw() + if _rings.is_empty(): + _build() func extractor_anchor() -> Vector2: return Vector2(0.0, -(CORE_RADIUS + float(RING_COUNT - 1) * RING_STEP + 12.0)) -func _process(delta: float) -> void: - disc_phase += delta - queue_redraw() - - -func _draw() -> void: +func _build() -> void: + for child in get_children(): + child.queue_free() + _rings.clear() for i in range(RING_COUNT): var radius: float = CORE_RADIUS + float(i) * RING_STEP # 1.0 at the innermost ring, fading to 0.0 at the outermost - drives # both colour (hotter inward) and stroke weight/brightness. var t: float = 1.0 - float(i) / float(maxi(RING_COUNT - 1, 1)) - var ring_color: Color = DISC_COLOR.lerp(DISC_HOT_COLOR, t * 0.65) - # Alternating spin direction per ring and a speed tied to t reads as + var piece: NeonPiece = NeonPiece.create(self, Vector2.ZERO, + DISC_COLOR.lerp(DISC_HOT_COLOR, t * 0.65)) + # The two arcs are built at their resting angles; the ring node turns + # them. Alternating direction per ring and a speed tied to t reads as # genuine turbulence rather than one disc rotating as a rigid whole. - var spin: float = disc_phase * (0.12 + t * 0.22) * (1.0 if i % 2 == 0 else -1.0) for arc_i in range(2): - var start: float = spin + float(arc_i) * PI + 0.15 + var start: float = float(arc_i) * PI + 0.15 var end: float = start + PI - 0.3 var points := PackedVector2Array() var steps: int = 24 for j in range(steps + 1): var angle: float = lerpf(start, end, float(j) / float(steps)) points.append(Vector2(cos(angle), sin(angle)) * radius) - NeonDraw.draw_glowing_path(self, points, ring_color, 0.5 + t * 0.5, 0.45 + t * 0.35) + piece.add_path(points, 0.5 + t * 0.5, 0.45 + t * 0.35) + _rings.append({ + "piece": piece, + "rate": (0.12 + t * 0.22) * (1.0 if i % 2 == 0 else -1.0), + }) + + +func _process(delta: float) -> void: + disc_phase += delta + # One property per ring. Nothing is rasterised again. + for ring in _rings: + (ring.piece as NeonPiece).rotation = disc_phase * float(ring.rate) diff --git a/planets/GasGiantVisual.gd b/planets/GasGiantVisual.gd index e9172ca..076dd95 100644 --- a/planets/GasGiantVisual.gd +++ b/planets/GasGiantVisual.gd @@ -1,6 +1,7 @@ extends Node2D var gas_phase: float = 0.0 +var _rim: NeonPiece func configure(_shape_seed: int) -> void: @@ -16,12 +17,24 @@ func _process(delta: float) -> void: queue_redraw() -func _draw() -> void: - var rim := PackedVector2Array() +# The bands genuinely deform - each point moves along its own sine - so they +# are the one thing here that has to be re-tessellated to animate. The rim +# does not move at all, so it is a NeonPiece child rasterised once rather +# than a thirty-two point polygon rebuilt every frame alongside them +# (issues #6/#10). +func _ensure_rim() -> void: + if _rim != null: + return + var points := PackedVector2Array() for i in range(32): var angle := TAU * float(i) / 32.0 - rim.append(Vector2(cos(angle) * 118.0, sin(angle) * 118.0)) - NeonDraw.draw_glowing_polygon(self, rim, Color(0.4, 0.78, 1.0), 0.9) + points.append(Vector2(cos(angle) * 118.0, sin(angle) * 118.0)) + _rim = NeonPiece.create(self, Vector2.ZERO, Color(0.4, 0.78, 1.0)) + _rim.add_polygon(points, 0.9) + + +func _draw() -> void: + _ensure_rim() for row in [-62.0, -28.0, 8.0, 43.0, 73.0]: var band := PackedVector2Array() var half_width: float = sqrt(maxf(0.0, 118.0 * 118.0 - row * row)) diff --git a/planets/ResearchLabPlanetVisual.gd b/planets/ResearchLabPlanetVisual.gd index 7f832a9..2ef6d28 100644 --- a/planets/ResearchLabPlanetVisual.gd +++ b/planets/ResearchLabPlanetVisual.gd @@ -5,6 +5,15 @@ extends Node2D # and armoured rather than a natural silhouette (#63: this is an ENEMY # installation, not a celestial body), with a rotating dish and blinking # warning lights to read as occupied/hostile at a glance. +# +# Built out of NeonPiece children rather than redrawn each frame. Issues #6 +# and #10 established that rule for the buildings: every NeonDraw call +# expands into four antialiased polylines, so re-running _draw() to advance +# an animation re-tessellates the whole thing. This visual was doing exactly +# that - nine draw calls a frame, of which eight were fixed geometry - to +# rotate one dish and fade six lights. Both are node properties: the dish is +# a child that spins, the lights are a child whose modulate breathes, and the +# hull is rasterised once and never touched again. var phase: float = 0.0 @@ -14,42 +23,72 @@ const CORE_COLOR: Color = Color(0.9, 0.25, 0.18) const WARNING_COLOR: Color = Color(1.0, 0.2, 0.15) const DARK_CORE: Color = Color(0.14, 0.02, 0.02) +var _dish: NeonPiece +var _lights: NeonPiece + + +func _ready() -> void: + _build() + func configure(_shape_seed: int) -> void: - queue_redraw() + if _dish == null: + _build() func extractor_anchor() -> Vector2: return Vector2(0.0, -(RADIUS + 40.0)) +func _build() -> void: + for child in get_children(): + child.queue_free() + + # The armoured hull and its core: fixed, so drawn once. + var shell: NeonPiece = NeonPiece.create(self, Vector2.ZERO, HULL_COLOR) + shell.core_color = DARK_CORE + shell.add_polygon(_ring(RADIUS, 0.0), 0.85) + var core: NeonPiece = NeonPiece.create(self, Vector2.ZERO, CORE_COLOR) + core.core_color = DARK_CORE + core.add_polygon(_ring(RADIUS * 0.55, PI / 6.0), 0.9) + + # The dish, drawn along +X and then SPUN by rotating this node - the + # rotation is what used to cost a full re-tessellation every frame. + _dish = NeonPiece.create(self, Vector2.ZERO, CORE_COLOR) + _dish.core_color = DARK_CORE + var tip: Vector2 = Vector2(RADIUS * 1.35, 0.0) + _dish.add_line(Vector2.ZERO, tip, 0.55, 0.8) + _dish.add_polygon(_circle(tip, 11.0), 1.0) + + # The warning lights blink by fading the whole piece, not by redrawing + # six circles at a new alpha. + _lights = NeonPiece.create(self, Vector2.ZERO, WARNING_COLOR) + _lights.core_color = DARK_CORE + for i in range(6): + var angle: float = TAU * float(i) / 6.0 + _lights.add_polygon(_circle(Vector2(cos(angle), sin(angle)) * RADIUS, 5.0), 1.0) + + +func _ring(radius: float, offset: float) -> PackedVector2Array: + var points: PackedVector2Array = PackedVector2Array() + for i in range(6): + var angle: float = TAU * float(i) / 6.0 + offset + points.append(Vector2(cos(angle), sin(angle)) * radius) + return points + + +func _circle(centre: Vector2, radius: float) -> PackedVector2Array: + var points: PackedVector2Array = PackedVector2Array() + for i in range(12): + var angle: float = TAU * float(i) / 12.0 + points.append(centre + Vector2(cos(angle), sin(angle)) * radius) + return points + + func _process(delta: float) -> void: phase += delta - queue_redraw() - - -func _draw() -> void: - var hull: PackedVector2Array = PackedVector2Array() - for i in range(6): - var angle: float = TAU * float(i) / 6.0 - hull.append(Vector2(cos(angle), sin(angle)) * RADIUS) - NeonDraw.draw_glowing_polygon(self, hull, HULL_COLOR, 0.85, DARK_CORE) - - var inner: PackedVector2Array = PackedVector2Array() - for i in range(6): - var angle: float = TAU * float(i) / 6.0 + PI / 6.0 - inner.append(Vector2(cos(angle), sin(angle)) * RADIUS * 0.55) - NeonDraw.draw_glowing_polygon(self, inner, CORE_COLOR, 0.9, DARK_CORE) - - # A slow-rotating dish - reads as actively transmitting, not derelict. - var dish_angle: float = phase * 0.6 - var dish_tip: Vector2 = Vector2(cos(dish_angle), sin(dish_angle)) * RADIUS * 1.35 - NeonDraw.draw_glowing_line(self, Vector2.ZERO, dish_tip, CORE_COLOR, 0.8, 0.55, DARK_CORE) - NeonDraw.draw_glowing_circle(self, dish_tip, 11.0, CORE_COLOR, 1.0, DARK_CORE) - - # Blinking hull-corner warning lights. - var blink: float = 0.4 + 0.6 * absf(sin(phase * 4.0)) - for i in range(6): - var angle: float = TAU * float(i) / 6.0 - var at: Vector2 = Vector2(cos(angle), sin(angle)) * RADIUS - NeonDraw.draw_glowing_circle(self, at, 5.0, WARNING_COLOR, blink, DARK_CORE) + # Two node properties. Nothing is rasterised again. + if _dish != null: + _dish.rotation = phase * 0.6 + if _lights != null: + _lights.modulate.a = 0.4 + 0.6 * absf(sin(phase * 4.0)) diff --git a/tests/_lab_planet_visual_preview.gd b/tests/_lab_planet_visual_preview.gd new file mode 100644 index 0000000..b8592b8 --- /dev/null +++ b/tests/_lab_planet_visual_preview.gd @@ -0,0 +1,32 @@ +extends Node2D + +# Preview, not a test - the research lab planet's own art, so the NeonPiece +# rebuild can be compared against what it replaced. +# +# bash tests/screenshot.sh res://tests/_lab_planet_visual_preview.tscn shots/lab_planet.png 20 720x720 + + +func _ready() -> void: + # Both planet visuals that were rebuilt out of NeonPiece children, side by + # side, wound forward so their moving parts are off their rest positions - + # the dish turned, the lights mid-blink, the disc rings spun apart. + var lab: Node2D = load("res://planets/ResearchLabPlanetVisual.gd").new() + lab.position = Vector2(200.0, 360.0) + add_child(lab) + lab.configure(1) + lab.phase = 1.1 + lab._process(0.0) + + var hole: Node2D = load("res://planets/BlackHoleVisual.gd").new() + hole.position = Vector2(540.0, 360.0) + add_child(hole) + hole.configure(1) + hole.disc_phase = 6.0 + hole._process(0.0) + + var gas: Node2D = load("res://planets/GasGiantVisual.gd").new() + gas.position = Vector2(360.0, 620.0) + add_child(gas) + gas.configure(1) + gas.gas_phase = 2.0 + gas._process(0.0) diff --git a/tests/_lab_planet_visual_preview.tscn b/tests/_lab_planet_visual_preview.tscn new file mode 100644 index 0000000..2e74eb3 --- /dev/null +++ b/tests/_lab_planet_visual_preview.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/_lab_planet_visual_preview.gd" id="1"] + +[node name="LabPlanetVisualPreview" type="Node2D"] +script = ExtResource("1") diff --git a/tests/test_guidance_marker_redraw.gd b/tests/test_guidance_marker_redraw.gd new file mode 100644 index 0000000..7e5b6c8 --- /dev/null +++ b/tests/test_guidance_marker_redraw.gd @@ -0,0 +1,111 @@ +extends SceneTree + +# The flight marker must not re-rasterise itself to point somewhere. +# +# Issues #6 and #10 set this rule for the rest of the game: geometry is drawn +# ONCE and thereafter moved, rotated, scaled or faded, because every NeonDraw +# call expands into four antialiased polylines and redrawing to animate +# re-tessellates all of them. The marker broke it - it re-queued a redraw +# every frame for a glow colour that only changes when the DESTINATION does - +# and it is the worst node in the game to get wrong, because it runs with +# PROCESS_MODE_ALWAYS: it kept working through every dialog that stops the +# rest of the game. +# +# Counted off CanvasItem's own `draw` signal rather than a counter of ours, +# so what is measured is real rasterisation. +# +# Covers: +# A. a marker tracking one destination settles - it stops redrawing +# B. it is genuinely still tracking (so this cannot pass by freezing) +# C. a destination of a different colour does redraw it, once + +var state: Node +var scene: Node +var indicator: Control +var draws: int = 0 +var stage: int = 0 +var elapsed: float = 0.0 +var settled_at: int = 0 +var first_position: Vector2 + + +func _initialize() -> void: + state = root.get_node("GameState") + state.reset_new_game() + state.intro_active = false + state.reset_quests() + state.buildings.clear() + scene = load("res://world/Main.tscn").instantiate() + root.add_child(scene) + current_scene = scene + indicator = scene.get_node("UI/ScannerGuidance/Indicator") + indicator.draw.connect(func(): draws += 1) + + +func _process(delta: float) -> bool: + elapsed += delta + match stage: + 0: + if elapsed < 0.4: + return false + # A destination far off, so the marker is pinned to a screen edge + # and its position keeps being recomputed every frame. + state.scanner_target = { + "planet_id": "redraw:probe", "definition_id": "rocky", + "name": "Probe", "world_position": Vector2(90000.0, -40000.0), + } + elapsed = 0.0 + stage = 1 + return false + 1: + # Let it take up the target and draw whatever it needs to. + if elapsed < 0.5: + return false + settled_at = draws + first_position = indicator.position + elapsed = 0.0 + stage = 2 + return false + 2: + # --- A. thirty-odd frames of tracking, no rasterising --------- + if elapsed < 0.6: + return false + var while_tracking: int = draws - settled_at + if while_tracking > 1: + return _fail("the marker rasterised %d times in %.1fs of tracking one destination" \ + % [while_tracking, elapsed]) + # --- B. and it really was tracking --------------------------- + if not scene.get_node("UI/ScannerGuidance").visible: + return _fail("the marker was not even visible, so this proves nothing") + print("tracking one destination: %d redraws in %.1fs" % [while_tracking, elapsed]) + + # --- C. a different world does repaint it -------------------- + var before: int = draws + state.scanner_target = { + "planet_id": "redraw:probe2", "definition_id": "gas_giant", + "name": "Probe Two", "world_position": Vector2(-70000.0, 55000.0), + } + elapsed = 0.0 + stage = 3 + settled_at = before + return false + 3: + if elapsed < 0.4: + return false + var repaints: int = draws - settled_at + if repaints < 1: + return _fail("changing destination never recoloured the marker") + if repaints > 2: + return _fail("changing destination rasterised %d times, expected about one" % repaints) + print("changing destination repaints it %d time(s), then settles" % repaints) + + print("TEST_GUIDANCE_MARKER_REDRAW: PASS") + quit(0) + return true + return false + + +func _fail(message: String) -> bool: + print("TEST_GUIDANCE_MARKER_REDRAW: FAIL (%s)" % message) + quit(1) + return true diff --git a/ui/ScannerGuidance.gd b/ui/ScannerGuidance.gd index e2accff..e33a674 100644 --- a/ui/ScannerGuidance.gd +++ b/ui/ScannerGuidance.gd @@ -14,6 +14,13 @@ const EDGE_MARGIN: float = 34.0 @onready var target_label: Label = $TargetLabel var fleet: Node2D +# The colour the arrow was last rasterised with. Issues #6/#10: a glyph is +# drawn once and thereafter moved and rotated, both of which reuse the cached +# geometry. Re-queueing a redraw every frame for a colour that changes only +# when the DESTINATION changes threw that away - and this node runs even +# while the game is stopped for a dialog, so it was the one piece of UI +# re-tessellating itself through the quietest moments in the game. +var _drawn_glow: Color = Color(0, 0, 0, 0) func _ready() -> void: @@ -53,8 +60,9 @@ func _process(_delta: float) -> void: # authored colour rather than being forced into one. var definition: Resource = PLANET_DEFINITIONS.get_definition( String(target.get("definition_id", ""))) - if definition != null: - indicator.glow_color = definition.map_color + if definition != null and definition.map_color != _drawn_glow: + _drawn_glow = definition.map_color + indicator.glow_color = _drawn_glow indicator.queue_redraw() var sectors: float = toward_target.length() / WorldGen.CHUNK_SIZE target_label.text = "%s ยท %.1f SECTORS" % [ diff --git a/ui/menus/StationMenu.gd b/ui/menus/StationMenu.gd index f02dcbf..104ce61 100644 --- a/ui/menus/StationMenu.gd +++ b/ui/menus/StationMenu.gd @@ -12,23 +12,25 @@ extends "res://ui/menus/ModalMenu.gd" # it does, so the bar row below shows what each of them has left, worst first, # which is the number that decides whether another needs building. -# One bar, wide, for the vessel closest to being spent - the one that decides +# One bar, for the vessel closest to being spent - the one that decides # whether another needs building. The per-vessel figures are listed as TEXT -# below it: a row of small bars was unreadable at the size a phone draws this -# menu, and "cycles left" is a number the player wants to actually read. -const BAR_WIDTH: float = 420.0 -const BAR_HEIGHT: float = 40.0 -const BAR_TOP: float = 494.0 +# beneath it: a row of small bars was unreadable at the size a phone draws +# this menu, and "cycles left" is a number the player wants to read. +# +# A ProgressBar NODE rather than rects in _draw(), the way the ship lab draws +# its research progress. Issues #6/#10 set the rule for the rest of the game - +# geometry is rasterised once and thereafter only moved, scaled or recoloured +# - and a menu that refreshes while it is open would otherwise re-tessellate +# its bar every frame to move one edge. const WORN_COLOR: Color = Color(1.0, 0.35, 0.25) const FRESH_COLOR: Color = Color(0.5, 1.0, 0.55) -const PLATE_COLOR: Color = Color(0.02, 0.02, 0.06, 0.9) -const FRAME_COLOR: Color = Color(0.85, 0.96, 1.0, 0.85) @onready var title: Label = $TitleLabel @onready var assigned_label: Label = $AssignedLabel @onready var status: Label = $StatusLabel @onready var condition_label: Label = $ConditionLabel @onready var condition_list: Label = $ConditionList +@onready var condition_bar: ProgressBar = $ConditionBar @onready var less_button: IconButton = $LessButton @onready var more_button: IconButton = $MoreButton @@ -89,6 +91,7 @@ func refresh() -> void: var left: Array = GameState.station_condition(building_id) var full: int = maxi(GameState.station_work_capacity(building_id), 1) var unit: String = "RUNS" if vessel_type == GameState.VESSEL_TRANSPORTER else "REPAIRS" + condition_bar.visible = not left.is_empty() if left.is_empty(): condition_label.text = "" condition_list.text = "" @@ -101,27 +104,11 @@ func refresh() -> void: lines.append("%s %d %d / %d %s" % [ name.to_upper(), i + 1, int(left[i]), full, unit]) condition_list.text = "\n".join(lines) - queue_redraw() - - -func _draw() -> void: - if building_id == 0: - return - # One bar per vessel standing here, worst first: what is left of the work - # it was built to do. Drawn rather than built from nodes because the count - # changes as vessels arrive and wear out, and a row of bars is cheaper to - # draw than to keep a pool of Controls in step with. - var left: Array = GameState.station_condition(building_id) - if left.is_empty(): - return - var full: int = maxi(GameState.station_work_capacity(building_id), 1) - # The worst of them. station_condition() answers worst first. - var fraction: float = clampf(float(left[0]) / float(full), 0.0, 1.0) - var x: float = (size.x - BAR_WIDTH) * 0.5 - draw_rect(Rect2(x - 3.0, BAR_TOP - 3.0, BAR_WIDTH + 6.0, BAR_HEIGHT + 6.0), - PLATE_COLOR) - draw_rect(Rect2(x, BAR_TOP, BAR_WIDTH, BAR_HEIGHT), - Color(FRESH_COLOR.r, FRESH_COLOR.g, FRESH_COLOR.b, 0.16)) - draw_rect(Rect2(x, BAR_TOP, BAR_WIDTH * fraction, BAR_HEIGHT), - WORN_COLOR.lerp(FRESH_COLOR, fraction)) - draw_rect(Rect2(x, BAR_TOP, BAR_WIDTH, BAR_HEIGHT), FRAME_COLOR, false, 2.0) + # Worst first, so this is the one about to go. + var fraction: float = clampf(float(left[0]) / float(full), 0.0, 1.0) + condition_bar.value = fraction + # The fill is recoloured rather than redrawn: a StyleBox is shared + # geometry, and setting its colour does not re-tessellate anything. + var fill: StyleBoxFlat = condition_bar.get_theme_stylebox("fill") + if fill != null: + fill.bg_color = WORN_COLOR.lerp(FRESH_COLOR, fraction) diff --git a/ui/menus/StationMenu.tscn b/ui/menus/StationMenu.tscn index ce5145c..b004fb7 100644 --- a/ui/menus/StationMenu.tscn +++ b/ui/menus/StationMenu.tscn @@ -1,8 +1,27 @@ -[gd_scene load_steps=3 format=3] +[gd_scene load_steps=5 format=3] [ext_resource type="Script" path="res://ui/IconButton.gd" id="icon"] [ext_resource type="Script" path="res://ui/menus/StationMenu.gd" id="menu"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_condition_background"] +bg_color = Color(0.025, 0.045, 0.09, 0.92) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.18, 0.48, 0.62, 0.9) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_condition_fill"] +bg_color = Color(0.5, 1, 0.55, 0.88) +corner_radius_top_left = 6 +corner_radius_top_right = 6 +corner_radius_bottom_right = 6 +corner_radius_bottom_left = 6 + [node name="StationMenu" type="Control" groups=["modal_menus"]] visible = false mouse_filter = 2 @@ -83,6 +102,21 @@ theme_override_colors/font_color = Color(0.55, 0.78, 0.86, 1) text = "" horizontal_alignment = 1 +[node name="ConditionBar" type="ProgressBar" parent="."] +visible = false +mouse_filter = 2 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -210.0 +offset_top = 494.0 +offset_right = 210.0 +offset_bottom = 534.0 +theme_override_styles/background = SubResource("StyleBoxFlat_condition_background") +theme_override_styles/fill = SubResource("StyleBoxFlat_condition_fill") +max_value = 1.0 +step = 0.001 +show_percentage = false + [node name="ConditionList" type="Label" parent="."] mouse_filter = 2 anchor_right = 1.0 diff --git a/vfx/NeonPiece.gd b/vfx/NeonPiece.gd index a4fc9b7..906a510 100644 --- a/vfx/NeonPiece.gd +++ b/vfx/NeonPiece.gd @@ -16,6 +16,9 @@ extends Node2D # owner can move, spin, scale and fade it without touching the shape. var polygons: Array = [] +# Open runs of points, as distinct from `polygons`, which close. An accretion +# disc is arcs, not rings, so it needs the open form to be a piece at all. +var paths: Array = [] var lines: Array = [] var glow_color: Color = Color.WHITE var core_color: Color = NeonDraw.DEFAULT_CORE @@ -35,6 +38,11 @@ func add_polygon(points: PackedVector2Array, piece_pulse: float = 1.0) -> void: polygons.append({"points": points, "pulse": piece_pulse}) +func add_path(points: PackedVector2Array, piece_pulse: float = 1.0, + width: float = 1.0) -> void: + paths.append({"points": points, "pulse": piece_pulse, "width": width}) + + func add_line(from: Vector2, to: Vector2, width: float, piece_pulse: float = 1.0) -> void: lines.append({"from": from, "to": to, "width": width, "pulse": piece_pulse}) @@ -42,6 +50,9 @@ 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) + for path in paths: + NeonDraw.draw_glowing_path(self, path.points, glow_color, + path.pulse * pulse, path.width, core_color) for line in lines: NeonDraw.draw_glowing_line(self, line.from, line.to, glow_color, line.pulse * pulse, line.width, core_color)