Tune and optimize boss encounter
This commit is contained in:
parent
40f284dcdd
commit
68985316e5
16 changed files with 316 additions and 88 deletions
|
|
@ -2,6 +2,7 @@ class_name OctopusBoss
|
|||
extends Node2D
|
||||
|
||||
signal phase_changed(phase: int)
|
||||
signal health_changed(current: int, maximum: int)
|
||||
signal defeated
|
||||
|
||||
const EYE_SCENE: PackedScene = preload("res://boss/BossEye.tscn")
|
||||
|
|
@ -15,13 +16,14 @@ const EYE_COUNT: int = 6
|
|||
const PHASE_TWO_EYES: int = 3
|
||||
const BODY_RADIUS: float = 86.0
|
||||
const TENTACLE_LENGTH: float = 235.0
|
||||
const MAX_ACTIVE_MINIONS: int = 8
|
||||
const REDRAW_INTERVAL: float = 1.0 / 12.0
|
||||
|
||||
@export var first_wave_delay: float = 1.5
|
||||
@export var wave_interval: float = 6.0
|
||||
@export var minions_per_wave: int = 5
|
||||
@export var laser_interval: float = 5.5
|
||||
@export var laser_charge_time: float = 1.6
|
||||
@export var laser_range: float = 1500.0
|
||||
@export var laser_interval: float = 3.5
|
||||
@export var laser_charge_time: float = 0.85
|
||||
|
||||
var phase: int = Phase.MINION_WAVES
|
||||
var wave_timer: float = 0.0
|
||||
|
|
@ -33,6 +35,8 @@ var eyes_destroyed: int = 0
|
|||
var spawned_minions: Array = []
|
||||
var return_signals: Array = []
|
||||
var animation_phase: float = 0.0
|
||||
var redraw_left: float = 0.0
|
||||
var total_max_health: int = 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -49,8 +53,10 @@ func _build_eyes() -> void:
|
|||
eye.position = eye_position(i)
|
||||
eye.rotation = eye.position.angle()
|
||||
add_child(eye)
|
||||
eye.health_changed.connect(_on_eye_health_changed)
|
||||
eye.destroyed.connect(_on_eye_destroyed.bind(i))
|
||||
eyes.append(eye)
|
||||
total_max_health += int(eye.max_hp)
|
||||
|
||||
|
||||
func eye_position(index: int) -> Vector2:
|
||||
|
|
@ -65,8 +71,6 @@ func _process(delta: float) -> void:
|
|||
if phase == Phase.DEFEATED:
|
||||
return
|
||||
animation_phase += delta
|
||||
spawned_minions = spawned_minions.filter(func(node): return is_instance_valid(node))
|
||||
return_signals = return_signals.filter(func(node): return is_instance_valid(node))
|
||||
|
||||
wave_timer -= delta
|
||||
if wave_timer <= 0.0:
|
||||
|
|
@ -78,20 +82,29 @@ func _process(delta: float) -> void:
|
|||
if laser_timer <= 0.0:
|
||||
fire_laser_volley()
|
||||
laser_timer = laser_interval
|
||||
queue_redraw()
|
||||
redraw_left -= delta
|
||||
if redraw_left <= 0.0:
|
||||
redraw_left = REDRAW_INTERVAL
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func spawn_wave() -> void:
|
||||
var fleet: Node2D = get_tree().get_first_node_in_group("player_fleet")
|
||||
if fleet == null or fleet.ships.is_empty() or get_parent() == null:
|
||||
return
|
||||
var count: int = maxi(3, minions_per_wave - (2 if phase == Phase.LASER_STORM else 0))
|
||||
_prune_invalid(spawned_minions)
|
||||
var wanted: int = maxi(3,
|
||||
minions_per_wave - (2 if phase == Phase.LASER_STORM else 0))
|
||||
var count: int = mini(wanted, MAX_ACTIVE_MINIONS - spawned_minions.size())
|
||||
if count <= 0:
|
||||
return
|
||||
var marked_slot: int = wave_index % count
|
||||
for i in range(count):
|
||||
var minion: Area2D = MINION_SCENE.instantiate()
|
||||
minion.marked = i == marked_slot
|
||||
minion.boss = self
|
||||
get_parent().add_child(minion)
|
||||
minion.tree_exiting.connect(_forget_minion.bind(minion))
|
||||
var angle: float = TAU * float(i) / float(count) + float(wave_index) * 0.47
|
||||
# Minions are siblings rather than boss children, so account for the
|
||||
# encounter scene's imposing boss scale when placing them off its body.
|
||||
|
|
@ -107,6 +120,7 @@ func accept_marked_kill(from: Vector2) -> void:
|
|||
var returning: Node2D = RETURN_SCENE.instantiate()
|
||||
returning.target = self
|
||||
get_parent().add_child(returning)
|
||||
returning.tree_exiting.connect(_forget_return.bind(returning))
|
||||
returning.global_position = from
|
||||
return_signals.append(returning)
|
||||
|
||||
|
|
@ -124,16 +138,33 @@ func open_next_eye() -> bool:
|
|||
|
||||
func _on_eye_destroyed(_index: int) -> void:
|
||||
eyes_destroyed += 1
|
||||
health_changed.emit(current_health(), max_health())
|
||||
if eyes_destroyed >= EYE_COUNT:
|
||||
_defeat()
|
||||
return
|
||||
if phase == Phase.MINION_WAVES and eyes_destroyed >= PHASE_TWO_EYES:
|
||||
phase = Phase.LASER_STORM
|
||||
laser_timer = 0.35
|
||||
laser_timer = 0.2
|
||||
phase_changed.emit(phase)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _on_eye_health_changed(_current: int, _maximum: int) -> void:
|
||||
health_changed.emit(current_health(), max_health())
|
||||
|
||||
|
||||
func current_health() -> int:
|
||||
var total: int = 0
|
||||
for eye in eyes:
|
||||
if is_instance_valid(eye):
|
||||
total += maxi(int(eye.hp), 0)
|
||||
return total
|
||||
|
||||
|
||||
func max_health() -> int:
|
||||
return total_max_health
|
||||
|
||||
|
||||
func fire_laser_volley() -> void:
|
||||
if phase != Phase.LASER_STORM:
|
||||
return
|
||||
|
|
@ -147,7 +178,7 @@ func fire_laser_volley() -> void:
|
|||
for spread in [-0.17, 0.0, 0.17]:
|
||||
var beam: Node2D = LASER_SCENE.instantiate()
|
||||
beam.charge_time = laser_charge_time
|
||||
beam.beam_range = laser_range
|
||||
beam.infinite_range = true
|
||||
beam.beam_half_width = 24.0
|
||||
beam.damage = 1
|
||||
beam.color = Color(1.0, 0.12, 0.58)
|
||||
|
|
@ -156,6 +187,20 @@ func fire_laser_volley() -> void:
|
|||
beam.launch(global_position, toward.rotated(float(spread)))
|
||||
|
||||
|
||||
func _forget_minion(minion: Node) -> void:
|
||||
spawned_minions.erase(minion)
|
||||
|
||||
|
||||
func _forget_return(returning: Node) -> void:
|
||||
return_signals.erase(returning)
|
||||
|
||||
|
||||
func _prune_invalid(nodes: Array) -> void:
|
||||
for i in range(nodes.size() - 1, -1, -1):
|
||||
if not is_instance_valid(nodes[i]) or nodes[i].is_queued_for_deletion():
|
||||
nodes.remove_at(i)
|
||||
|
||||
|
||||
func _defeat() -> void:
|
||||
if phase == Phase.DEFEATED:
|
||||
return
|
||||
|
|
|
|||
Loading…
Reference in a new issue