I am developing a 2D game in Godot 4.4.1 where multiple cannons spawn missiles aimed at the player. The problem is that missiles often disappear immediately after spawning, especially from the top-left, top-right, and bottom-left cannons, while only the bottom-right cannon behaves normally. Debug logs show that the missiles are spawned at positions inside the camera’s view, but right after they appear, they trigger the “exiting” event and get removed from the scene. This suggests that something in the missile scene is incorrectly treating the missiles as if they were already offscreen or hit something when they spawn, causing them to be freed immediately. Here is the code:
extends Node2D
@onready var camera = $Camera2D
@onready var missiles: Node2D = $Missiles
@onready var player: CharacterBody2D = $Player
@onready var game_time: Timer = $GameTime
@onready var countdown: Label = $Countdown
@export var missile_scene: PackedScene
@export var spawn_interval: float = 2
@export var min_spawn_interval: float = 0.7
@onready var canon: CharacterBody2D = $Canon
@onready var canon_2: CharacterBody2D = $Canon2
@onready var canon_3: CharacterBody2D = $Canon3
@onready var canon_4: CharacterBody2D = $Canon4
var has_spawn_started = false
var timer: Timer
var stored_missiles = [] # Save last position when out of screen
func _ready() -> void:
if not is_instance_valid(missiles):
print("Missiles node is missing!")
return
# Timer 10s
game_time.wait_time = 10
game_time.start()
#game_time.timeout.connect(_on_game_time_timeout)
game_time.timeout.connect(player.call_deferred.bind("_on_game_time_timeout"))
print("Initial Spawn Interval: ", spawn_interval)
func _process(delta: float) -> void:
if game_time.is_stopped():
countdown.global_position = player.global_position + Vector2(-32, -50)
if game_time.time_left > 0:
countdown.text = str(ceil(game_time.time_left))
countdown.global_position = player.global_position + Vector2(-12, -50)
# 10-5 seconds, spawn missiles from canons
if game_time.time_left <= 10 and game_time.time_left > 5 and not has_spawn_started:
has_spawn_started = true
timer = Timer.new()
timer.wait_time = spawn_interval
timer.autostart = true
timer.timeout.connect(_spawn_missile)
add_child(timer)
# in 5 seconds, spawn the out missile to inside again / reverse
if game_time.time_left <= 5 and has_spawn_started:
player.set_run_speed(1000.0)
_reverse_missiles()
# Canon always point to player
for c in [canon, canon_2, canon_3, canon_4]:
c.rotation = (player.global_position - c.global_position).angle()
func _spawn_missile() -> void:
if not missile_scene:
print("ERROR: Missile scene is not assigned!")
return
# pick random canon as spawnpoint
var canon_list = [canon, canon_2, canon_3, canon_4]
var spawn_canon = canon_list[randi() % canon_list.size()]
# Take position from Marker2D inside of canon scene
var missile_spawn_marker = spawn_canon.get_node_or_null("MissileSpawn")
if missile_spawn_marker == null:
print("ERROR: Couldn't find MissileSpawn node in", spawn_canon.name)
return
# new missile spawn in Marker2D pos
var new_missile = missile_scene.instantiate()
new_missile.global_position = missile_spawn_marker.global_position
print("Missile launched from:", spawn_canon.name, "at position", new_missile.global_position)
# direction to player
var target_position = player.global_position
var direction_vector = (target_position - new_missile.global_position).normalized()
new_missile.rotation = direction_vector.angle()
new_missile.velocity = direction_vector * randf_range(340.0, 390.0)
spawn_canon.shoot()
# Save last position before out of screen
new_missile.connect("tree_exiting", Callable(self, "_on_missile_exiting").bind(new_missile))
# Add missile to main node
missiles.add_child(new_missile)
# Fasten spawn interval
spawn_interval = max(spawn_interval - 0.3, min_spawn_interval)
timer.wait_time = spawn_interval
print("Updated Spawn Interval:", spawn_interval)
func _on_missile_exiting(missile) -> void:
if is_instance_valid(missile):
print("Missile exited at position:", missile.global_position)
stored_missiles.append({
"position": missile.global_position,
"velocity": missile.velocity
})
missile.queue_free()
func _reverse_missiles() -> void:
if stored_missiles.is_empty():
return
print("Reversing stored missiles:", len(stored_missiles))
for missile_data in stored_missiles:
var new_missile = missile_scene.instantiate()
new_missile.global_position = missile_data["position"]
# Pick canon as a last target
var canon_list = [canon, canon_2, canon_3, canon_4]
var target_canon = canon_list[randi() % canon_list.size()]
# Calculate reverse trajectory to canon
var direction_vector = (target_canon.global_position - new_missile.global_position).normalized()
var direction_angle = direction_vector.angle()
new_missile.rotation = direction_angle
new_missile.velocity = direction_vector * missile_data["velocity"].length()
missiles.add_child(new_missile)
stored_missiles.clear()
func _on_player_hit() -> void:
get_tree().call_deferred("reload_current_scene")
func _on_game_time_timeout() -> void:
if is_instance_valid(player):
game_time.stop()
countdown.text = "RESPECT"
Here is the log:
Godot Engine v4.4.1.stable.official.49a5bc7b6 - https://godotengine.org
Vulkan 1.3.278 - Forward+ - Using Device #0: NVIDIA - NVIDIA GeForce MX110
Posisi global MissileSpawn:(-169.8873, 231.1127)
Posisi global MissileSpawn:(175.8873, -176.1127)
Posisi global MissileSpawn:(238.1127, 172.8873)
Posisi global MissileSpawn:(-238.1127, -114.8873)
Initial Spawn Interval: 1.0
Missile launched from:Canon2at position(171.5988, -118.8705)
Updated Spawn Interval:0.7
Missile exited at position:(166.5971, -115.1788)
Missile launched from:Canon4at position(-172.0483, -119.2722)
Updated Spawn Interval:0.4
Missile exited at position:(-167.5382, -115.8233)
Missile launched from:Canonat position(-169.2526, 169.5353)
Updated Spawn Interval:0.1
Missile exited at position:(-164.9812, 165.4365)
Missile launched from:Canon4at position(-172.0483, -119.2722)
Updated Spawn Interval:0.1
Missile exited at position:(-167.4905, -115.7869)
Missile launched from:Canon4at position(-172.0483, -119.2722)
Updated Spawn Interval:0.1
Missile exited at position:(-166.9798, -115.3963)
Missile launched from:Canonat position(-169.2526, 169.5353)
Updated Spawn Interval:0.1
Missile exited at position:(-165.086, 165.5371)
Missile launched from:Canon3at position(174.6805, 174.1429)
Updated Spawn Interval:0.1
Missile launched from:Canonat position(-169.2526, 169.5353)
Updated Spawn Interval:0.1
Missile exited at position:(-165.0627, 165.5147)
Missile launched from:Canon3at position(174.6805, 174.1429)
Updated Spawn Interval:0.1
Missile launched from:Canon2at position(171.5988, -118.8705)
Updated Spawn Interval:0.1
Missile exited at position:(166.6624, -115.227)
Missile launched from:Canon3at position(174.6805, 174.1429)
Updated Spawn Interval:0.1
Missile launched from:Canon2at position(171.5988, -118.8705)
Updated Spawn Interval:0.1
Missile exited at position:(166.7753, -115.3103)
Missile exited at position:(118.9903, 122.6958)
Missile exited at position:(63.83698, 71.74464)
Missile exited at position:(20.38785, 31.60592)
Posisi global MissileSpawn:(-169.8873, 231.1127)
Posisi global MissileSpawn:(175.8873, -176.1127)
Posisi global MissileSpawn:(238.1127, 172.8873)
Posisi global MissileSpawn:(-238.1127, -114.8873)
Initial Spawn Interval: 1.0
--- Debugging process stopped ---


