113 lines
4.1 KiB
GDScript3
113 lines
4.1 KiB
GDScript3
|
|
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)
|