Export and onready behavior

Godot Version

4.2.1

Question

Hi all!
I had a little problem here with a script that makes an enemy on my game(simple 2d platformer) that walks, shoots some projectiles and possibly die if a condition is met. This enemy just walk from one point to another and shoot a projectile(that’s not important)

The problem I was facing is when on_dead() function was executed.
After that, in the physics process I got this error:

Invalid get index ‘flip_h’ (on base: ‘previously freed’).

I made a debug inside of on_dead() and I noticed that even after the queue free, the physics process still executing the lines inside of it. I know(I think) this is because all this will happen at the end of the frame.
But this problem was solved simply changing how I declare the first time this varable, and I dont understand exactly why.

In my first version i had the sprite 2d like this:

@export var sprite: Sprite2D

And then i just changed it for

@onready var sprite= $Sprite2D

And… All of this just happened after put this enemy scene inside of my level scene AND then make a copy(ctrl+D) of this enemy to use it later in the game(like 500px ahead to say something), If I decided to not copy and just drag from my filesystem window, worked fine.

Since every enemy scene is packed into a individual scene, is not supposed to be safe to make copies with ctrl+D inside of my level?

I hope i was enough clear, and I really sorry if I wasn’t.

This is the code I’m using now that is working.

extends CharacterBody2D

@export var speed:= 100.0
@export var health:= 100.0
@export var projectile: PackedScene
@export var death_scene: PackedScene
@export var health_component: HealthComponent

@onready var sprite = $Sprite2D 
@onready var marker_2d_left = $Marker2DLeft
@onready var marker_2d_right = $Marker2DRight
@onready var projectile_container = $ProjectileContainer
@onready var missile_launcher = $MissileLauncher
@onready var timer = $Timer

var initial_position_left
var initial_position_right
var walk_right:= false
var walk_left:= false
#var activation_projectile_distance = 400
var target: Player


func _ready():
	target = get_tree().get_first_node_in_group("player")
	health_component.base_health = health
	initial_position_left = marker_2d_left.global_position.x
	initial_position_right = marker_2d_right.global_position.x
	timer.timeout.connect(on_timeout)
	health_component.dead.connect(on_dead)


func on_timeout():
	if target != null:
		var projectile_instance = projectile.instantiate()
		projectile_container.add_child(projectile_instance)
		projectile_instance.global_position = missile_launcher.global_position
		projectile_instance.speed = 30
		timer.wait_time = randi_range(4,6)


func _physics_process(delta):
	if global_position.x  >= initial_position_right or walk_left:
		walk_left = true
		sprite.flip_h = walk_right
		walk_right = false
		velocity.x = -speed
		
	if global_position.x  <= initial_position_left or walk_right:
		walk_right = true
		walk_left = false
		sprite.flip_h = walk_right		
		velocity.x = speed

	if velocity.y <= 0:
		velocity.y += 200
	
	move_and_slide()


func set_limits_right_left():
	velocity.x = 0


func on_dead():
	health_component.queue_free()
	if death_scene != null:
		print("dead walker")	
		var death_instance = death_scene.instantiate()
		death_instance.global_position = global_position
		
		if sprite.flip_h and sprite != null: #Invalid get index 'flip_h' (on base: 'previously freed').
			death_instance.scale.x *= -1
		get_tree().get_first_node_in_group("enemies").add_child(death_instance)
		queue_free()

I don’t have a great answer for you, but since I’ve already spent 30 minutes thinking about how to answer this I’m going to share my experience with this topic anyways, haha.

I’ve encountered that behaviour before where there is a difference between using control+D vs dragging and dropping from the filesystem window. My understanding is that when you drag and drop from the filesystem window, you are creating a new instance of the packed scene. But when you do control+D on an instance, you are creating a duplicate of that instance, which has the subtle effect where the two instances are sharing certain resources unless they are marked as unique. Or something along those lines.

As for the difference between using export vs onready to reference your sprite child, I don’t really know why that made a difference either.

What were the results of the following experiments when you tried it?

  1. Drag and Drop both times, using @export
  2. Drag and Drop once, then control+D, using @export
  3. Drag and Drop both times, using @onready
  4. Drag and Drop once, then control+D, using @onready?

Would be interested to see the results!

1 Like

I think I noticed this behavior when duplicating a node, looks like resources got shared between the nodes indeed when creating nodes this way.

1 Like

I’m pretty sure that’s what is happening, but I don’t understand really why they are shared if every scene is “encapsulated”. I think from now I have to be very careful with this type of choices when place my enemies in my level.

They are planning to change this behaviour a bit or show you options when duplicating things. But for now you have to be careful.