spaceship/Projectile.gd

48 lines
1.5 KiB
GDScript3
Raw Normal View History

extends Area2D
@export var speed: float = 500.0
@export var lifetime: float = 1.5
@export var damage: int = 1
@export var bolt_length: float = 20.0
var velocity: Vector2 = Vector2.ZERO
var age: float = 0.0
func _ready() -> void:
add_to_group("player_projectiles")
area_entered.connect(_on_area_entered)
Skill tree, node-based building animation, bigger UI, and a 19x faster test suite Gameplay and UI - Research tree at the mothership gates which structures can be built and grants permanent fleet-capacity upgrades (GameState.SKILL_TREE, SkillTreeView). Node depth is derived from prerequisites, so the layout cannot drift out of step with the dependencies. - Building info popup before committing to a build, with per-building cost and description. - Ship crafter redrawn as a space factory with an assembly animation; mothership redrawn as a radial flagship; command centre as a plotting hub. - Keyboard and mouse support (WASD, Q/E pivot, wheel zoom) for the web build. - UI and font scaled up for phone use, via a custom Theme resource - gui/theme/default_font_size does not reach the default theme. - Asteroids no longer spawn on top of the fleet's start position. - Fleet formation origin derived from the layout's own offsets rather than the centroid, fixing the lurch when a lopsided formation started moving. - Fleet.stop() brakes from the ship's TRUE motion (velocity + fleet_velocity). Ship zeroes its velocity when seated in its slot even while the anchor drags that slot forward, so braking from velocity alone stopped the fleet dead instead of coasting - and whether it did depended on the frame rate. Animation architecture (issue #10) - Buildings, projectiles and explosion VFX no longer animate by redrawing. Static geometry is rasterised once in _draw(); moving parts are NeonPiece children animated through node properties, which reuse the cached geometry. Every NeonDraw call expands into four antialiased polylines, so redrawing to move something re-tessellated all of it. Test suite (issue #8): 1075s -> ~55s - Runs from a copy on Termux's own filesystem. The project lives on Android's FUSE bridge, read through proot, where load("res://Main.tscn") alone cost 8.5s per test against ~2s natively - and nearly every test loads it. - Runs 6 tests at a time, in a single proot-distro login, with output buffered per test and printed only on failure. - --fixed-fps 60 pins delta instead of measuring the wall clock, so a test's result no longer depends on how busy the machine is. Without it, parallelism made two tests fail on 2 of 3 runs. - A test passes only if it both exits 0 and prints ": PASS". - 38 tests, green on three consecutive runs. Bundles Carrois Gothic SC (Apache 2.0) with its licence text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 15:00:41 +02:00
# Draw once. The earlier invisible-bolt bug came from _draw() deriving
# the bolt's look from `age` - drawing only at age==0 froze it at
# whatever the age==0 case rendered as. _draw() below is now a fixed
# shape with no age dependency, so one draw is safe and matches the
# Ship.gd/Asteroid.gd pattern: position/rotation are transform
# properties Godot applies at render time without re-tessellating.
queue_redraw()
func launch(direction: Vector2) -> void:
velocity = direction.normalized() * speed
rotation = direction.angle() + PI / 2.0
func _physics_process(delta: float) -> void:
position += velocity * delta
age += delta
if age >= lifetime:
queue_free()
func _on_area_entered(area: Area2D) -> void:
Asteroids can be shot apart for the ore inside them Asteroids were pure hazard: the only thing one could ever do was cost you a ship. They now take damage like anything else and break open, dropping ore the fleet can fly over and collect - the one resource reachable in open space without conquering anything, and something for the new fire stick to do out there. Two ways to destroy one, deliberately not equivalent. Shot apart yields the ore; flown into does not - the rock shatters against a hull rather than being mined, and a ship or a shield charge has just been spent on it. Without that split a shield vessel could farm asteroids by ramming them. Asteroid.take_damage() matches Enemy's contract exactly, so every weapon that already knew how to hurt an enemy hurts an asteroid through the same branch rather than a second special case: bolts, the laser sweep, and the gas bomb's blast all pick both groups up together. Dropped ore carries the asteroid's own drift, so it reads as debris rather than as a spawned pickup, and it expires after a minute. Rock grew an optional lifetime for that, defaulting to 0 (never) so hand-placed surface rocks are unchanged - a planet surface is bounded and authored, open space is not, and a field shot up and abandoned would otherwise leak nodes for the session. Rock's header note that asteroids and rocks are never on screen together is no longer true and now says so; the hexagon-versus-lump shape language matters more now that they sit side by side, not less. Five weapon tests silenced the asteroid field but never cleared what it had already placed. That was harmless while asteroids were untouchable and is not any more - two of them assert exact hit counts, and a drifting rock in the beam would have made those fail intermittently. Suite 111/111. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:27:40 +02:00
# Asteroids answer take_damage() exactly as enemies do, so both are hit by
# the same branch rather than by a second special case.
if area.is_in_group("enemies") or area.is_in_group("asteroids"):
area.take_damage(damage)
queue_free()
func _draw() -> void:
# Fixed-length, constant-brightness bolt. Drawn once and left alone.
var tip: Vector2 = Vector2(0, -bolt_length * 0.6)
var tail: Vector2 = Vector2(0, bolt_length * 0.4)
NeonDraw.draw_glowing_line(self, tail, tip, Color(1.0, 0.85, 0.3), 1.0, 0.5)