Updates for Hivemind Fight

This commit is contained in:
2026-03-10 11:06:47 -04:00
parent a8dfc215f1
commit 7559114623
3 changed files with 28 additions and 65 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ radius = 40.0
[node name="Hivemind" type="CharacterBody2D"] [node name="Hivemind" type="CharacterBody2D"]
collision_layer = 8 collision_layer = 8
collision_mask = 3 collision_mask = 1
script = ExtResource("1") script = ExtResource("1")
[node name="Collision" type="CollisionShape2D" parent="."] [node name="Collision" type="CollisionShape2D" parent="."]
+1 -1
View File
@@ -8,7 +8,7 @@ radius = 10.0
[node name="Player" type="CharacterBody2D"] [node name="Player" type="CharacterBody2D"]
collision_layer = 2 collision_layer = 2
collision_mask = 9 collision_mask = 1
script = ExtResource("1") script = ExtResource("1")
[node name="Collision" type="CollisionShape2D" parent="."] [node name="Collision" type="CollisionShape2D" parent="."]
+26 -63
View File
@@ -28,14 +28,12 @@ const TELEPORT_CD_MAX := 7.0
const FADE_OUT_TIME := 0.15 const FADE_OUT_TIME := 0.15
const TRANSIT_TIME := 0.5 const TRANSIT_TIME := 0.5
# Orbiting projectiles # Orbiting projectiles (6 static orbiters, no decay)
const ORBIT_MAX := 6 const ORBIT_COUNT := 6
const ORBIT_RADIUS := 60.0 const ORBIT_RADIUS := 60.0
const ORBIT_SPEED := 2.0 # rad/sec const ORBIT_SPEED := 2.0 # rad/sec
const ORBIT_PROJ_SIZE := 10.0 const ORBIT_PROJ_SIZE := 10.0
const ORBIT_TRANSITION_SPEED := 200.0 # how fast orbiters follow after teleport const ORBIT_TRANSITION_SPEED := 200.0 # how fast orbiters follow after teleport
const ORBIT_DECAY_TIME := 6.0 # orbiters fade after this time
const ORBIT_REPLENISH_CD := 2.0 # spawn a new orbiter every X seconds
# Sweep attack (from Warden) # Sweep attack (from Warden)
const SWEEP_CD := 4.5 const SWEEP_CD := 4.5
@@ -90,9 +88,8 @@ var _fade_timer: float = 0.0
var _transit_timer: float = 0.0 var _transit_timer: float = 0.0
# Orbiting projectiles # Orbiting projectiles
var _orbiters: Array[Dictionary] = [] # {node, angle, age, transitioning} var _orbiters: Array[Dictionary] = [] # {node, angle, transitioning}
var _orbit_angle: float = 0.0 var _orbit_angle: float = 0.0
var _orbit_replenish_timer: float = 2.0
# Sweep attack # Sweep attack
var _sweep_timer: float = 3.0 var _sweep_timer: float = 3.0
@@ -138,17 +135,16 @@ func _ready() -> void:
_core_visual.visible = false # hidden during assembly _core_visual.visible = false # hidden during assembly
_core_visual.scale = Vector2(_core_scale, _core_scale) _core_visual.scale = Vector2(_core_scale, _core_scale)
# During assembly: disable contact area and player collision # During assembly: disable all collision (no hitbox until boss appears)
# Only collide with walls (layer 1) to avoid "attaching" to player
if _contact_area: if _contact_area:
_contact_area.monitoring = false _contact_area.monitoring = false
_contact_area.monitorable = false _contact_area.monitorable = false
_contact_area.scale = Vector2(_core_scale, _core_scale)
if _collision_shape: if _collision_shape:
_collision_shape.scale = Vector2(_core_scale, _core_scale) _collision_shape.disabled = true
# Disable player collision during assembly (mask 1 = walls only, not 3 = walls+player) # Never collide with player via CharacterBody2D - use Area2D for contact damage only
collision_mask = 1 # This prevents the "attach" bug where player pushes boss around
collision_mask = 1 # walls only
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
@@ -296,16 +292,20 @@ func _grow_core() -> void:
var growth_per_kill := 0.7 / float(total_enemies) # grows from 0.3 to 1.0 var growth_per_kill := 0.7 / float(total_enemies) # grows from 0.3 to 1.0
_core_scale = minf(_core_scale + growth_per_kill, 1.0) _core_scale = minf(_core_scale + growth_per_kill, 1.0)
# Show core once it starts growing # Show core once it starts growing, and enable scaled collision
if not _core_visual.visible: if not _core_visual.visible:
_core_visual.visible = true _core_visual.visible = true
# Enable collision now that visual is appearing
if _collision_shape:
_collision_shape.disabled = false
_core_visual.scale = Vector2(_core_scale, _core_scale) _core_visual.scale = Vector2(_core_scale, _core_scale)
# Scale collision shapes to match visual # Scale collision to match visual
if _contact_area:
_contact_area.scale = Vector2(_core_scale, _core_scale)
if _collision_shape: if _collision_shape:
_collision_shape.scale = Vector2(_core_scale, _core_scale) _collision_shape.scale = Vector2(_core_scale, _core_scale)
if _contact_area:
_contact_area.scale = Vector2(_core_scale, _core_scale)
func _enter_active() -> void: func _enter_active() -> void:
@@ -313,22 +313,20 @@ func _enter_active() -> void:
_core_visual.visible = true _core_visual.visible = true
_core_visual.scale = Vector2(1.0, 1.0) _core_visual.scale = Vector2(1.0, 1.0)
# Enable contact area and scale up collision # Enable contact area for damage detection (Area2D handles player contact)
if _contact_area: if _contact_area:
_contact_area.monitoring = true _contact_area.monitoring = true
_contact_area.monitorable = true _contact_area.monitorable = true
_contact_area.scale = Vector2(1.0, 1.0) _contact_area.scale = Vector2(1.0, 1.0)
if _collision_shape: if _collision_shape:
_collision_shape.disabled = false
_collision_shape.scale = Vector2(1.0, 1.0) _collision_shape.scale = Vector2(1.0, 1.0)
# Keep collision_mask = 1 (walls only) to prevent "attach" bug
# Re-enable player collision (mask 3 = walls + player)
collision_mask = 3
_teleport_timer = 2.0 _teleport_timer = 2.0
_sweep_timer = 3.0 _sweep_timer = 3.0
_orbit_replenish_timer = ORBIT_REPLENISH_CD
# Spawn orbiting projectiles # Spawn 6 static orbiting projectiles
_spawn_orbiters() _spawn_orbiters()
# Emit boss health bar start # Emit boss health bar start
@@ -425,19 +423,17 @@ func _roll_teleport_cooldown() -> void:
# Orbiting Projectiles # Orbiting Projectiles
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
func _spawn_orbiters() -> void: func _spawn_orbiters() -> void:
# Start with a few orbiters # Spawn 6 static orbiters evenly spaced
for i in 4: for i in ORBIT_COUNT:
var angle := TAU * i / 4.0 var angle := TAU * i / float(ORBIT_COUNT)
var orb := _create_orbiter(angle) var orb := _create_orbiter(angle)
_orbiters.append(orb) _orbiters.append(orb)
_orbit_replenish_timer = ORBIT_REPLENISH_CD
func _create_orbiter(angle: float) -> Dictionary: func _create_orbiter(angle: float) -> Dictionary:
var proj := Node2D.new() var proj := Node2D.new()
var poly := Polygon2D.new() var poly := Polygon2D.new()
poly.name = "Poly"
var hs := ORBIT_PROJ_SIZE * 0.5 var hs := ORBIT_PROJ_SIZE * 0.5
poly.polygon = PackedVector2Array([ poly.polygon = PackedVector2Array([
Vector2(-hs, -hs), Vector2(hs, -hs), Vector2(-hs, -hs), Vector2(hs, -hs),
@@ -460,7 +456,7 @@ func _create_orbiter(angle: float) -> Dictionary:
get_parent().add_child(proj) get_parent().add_child(proj)
proj.global_position = global_position + Vector2.from_angle(angle) * ORBIT_RADIUS proj.global_position = global_position + Vector2.from_angle(angle) * ORBIT_RADIUS
return {"node": proj, "angle": angle, "age": 0.0, "transitioning": false} return {"node": proj, "angle": angle, "transitioning": false}
func _on_orbiter_hit(body: Node, _proj: Node2D) -> void: func _on_orbiter_hit(body: Node, _proj: Node2D) -> void:
@@ -474,31 +470,17 @@ func _tick_orbiters(delta: float) -> void:
_orbit_angle += ORBIT_SPEED * delta _orbit_angle += ORBIT_SPEED * delta
# Decay and orbit movement # Simple orbit movement (no decay)
var still_valid: Array[Dictionary] = []
for orb in _orbiters: for orb in _orbiters:
var node: Node2D = orb["node"] var node: Node2D = orb["node"]
if not is_instance_valid(node): if not is_instance_valid(node):
continue continue
# Age and decay
orb["age"] += delta
if orb["age"] >= ORBIT_DECAY_TIME:
node.queue_free()
continue
# Fade visual as it ages
var age_val: float = orb["age"]
var fade: float = 1.0 - (age_val / ORBIT_DECAY_TIME) * 0.5
var poly := node.get_node_or_null("Poly") as Polygon2D
if poly:
poly.color = Color(0.6, 0.3, 0.7, fade)
var angle: float = orb["angle"] + _orbit_angle var angle: float = orb["angle"] + _orbit_angle
var target_pos := global_position + Vector2.from_angle(angle) * ORBIT_RADIUS var target_pos := global_position + Vector2.from_angle(angle) * ORBIT_RADIUS
if orb["transitioning"]: if orb["transitioning"]:
# Move toward target position # Move toward target position after teleport
var dir := (target_pos - node.global_position).normalized() var dir := (target_pos - node.global_position).normalized()
var dist := node.global_position.distance_to(target_pos) var dist := node.global_position.distance_to(target_pos)
if dist < ORBIT_TRANSITION_SPEED * delta: if dist < ORBIT_TRANSITION_SPEED * delta:
@@ -510,25 +492,6 @@ func _tick_orbiters(delta: float) -> void:
# Normal orbit # Normal orbit
node.global_position = target_pos node.global_position = target_pos
still_valid.append(orb)
_orbiters = still_valid
# Replenish orbiters
if _orbiters.size() < ORBIT_MAX:
_orbit_replenish_timer -= delta
if _orbit_replenish_timer <= 0.0:
_replenish_orbiter()
_orbit_replenish_timer = ORBIT_REPLENISH_CD
func _replenish_orbiter() -> void:
# Find an angle that's not too close to existing orbiters
var new_angle := randf() * TAU
var orb := _create_orbiter(new_angle)
orb["transitioning"] = true # fly in from boss position
_orbiters.append(orb)
func _clear_orbiters() -> void: func _clear_orbiters() -> void:
for orb in _orbiters: for orb in _orbiters: