class_name ShipWreckVFX extends Node2D # Forgejo #130: a vessel's death, as a short sequence rather than a moment. # # A ship used to vanish on the frame it was hit and leave a twelve-particle # puff behind - lighter than the one an ENEMY gets (see EnemyExplosionVFX, # which #87 made deliberately loud). Losing a hull is the more expensive # event of the two and it read as the cheaper one. # # So the hull comes off the formation and keeps going: it breaks away from # the fleet, tumbling and venting, for the length of DRIFT_SECONDS, and only # then goes up - out where it has some room, rather than inside the wedge it # was flying in. # # It is a separate node rather than the Ship staying alive a little longer, # and that is the important part. A dying Ship still in the tree is still an # Area2D in the player_ships group with live weapon components: it would keep # shooting, keep being shot at, keep counting as a target, and keep colliding # on its way out. Everything about the ship's own lifecycle - the `destroyed` # signal, leaving Fleet.ships, freeing the node - happens exactly when it did # before. What is left behind is scenery. const DRIFT_SECONDS: float = 0.62 # How hard it is pushed out of the formation. Enough to clear the wedge in # the time above, not so much that it reads as being thrown. const BREAKAWAY_SPEED: float = 300.0 const DRAG: float = 0.72 const SPIN_MIN: float = 2.4 const SPIN_MAX: float = 7.0 const VENT_COUNT: int = 4 const VENT_SPEED: float = 90.0 # The hull darkens as it goes, so the drift reads as dying rather than as # flying away. const FADE_TO: float = 0.35 var hull: PackedVector2Array = PackedVector2Array() var glow_color: Color = Color(0.25, 0.85, 1.0) var core_color: Color = Color.WHITE var velocity: Vector2 = Vector2.ZERO var spin: float = 0.0 var age: float = 0.0 var vents: Array = [] var _blown: bool = false # `away` is the direction out of the formation, so the wreck leaves the fleet # rather than drifting through the middle of it. static func spawn(parent: Node, ship: Node2D, away: Vector2) -> ShipWreckVFX: var wreck := ShipWreckVFX.new() wreck.position = ship.position wreck.rotation = ship.rotation wreck.glow_color = ship.glow_color wreck.core_color = ship.core_color if ship.definition != null: wreck.hull = ship.definition.hull_points var drift: Vector2 = away if away.length() > 0.01 else Vector2.UP.rotated(ship.rotation) # Its own momentum plus the push: a ship hit at speed carries on the way # it was already going. wreck.velocity = ship.velocity + drift.normalized() * BREAKAWAY_SPEED wreck.spin = randf_range(SPIN_MIN, SPIN_MAX) * (1.0 if randf() < 0.5 else -1.0) parent.add_child(wreck) return wreck func _ready() -> void: if hull.is_empty(): hull = PackedVector2Array([Vector2(0, -22), Vector2(-14, 16), Vector2(14, 16)]) for i in range(VENT_COUNT): var angle: float = TAU * float(i) / float(VENT_COUNT) + randf() * 0.6 vents.append({ "pos": Vector2.ZERO, "vel": Vector2(cos(angle), sin(angle)) * randf_range(VENT_SPEED * 0.4, VENT_SPEED), "len": randf_range(9.0, 18.0), }) func _process(delta: float) -> void: age += delta if age >= DRIFT_SECONDS: _blow_up() return position += velocity * delta velocity = velocity.lerp(Vector2.ZERO, clampf(DRAG * delta, 0.0, 1.0)) rotation += spin * delta for vent in vents: vent.pos += vent.vel * delta vent.vel *= 0.94 queue_redraw() func _blow_up() -> void: if _blown: return _blown = true # Where it ended up, not where it was hit - the point of the drift. if is_inside_tree(): ShipExplosionVFX.spawn(get_tree().root, global_position) queue_free() func _draw() -> void: var t: float = clampf(age / DRIFT_SECONDS, 0.0, 1.0) var fade: float = lerpf(1.0, FADE_TO, t) NeonDraw.draw_glowing_polygon(self, hull, Color(glow_color, fade), 1.0, Color(core_color, fade)) # What is coming out of it. Drawn in the wreck's own local space, so the # vents tumble with the hull the way a breach in it would. var vent_colour: Color = Color(1.0, 0.72, 0.35, (1.0 - t) * 0.9) for vent in vents: var dir: Vector2 = Vector2(vent.vel).normalized() NeonDraw.draw_glowing_line(self, vent.pos, vent.pos + dir * float(vent.len), vent_colour, 0.8, 0.4)