Respawning offscreen objects; best way to do it?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Pyhrrous

Hi, I’m working on a game where if an enemy goes offscreen or dies, if the player revisits the position they originally started from then they respawn at that position. However, how I have the code currently set up doesn’t work how I expect:

# for setting things when this comes onscreen
func _on_VisibilityNotifier2D_screen_entered():
	if (can_spawn):
		visible = true;
		set_physics_process(true)
		health = MAX_HEALTH
		motion = Vector2()
		can_spawn = false

#disable things when they leave the screen + reset them to
#their starting position
func _on_VisibilityNotifier2D_screen_exited():
	set_physics_process(false)
	set_position(starting_position)
	
	print($VisibilityNotifier2D.is_on_screen())
	#only allow this to spawn if the spawn position is not onscreen
	if (!$VisibilityNotifier2D.is_on_screen()):
		can_spawn = true;
		visible = false;

What is supposed to happen is that when an object goes offscreen, it teleports back to the position back to where it started off. If the starting position is still onscreen however, then the object will need to be scrolled offscreen again before it can be spawned – thus, the if(!is_on_screen()): can_spawn = true check. Here’s a finite state machine diagram for the intended behaviour in case it’s unclear:
enter image description here

What actually happens though is that the object instantly spawns regardless of the aforementioned check. Looking at some print() debugging, is_on_screen() always returns false from this method, even if the object was just moved onscreen.

Can anyone recommend any ways to fix this? I was thinking of using a 1-frame timer to make sure the object cannot respawn in the same frame that it despawns, but I was wondering if there were any better solutions.

:bust_in_silhouette: Reply From: timothybrentwood

“Note: For performance reasons, VisibilityNotifier2D uses an approximate heuristic with precision determined by ProjectSettings.world/2d/cell_size. If you need precise visibility checking, use another method such as adding an Area2D node as a child of a Camera2D node.”

VisibilityNotifier2Ds aren’t the most precise things. is_on_screen() will always return false when checked from the screen_exited signal callback. The signal is literally fired when is_on_screen() == false. Are you checking a different VisibilityNotifier2D in that callback?

As for the Enemy respawning right away I’m thinking it’s because you’re setting its position in the screen_exited callback. Try:

func _on_VisibilityNotifier2D_screen_exited():
    set_physics_process(false)
    can_spawn = true;
    visible = false;
    call_deferred("set_position", starting_position)

I was aware that VisibilityNotifier2D was imprecise from the documentation description, but seeing in what regards it was from some independent testing (on_screen_entered() would fire before the VisibilityNotifier came onscreen, at certain cell sizes) I assumed it wasn’t the same thing.

Also, no, I’m checking the same VisibilityNotifier2D that calls the signal. I set the position before checking is_on_screen(), so I assumed that the value would change based on the new position. It seems that is_on_screen() only updates at the start of each frame?

Oh, and, the code you describe there doesn’t appear to work. Seems to work the same as it did prior, unless I was supposed to change something else as well.

Pyhrrous | 2021-05-21 22:52