Updates for Practice room and Relay

This commit is contained in:
2026-03-09 13:16:19 -04:00
parent 171a40a4a8
commit c4cc60f715
2 changed files with 109 additions and 8 deletions
+103 -2
View File
@@ -28,6 +28,12 @@ const BURST_COUNT_P2 := 12
const BURST_SPEED := 150.0 const BURST_SPEED := 150.0
const PROJ_SIZE := 12.0 const PROJ_SIZE := 12.0
const HOMING_COUNT_P1 := 4
const HOMING_COUNT_P2 := 6
const HOMING_SPEED := 100.0
const HOMING_TURN_SPEED := 1.8 # radians per second
const HOMING_DELAY := 0.3 # delay after radial burst before homing fires
const DRIFTER_SPAWN_MIN := 2 const DRIFTER_SPAWN_MIN := 2
const DRIFTER_SPAWN_MAX := 4 const DRIFTER_SPAWN_MAX := 4
const DRIFTER_COUNT_P1_MIN := 2 const DRIFTER_COUNT_P1_MIN := 2
@@ -57,6 +63,8 @@ var _teleport_count : int = 0
var _next_drifter_threshold : int = 0 var _next_drifter_threshold : int = 0
var _burst_projs : Array[Node2D] = [] var _burst_projs : Array[Node2D] = []
var _homing_projs : Array[Node2D] = []
var _homing_delay : float = 0.0
var _climax_timer : float = 0.0 var _climax_timer : float = 0.0
var _blink_accum : float = 0.0 var _blink_accum : float = 0.0
@@ -80,6 +88,7 @@ func _ready() -> void:
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
_tick_spawn_delay(delta) _tick_spawn_delay(delta)
_find_player()
if _phase == Phase.CLIMAX: if _phase == Phase.CLIMAX:
_tick_climax(delta) _tick_climax(delta)
return return
@@ -88,6 +97,8 @@ func _physics_process(delta: float) -> void:
_tick_teleport(delta) _tick_teleport(delta)
_tick_burst_projs(delta) _tick_burst_projs(delta)
_tick_homing_delay(delta)
_tick_homing_projs(delta)
velocity = Vector2.ZERO velocity = Vector2.ZERO
move_and_slide() move_and_slide()
@@ -151,8 +162,9 @@ func _reappear() -> void:
_tp_state = TeleportState.IDLE _tp_state = TeleportState.IDLE
_teleport_count += 1 _teleport_count += 1
# Fire burst # Fire radial burst immediately, then homing after delay
_fire_burst() _fire_burst()
_homing_delay = HOMING_DELAY
# Check drifter spawn # Check drifter spawn
if _teleport_count >= _next_drifter_threshold: if _teleport_count >= _next_drifter_threshold:
@@ -186,7 +198,6 @@ func _fire_burst() -> void:
func _spawn_burst_proj(dir: Vector2) -> void: func _spawn_burst_proj(dir: Vector2) -> void:
var proj := Node2D.new() var proj := Node2D.new()
proj.set_meta("dir", dir) proj.set_meta("dir", dir)
proj.global_position = global_position
var poly := Polygon2D.new() var poly := Polygon2D.new()
var hs := PROJ_SIZE * 0.5 var hs := PROJ_SIZE * 0.5
@@ -209,6 +220,7 @@ func _spawn_burst_proj(dir: Vector2) -> void:
area.body_entered.connect(_on_burst_hit_player.bind(proj)) area.body_entered.connect(_on_burst_hit_player.bind(proj))
get_parent().add_child(proj) get_parent().add_child(proj)
proj.global_position = global_position # Set AFTER add_child for correct transform
_burst_projs.append(proj) _burst_projs.append(proj)
func _on_burst_hit_player(body: Node2D, proj: Node2D) -> void: func _on_burst_hit_player(body: Node2D, proj: Node2D) -> void:
@@ -236,6 +248,89 @@ func _tick_burst_projs(delta: float) -> void:
_burst_projs = still_valid _burst_projs = still_valid
# ---------------------------------------------------------------------------
# Homing Projectiles
# ---------------------------------------------------------------------------
func _tick_homing_delay(delta: float) -> void:
if _homing_delay <= 0.0:
return
_homing_delay -= delta
if _homing_delay <= 0.0:
_fire_homing()
func _fire_homing() -> void:
var count := HOMING_COUNT_P2 if _phase == Phase.TWO else HOMING_COUNT_P1
for i in count:
var angle := TAU * float(i) / float(count)
_spawn_homing_proj(angle)
func _spawn_homing_proj(start_angle: float) -> void:
var proj := Node2D.new()
proj.set_meta("angle", start_angle)
# Diamond shape visual
var poly := Polygon2D.new()
poly.polygon = PackedVector2Array([
Vector2(0, -8), Vector2(5, 0),
Vector2(0, 8), Vector2(-5, 0)
])
poly.color = Color(0.9, 0.3, 0.9) # purple/magenta
proj.add_child(poly)
var area := Area2D.new()
area.collision_layer = 0
area.collision_mask = 2
var shape := CollisionShape2D.new()
var circle := CircleShape2D.new()
circle.radius = 6.0
shape.shape = circle
area.add_child(shape)
proj.add_child(area)
area.body_entered.connect(_on_homing_hit_player.bind(proj))
get_parent().add_child(proj)
proj.global_position = global_position # Set AFTER add_child
_homing_projs.append(proj)
func _on_homing_hit_player(body: Node2D, proj: Node2D) -> void:
if body.is_in_group("player") and body.has_method("take_damage"):
body.take_damage(1)
if is_instance_valid(proj):
proj.queue_free()
func _tick_homing_projs(delta: float) -> void:
if _player == null or not is_instance_valid(_player):
return
var still_valid: Array[Node2D] = []
for proj in _homing_projs:
if not is_instance_valid(proj):
continue
var angle: float = proj.get_meta("angle")
var to_player := (_player.global_position - proj.global_position).normalized()
var desired_angle := to_player.angle()
# Smoothly turn towards player
var diff := angle_difference(angle, desired_angle)
angle += clampf(diff, -HOMING_TURN_SPEED * delta, HOMING_TURN_SPEED * delta)
proj.set_meta("angle", angle)
# Move forward
var dir := Vector2.from_angle(angle)
proj.global_position += dir * HOMING_SPEED * delta
proj.rotation = angle + PI / 2 # Point diamond forward
# Check bounds
var rel := proj.global_position - _room_center
if absf(rel.x) > ROOM_HW - WALL_T or absf(rel.y) > ROOM_HH - WALL_T:
proj.queue_free()
else:
still_valid.append(proj)
_homing_projs = still_valid
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Signal Drifters # Signal Drifters
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -311,4 +406,10 @@ func _die() -> void:
proj.queue_free() proj.queue_free()
_burst_projs.clear() _burst_projs.clear()
# Cleanup homing projectiles
for proj in _homing_projs:
if is_instance_valid(proj):
proj.queue_free()
_homing_projs.clear()
queue_free() queue_free()
+4 -4
View File
@@ -370,13 +370,13 @@ func _unhandled_input(event: InputEvent) -> void:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
const WARDEN_SCENE = preload("res://scenes/enemies/warden.tscn") const WARDEN_SCENE = preload("res://scenes/enemies/warden.tscn")
const RELAY_SCENE = preload("res://scenes/enemies/relay.tscn") const RELAY_SCENE = preload("res://scenes/enemies/relay.tscn")
const SUPERVISOR_SCENE = preload("res://scenes/enemies/supervisor.tscn") const HIVEMIND_SCENE = preload("res://scenes/enemies/hivemind.tscn")
const BOSS_NAMES := ["WARDEN", "RELAY", "SUPERVISOR"] const BOSS_NAMES := ["WARDEN", "RELAY", "HIVEMIND"]
const BOSS_COLORS := [ const BOSS_COLORS := [
Color(0.6, 0.35, 0.2), # Warden — rust Color(0.6, 0.35, 0.2), # Warden — rust
Color(0.15, 0.55, 0.65), # Relay — teal Color(0.15, 0.55, 0.65), # Relay — teal
Color(0.7, 0.2, 0.2), # Supervisor — red Color(0.5, 0.2, 0.6), # Hivemind — purple
] ]
func _build_practice_room() -> void: func _build_practice_room() -> void:
@@ -469,7 +469,7 @@ func _spawn_practice_boss(boss_idx: int) -> void:
match boss_idx: match boss_idx:
0: scene = WARDEN_SCENE 0: scene = WARDEN_SCENE
1: scene = RELAY_SCENE 1: scene = RELAY_SCENE
_: scene = SUPERVISOR_SCENE _: scene = HIVEMIND_SCENE
_practice_boss = scene.instantiate() _practice_boss = scene.instantiate()
contents.add_child(_practice_boss) contents.add_child(_practice_boss)