spaceship/vfx/NeonPiece.gd
Loïc Wisniewski 7d1a584c15 Stop redrawing what only needs moving
Reviewed against the rule issues #6 and #10 set for the buildings: geometry
is rasterised once and thereafter only moved, scaled or faded, because every
NeonDraw call expands into four antialiased polylines.

The planet visuals never got that refactor. The black hole rebuilt ten
glowing paths of twenty-five points every frame - forty polylines - to spin
five rings; each ring is a NeonPiece that rotates now. The research lab spent
nine draw calls a frame, eight of them fixed geometry, to turn one dish and
blink six lights; the dish is a child that rotates and the lights a child
whose modulate breathes. The gas giant's bands genuinely deform, so they
still draw, but its constant rim moved out to a child. NeonPiece gained
add_path, which it needed for open arcs.

And I had broken the rule myself. ScannerGuidance re-queued a redraw of its
arrow every frame for a colour that changes only with the destination - and
it runs with PROCESS_MODE_ALWAYS, so it was re-tessellating through every
dialog that stops the rest of the game. Guarded, and pinned by a test that
counts CanvasItem's own draw signal: 36 rasterisations in 0.6s before, one
after.

Two smaller things from the same pass: the station menu drew its condition
bar by hand where the ship lab already uses a ProgressBar node, so it uses
one now and its _draw is gone; and station/upkeep refreshes were wired into
_refresh_garrison(), which nothing but a test calls, so both menus showed
whatever was true when they opened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
2026-09-08 11:35:45 +02:00

58 lines
2.2 KiB
GDScript

class_name NeonPiece
extends Node2D
# One movable piece of neon art, rasterised ONCE and thereafter animated
# only through node properties.
#
# Godot re-runs _draw() solely when a redraw is requested; changing
# position/rotation/scale/modulate/visible reuses the cached geometry
# instead. Since every NeonDraw call expands into four antialiased
# polylines, redrawing a shape each frame just to move or fade it is the
# expensive way to do the cheapest kind of animation. Ship.gd and
# Asteroid.gd already work this way - this is the same idea packaged so a
# building can be assembled out of independently movable parts.
#
# Geometry is defined in local space around the piece's own origin, so the
# 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
var pulse: float = 1.0
static func create(parent: Node, at: Vector2, colour: Color, alpha: float = 1.0) -> NeonPiece:
var piece: NeonPiece = NeonPiece.new()
piece.position = at
piece.glow_color = colour
piece.modulate.a = alpha
parent.add_child(piece)
return piece
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})
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)