106 lines
3.8 KiB
GDScript3
106 lines
3.8 KiB
GDScript3
|
|
class_name EnemyBigLaserBeam
|
||
|
|
extends Node2D
|
||
|
|
|
||
|
|
# Self-contained charge -> fire -> flash sequence for the mothership's big
|
||
|
|
# laser (see EnemyBigLaserComponent). Lives independently of the enemy that
|
||
|
|
# fired it once launched - added to the tree root, exactly like
|
||
|
|
# ArtilleryShell/EnemyProjectile - so the mothership dying mid-charge
|
||
|
|
# doesn't cancel a shot already telegraphed to the player.
|
||
|
|
#
|
||
|
|
# The aim direction is locked once, in launch(), and never touched again:
|
||
|
|
# that is what makes the telegraph dodgeable. Nothing in _process() re-reads
|
||
|
|
# the fleet's position while charging.
|
||
|
|
#
|
||
|
|
# Damage: every ship in group "player_ships" whose perpendicular distance
|
||
|
|
# to the beam segment is within beam_half_width, AND whose projection along
|
||
|
|
# the beam falls inside [0, beam_range], takes Ship.take_damage(damage) -
|
||
|
|
# same all-or-nothing shield semantics as every other damage source in this
|
||
|
|
# game (Ship.take_damage already handles the absorb_hit check itself).
|
||
|
|
|
||
|
|
signal finished
|
||
|
|
|
||
|
|
@export var charge_time: float = 2.5
|
||
|
|
@export var beam_range: float = 1200.0
|
||
|
|
@export var beam_half_width: float = 35.0
|
||
|
|
@export var damage: int = 3
|
||
|
|
@export var color: Color = Color(0.55, 0.05, 0.85)
|
||
|
|
|
||
|
|
enum Phase { CHARGING, FLASH }
|
||
|
|
const FLASH_DURATION: float = 0.25
|
||
|
|
|
||
|
|
var phase: int = Phase.CHARGING
|
||
|
|
var origin: Vector2 = Vector2.ZERO
|
||
|
|
var aim_direction: Vector2 = Vector2.RIGHT
|
||
|
|
var charge_left: float = 0.0
|
||
|
|
var flash_time: float = 0.0
|
||
|
|
|
||
|
|
|
||
|
|
func launch(from: Vector2, toward: Vector2) -> void:
|
||
|
|
origin = from
|
||
|
|
aim_direction = toward.normalized() if toward.length() > 0.001 else Vector2.RIGHT
|
||
|
|
position = origin
|
||
|
|
rotation = aim_direction.angle()
|
||
|
|
charge_left = charge_time
|
||
|
|
phase = Phase.CHARGING
|
||
|
|
queue_redraw()
|
||
|
|
|
||
|
|
|
||
|
|
func _process(delta: float) -> void:
|
||
|
|
match phase:
|
||
|
|
Phase.CHARGING:
|
||
|
|
charge_left -= delta
|
||
|
|
if charge_left <= 0.0:
|
||
|
|
_fire()
|
||
|
|
queue_redraw()
|
||
|
|
Phase.FLASH:
|
||
|
|
flash_time -= delta
|
||
|
|
queue_redraw()
|
||
|
|
if flash_time <= 0.0:
|
||
|
|
finished.emit()
|
||
|
|
queue_free()
|
||
|
|
|
||
|
|
|
||
|
|
func _fire() -> void:
|
||
|
|
phase = Phase.FLASH
|
||
|
|
flash_time = FLASH_DURATION
|
||
|
|
for ship in get_tree().get_nodes_in_group("player_ships"):
|
||
|
|
if not is_instance_valid(ship):
|
||
|
|
continue
|
||
|
|
var to_ship: Vector2 = ship.global_position - origin
|
||
|
|
var along: float = to_ship.dot(aim_direction)
|
||
|
|
if along < 0.0 or along > beam_range:
|
||
|
|
continue
|
||
|
|
var perpendicular: float = (to_ship - aim_direction * along).length()
|
||
|
|
if perpendicular <= beam_half_width:
|
||
|
|
ship.take_damage(damage)
|
||
|
|
queue_redraw()
|
||
|
|
|
||
|
|
|
||
|
|
func _draw() -> void:
|
||
|
|
# Drawn in local space, rotated in launch() so local +X already points
|
||
|
|
# along the locked aim_direction - the same reason the beam segment's
|
||
|
|
# own math is done in world space up in _fire() (global_position/origin)
|
||
|
|
# while _draw() only ever needs the length along that local +X axis.
|
||
|
|
match phase:
|
||
|
|
Phase.CHARGING:
|
||
|
|
var progress: float = clampf(1.0 - charge_left / maxf(charge_time, 0.001), 0.0, 1.0)
|
||
|
|
var width: float = lerpf(4.0, beam_half_width * 2.0, progress)
|
||
|
|
_draw_beam_line(width, 0.65)
|
||
|
|
# Countdown arc at the origin, the same idiom as
|
||
|
|
# ArtilleryShell's growing-ring-plus-arc telegraph.
|
||
|
|
draw_arc(Vector2.ZERO, 40.0, -PI / 2.0, -PI / 2.0 + TAU * progress, 32,
|
||
|
|
Color(color, 0.85), 8.0, true)
|
||
|
|
Phase.FLASH:
|
||
|
|
var fprogress: float = 1.0 - flash_time / FLASH_DURATION
|
||
|
|
var width: float = lerpf(beam_half_width * 2.2, 6.0, fprogress)
|
||
|
|
_draw_beam_line(width, 1.0 - fprogress * 0.3)
|
||
|
|
|
||
|
|
|
||
|
|
func _draw_beam_line(width: float, alpha: float) -> void:
|
||
|
|
# Three-layer glow along local +X - the same soft-halo / core / bright-
|
||
|
|
# hairline structure as ArtilleryShell._draw_ring, adapted from a ring
|
||
|
|
# to a straight line.
|
||
|
|
var to: Vector2 = Vector2(beam_range, 0.0)
|
||
|
|
draw_line(Vector2.ZERO, to, Color(color, 0.16 * alpha), width * 2.6, true)
|
||
|
|
draw_line(Vector2.ZERO, to, Color(color, 0.5 * alpha), width, true)
|
||
|
|
draw_line(Vector2.ZERO, to, Color(1.0, 0.92, 0.98, 0.9 * alpha), width * 0.35, true)
|