spaceship/vfx/MuzzleSparkVFX.gd
Loïc Wisniewski c8d4731232 #90: give the laser an ignition instead of a blink
The shot was one flat line that appeared at full length and faded. Nothing
happened at the ship, nothing moved, and a hit looked exactly like a miss.

The blade now runs out of the hull over the first 30% of the flash on a
cubic ease-out, holds white-hot, then falls away, with a hum in its width,
a bright cap at the leading end, a flare at the emitter, and sparks off the
hull that fired it. MuzzleSparkVFX takes an optional colour so the laser
throws cyan where the ordinary gun (#87) keeps its gold.

The extra light is a white-hot rod down the middle at 42% of the width,
deliberately not a wider halo: the note at BEAM_WIDTH_SCALE records that the
drawn beam spans exactly what the beam hits, and a halo past that would put
the "it visibly missed" complaint back the other way round.

The damage is untouched - dealt in full, along the whole beam, on the frame
of the shot. A beam that had to arrive would change how the weapon plays,
which the ticket did not ask for, so the test pins it down: a target at the
far end of the range must be hit before the drawn blade has visibly reached
it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:29:55 +02:00

73 lines
2.5 KiB
GDScript

extends Node2D
# Forgejo #87: a brief spray of sparks off the hull that just fired.
#
# Firing was silent at the ship's end - a bolt simply appeared in front of it.
# With a whole fleet shooting there was nothing tying a shot to the vessel it
# came from, which mattered more once the player started aiming: you could not
# see which of your ships were actually in the fight.
#
# Deliberately tiny and short. This fires several times a second per vessel,
# so it has to be legible at a glance and gone before the next one - anything
# with weight would turn a firing fleet into a smear.
#
# No class_name, and it loads itself by path: a global class-name identifier
# is not reliably resolvable from a script reached through a preload chain,
# which is what happens here. Same reason as ResourcePickupVFX.
const SELF_PATH: String = "res://vfx/MuzzleSparkVFX.gd"
const SPARK_COUNT: int = 4
const DURATION: float = 0.13
const SPREAD: float = 0.5
const LENGTH: float = 9.0
const COLOR: Color = Color(1.0, 0.88, 0.42)
var age: float = 0.0
var color: Color = COLOR
var sparks: Array = []
# The colour is optional and defaults to the gun's own: #90 wanted the laser
# to do something at the hull too, and a cyan weapon throwing gold sparks
# would read as a different weapon firing from the same ship.
static func spawn(parent: Node, world_position: Vector2, direction: Vector2,
spark_color: Color = COLOR) -> void:
if direction.length() < 0.001:
return
var vfx: Node2D = (load(SELF_PATH) as GDScript).new()
vfx.position = world_position
vfx.rotation = direction.angle()
vfx.color = spark_color
parent.add_child(vfx)
func _ready() -> void:
# Built once here rather than per frame: the streaks never change shape,
# only how far along their own line they have travelled, so _draw reads
# fixed values. Same discipline as the explosion particles.
for i in range(SPARK_COUNT):
sparks.append({
"angle": randf_range(-SPREAD, SPREAD),
"speed": randf_range(190.0, 340.0),
"length": randf_range(LENGTH * 0.6, LENGTH),
})
queue_redraw()
func _process(delta: float) -> void:
age += delta
if age >= DURATION:
queue_free()
return
queue_redraw()
func _draw() -> void:
var t: float = clampf(age / DURATION, 0.0, 1.0)
var fade: float = 1.0 - t
for spark in sparks:
var heading: Vector2 = Vector2.RIGHT.rotated(float(spark.angle))
var start: Vector2 = heading * float(spark.speed) * age
var end: Vector2 = start + heading * float(spark.length)
NeonDraw.draw_glowing_line(self, start, end, color, fade, 0.35,
color.darkened(0.7))