I understand that the issue is solved, however, I feel the need to clarify a few things here. They are nuances, but very important ones. As a beginner, it’s important to get these basics right, because they can drastically improve the speed at which you comprehend newer, more complicated, ideas that build upon them.
“but i have no idea how to track some things like what instances of enamys are currently dead” @tdvditchfield
“do you know about queue_free() ?” @AlexanderLife
queue_free() is an important aspect of memory management. However, in Godot, there are two very different kinds of managing memory, and it isn’t clear at first which one is used or why.
Object
Is the base class in Godot. It defines both queue_free()
and free()
.
Object
splits into two main groups:
RefCounted
and all classes that derive from that Resource
, ButtonGroup
, etc., do not need to be freed. That is because they track how many variables are using (referencing) them. I need to explain references, but this post is too long, already.
- All other classes (including
Node
) that don’t inherit from RefCounted
need to be freed manually. With some things like regular Object
, you can and should usually use free()
. queue_free()
tells the Godot engine “Hey, delete this as soon as you can.” If you were to call free()
instead, it would be freed immediately, and then Godot would have no way to remove it from the tree, update the parents, etc.
As for performance enhancing… you can do strong typing func myfunk(myPram :int)->String: I personally love typing everything because it really helps with auto complete and error messages like Cannot Pass a value of type “int” as “String” that avoids “1”+“1”== “11”.
Strong typing is, indeed, a good thing in most cases. It helps prevent accidents before they happen, just like you said. However, it doesn’t help performance at all. GDScript is weakly typed, and it is also interpreted. Those typing hints are only there to tell Godot that something is wrong and to give you an immediate error message, rather than letting it silently continue with unintended behavior. Because it’s interpreted, it still runs the code which would allow you to call the above myfunk
with myPram
with a String
. Only when the call happens, does it fail.
For a practical example of how this can be, have a look at the following minimal example:
extends Node
func _ready():
var three = "3" # three is a Variant, not a String.
# No error, the game starts fine. It breaks when you run it.
add(three)
# add("4") # This one has an Editor error though...
func add(i:int): # Can be int or Variant
print(i + i)
Now, about tracking instances of enemies that are currently dead. That question was never answered.
Let’s say you add an enemy. In their script, let’s add a signal. Instead of calling queue_free(), let’s define a function which is a more specific kind of removing: When they die.
class_name MyDude
extends Node
signal killed(which:Node)
func kill() -> void:
killed.emit(self)
queue_free()
Now, in your spawner:
class_name EnemySpawner
extends Node
@export var my_dude:PackedScene
func make_enemy():
var instance = my_dude.instantiate()
get_parent().add_child(instance)
instance.killed.connect(_on_dude_killed)
func _on_dude_killed(dude):
print("he's dead, jim!")
This creates an enemy instance, and connects the signal. When that specific dude’s kill() function is called, the signal is emitted with that dude’s self
(so you can see which one was killed.
Because queue_free() didn’t actually delete him (yet), you can still do stuff without getting errors.
I enjoy helping beginners out, it’s one of the two reasons I became active here recently. If you need more information than this, it’s best to message me. If you do, as a reminder, only use the built-in messages to talk to users on this platform, because they’re moderated.