Invalid Instances of Previously Freed in Tower Defense Project

Godot Version

v4.3

Question

I am trying to make a simple tower defense game and thought it would be pretty simple to make. It has been so far, but I have a glaring issue that I can’t seem to solve.

(FYI I call the enemies ‘mobs’ because why not)

Currently, each tower has it’s own ‘mob queue’ that keeps track of all mobs that enter and exit the tower’s radius. When the shoot timer finishes, the towers check if the mob queue is empty, and if it isn’t, they fire a projectile at the first mob in the queue. This works great for small amounts of mobs, but with even a slightly larger amount it starts to break.

If the mob is valid when checking if the queue is empty, and then dies (is freed) when the checks for the mob position happen, I get the error:

Invalid access to property or key ‘global_position’ on base object of type ‘previously_freed’

The error makes sense for the situation, but I don’t know how to solve it. Doing a check of is_instance_valid() works to not give this error, but will cause the towers to not fire at all whenever the head of the queue is invalid, which lasts until that mob leaves queue. Attempts to remove the mob from the queue when invalid haven’t worked either.

The only other fix I could think of was reassembling the mob queue every time a mob entered or exited, making sure that the mob queue was fresh each check. This didn’t work and the towers would start firing at either the last mobs in the queue or the first. I think this might be because the mob queue doesn’t finish building before the shoot check happens.

My question is: what do I do now?

I’m fine using a completely different method for everything if the method I came up with is just fundamentally flawed, would only be a little butt hurt about it. If anyone has any tips or advice, it would be greatly appreciated. All the relevant code and attachments are below:

Tower Code

extends Node2D

# DEBUG
var id = 0

@onready var arrow_scene = preload("res://Projectiles/arrow.tscn")

# A queue of entities that are currently in the fire radius
# A mob will be added when they enter and removed when they leave
var mob_queue: Array[Area2D]


func _on_timer_timeout() -> void:
	fire()


func fire():
	if !mob_queue.is_empty():
		var mob = mob_queue.front()
		if is_instance_valid(mob): # This bypasses the error, but creates the bug
			var arrow = arrow_scene.instantiate() 
			arrow.rotation = global_position.angle_to_point(mob.global_position) # This is normally where the error happens
			add_child(arrow)
		else:
			print("Invalid instance of mob left in mob queue for ranger ", id)
			print("Mob queue: \n", mob_queue)


func _on_area_2d_area_entered(area: Area2D) -> void:
	if area.is_in_group("mob"):
		mob_queue.append(area)


func _on_area_2d_area_exited(area: Area2D) -> void:
	if area.is_in_group("mob"):
		mob_queue.pop_front()

Console messages from above code

(I tried to upload a video of the no shot bug but I can’t because I’m a new user so you just gotta take my word for it)

Hi
Since the “mob” does not know in which lists he is present it’s ain’t easy to remove them on “exit tree” therefor you can read from the top of the mob list while removing invalid items until you have a valid one:

var mob = mob_queue.front() #get the first mob instance
while not mob_queue.is_empty() and not is_instance_valid(mob): #while there are still mobs and the current mob isnt valid
    mob_queue.remove_at( 0 ) #remove the first mob cause its invalid
    mob = mob_queue.front() #get the next mob

if !mob_queue.is_empty():
    #shot your arrow here on mob

have not tested … but must be okay

1 Like

This works pretty well, thank you for the answer. When testing I did get one weird case where the towers were not shooting at the front of the line anymore, but I only got it once and I don’t know how to replicate it. So, until it poses a problem or a better solution comes up, I’ll use this for now. Thank you!