extends Area2D # A drifting rock that can be shot apart for the ore inside it. # # Asteroids used to be pure hazard: they could only ever cost you a ship. Now # they are also the one resource you can reach without conquering anything, # which is what the fleet's guns are for out in open space. # # Two ways to destroy one, and they are deliberately NOT the same: # - shot apart, which yields the ore # - flown into, which does not. The rock shatters against a hull instead of # being mined, and you have just lost a ship (or a shield charge) for it. # Without that split, a shield vessel could farm asteroids by ramming. const ROCK_SCENE: PackedScene = preload("res://surface/Rock.tscn") # Shots needed to crack one open. Low enough that a single vessel clears one # in a couple of bursts, high enough that a stray shot in passing does not. const HIT_POINTS: int = 2 # How long the dropped ore survives if nobody comes for it. Surface rocks live # forever because a planet surface is bounded and hand-authored; open space is # not, and an asteroid field shot up and left behind would otherwise leak # nodes for the rest of the session. const DROP_LIFETIME: float = 60.0 var velocity: Vector2 = Vector2.ZERO var rotation_speed: float = 0.0 var hp: int = HIT_POINTS var shape_points: PackedVector2Array = PackedVector2Array() func _ready() -> void: randomize() add_to_group("asteroids") area_entered.connect(_on_area_entered) var point_count: int = 7 for i in range(point_count): var angle: float = (float(i) / point_count) * TAU var radius: float = randf_range(14.0, 22.0) shape_points.append(Vector2(cos(angle), sin(angle)) * radius) queue_redraw() func _process(delta: float) -> void: # Deliberately no queue_redraw() here. position and rotation are node # TRANSFORM properties - Godot applies them when rendering, and the # polygon built in _ready() is in local space, so it never changes. # Redrawing each frame re-tessellated four antialiased polylines per # asteroid for an identical result; with ~150 asteroids on screen that # was the single largest source of stutter. position += velocity * delta rotation += rotation_speed * delta # Same contract as Enemy.take_damage, so every weapon that already knows how # to hurt an enemy can hurt an asteroid without learning a second protocol. func take_damage(amount: int) -> bool: hp -= maxi(amount, 1) if hp > 0: return false shatter(true) return true func shatter(drop_ore: bool) -> void: AsteroidExplosionVFX.spawn(get_tree().root, position) if drop_ore: _drop_ore() queue_free() func _drop_ore() -> void: var rock: Node2D = ROCK_SCENE.instantiate() rock.build_shape(randi()) rock.position = position rock.lifetime = DROP_LIFETIME # Inherits the asteroid's drift, so the ore carries on along the path the # rock it came from was already travelling instead of stopping dead in # space - which reads as debris rather than as a spawned pickup. rock.velocity = velocity # Deferred: this runs from inside an area_entered signal, where the # physics server is mid-flush and refuses a direct add_child. get_parent().add_child.call_deferred(rock) func _on_area_entered(area: Area2D) -> void: if area.is_in_group("player_ships"): # No ore: see the note at the top of this file. shatter(false) func _draw() -> void: NeonDraw.draw_glowing_polygon(self, shape_points, Color(0.65, 0.5, 0.35), 1.0)