55 lines
1.9 KiB
GDScript3
55 lines
1.9 KiB
GDScript3
|
|
class_name EnemySpawnerComponent
|
||
|
|
extends Node
|
||
|
|
|
||
|
|
# Continuously spawns another enemy type beside its host, at a fixed
|
||
|
|
# interval, capped at max_alive concurrently. The spawned enemies are
|
||
|
|
# added as children of the spawner's OWN PARENT - not the spawner itself -
|
||
|
|
# so killing the spawner never frees its already-spawned children (they
|
||
|
|
# are siblings, not descendants) and so a surface-hosted spawner keeps its
|
||
|
|
# spawns under the same host a `../Fleet`-style path assumption would miss
|
||
|
|
# (see #44's note on why that hazard exists for anything parented under a
|
||
|
|
# planet surface's PlanetSetupHost rather than directly under Main.tscn).
|
||
|
|
|
||
|
|
var capability_id: String = "spawner"
|
||
|
|
|
||
|
|
@export var spawn_interval: float = 4.0
|
||
|
|
@export var spawned_enemy_id: String = "chaser"
|
||
|
|
@export var max_alive: int = 6
|
||
|
|
@export var spawn_radius: float = 90.0
|
||
|
|
|
||
|
|
var spawn_timer: float = 0.0
|
||
|
|
var spawned: Array = []
|
||
|
|
var enemy_scene: PackedScene = preload("res://Enemy.tscn")
|
||
|
|
|
||
|
|
|
||
|
|
func tick(delta: float) -> void:
|
||
|
|
spawned = spawned.filter(func(node): return is_instance_valid(node))
|
||
|
|
if spawned.size() >= max_alive:
|
||
|
|
return
|
||
|
|
|
||
|
|
spawn_timer -= delta
|
||
|
|
if spawn_timer > 0.0:
|
||
|
|
return
|
||
|
|
spawn_timer = spawn_interval
|
||
|
|
|
||
|
|
var fleet: Node2D = get_tree().get_first_node_in_group("player_fleet")
|
||
|
|
if fleet == null or fleet.ships.is_empty():
|
||
|
|
return
|
||
|
|
|
||
|
|
var spawner: Node2D = get_parent()
|
||
|
|
var host: Node = spawner.get_parent()
|
||
|
|
if host == null:
|
||
|
|
return
|
||
|
|
|
||
|
|
var angle: float = randf() * TAU
|
||
|
|
var offset: Vector2 = Vector2(cos(angle), sin(angle)) * spawn_radius
|
||
|
|
|
||
|
|
var new_enemy: Node2D = enemy_scene.instantiate()
|
||
|
|
new_enemy.enemy_id = spawned_enemy_id
|
||
|
|
# spawner.position is already expressed in `host`'s local space, and
|
||
|
|
# new_enemy is about to be added to that same `host` - unlike
|
||
|
|
# EnemyRangedWeaponComponent's projectile, which reparents to root and
|
||
|
|
# needs global_position, there is no space conversion needed here.
|
||
|
|
new_enemy.position = spawner.position + offset
|
||
|
|
host.add_child(new_enemy)
|
||
|
|
spawned.append(new_enemy)
|