queue_free() signals on_viewport_exited(), running timers on reload_current_scene. How to go around this?

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

I’m making an asteroid like game and I’m trying to build this function where if the enemy leaves the screen on the bottom, he will be ported to the top again (so enemies you dont kill are re-spawned on top)

func _on_viewport_exited(_viewport):
	print("out of screen")
	$RespwnTimer.start()
	yield($RespwnTimer,"timeout")
	var stage_width = get_viewport_rect().size
	var n = rand_range(0, stage_width.x) #stage width
	self.position = Vector2(n,-32) #port to top
	print("respawned on top")

this works flawlessly… untill I shoot an enemy… and when he dies and queue_free() is triggered, my respwn function is also triggered. The mob will correctly die, queue_free() and will not respwn, but the on_viewport_exited() will ALSO trigger and give an error that the timer doesnt exist (obviously)… and print “out of screen”

so… How do I queue_free() my enemy without triggering _on_viewport_exited()… OR… how do I detect it left the viewport/screen that doesnt conflict with queue_free()

I understand that queueing_free makes the enemy leave the screen and viewport… so how should I go about this? I’m confused.

EDIT:
I added a boolean “died” before my death animation and it fixed the problem… but now when I reload the scene to start a new game (simple get_tree().reload_current_scene()) it screams that the $respwntimer doesnt exist…

(E 0:01:14.838 start: Timer was not added to the SceneTree. Either
add it or set autostart to true. <C++ Error> Condition
“!is_inside_tree()” is true. <C++ Source> scene/main/timer.cpp:111
@ start() Enemy.gd:84 @ _on_viewport_exited()

not sure whats going on… was this the right way to do it?

func _got_hit(): #gets hit by Player Bullet
	died = true
	$AnimationPlayer.play("death") #removes mob (queue_free on anim)

func _on_viewport_exited(_viewport):
	if not died:
		print("out of screen")
		$RespwnTimer.start()
		yield($RespwnTimer,"timeout")
		var stage_width = get_viewport_rect().size
		var n = rand_range(0, stage_width.x) #stage width
		self.position = Vector2(n,-32) #port to top
		print("respawned on top")

stoping the timer on the ready function didnt help either… it seems that the timers are still counting for mobs in the limbo (out of screen but not ported yet) when I reload the scene and it cant find them… i dont know, im really confused right now lol

if I comment out the Timer from the viewport_exited function everything works fine with no errors… except the teleport is instant and I dont like it =(

Surtarso | 2021-02-25 14:42

:bust_in_silhouette: Reply From: Inces

I would use cartesian coordinates for checking if unit leaves players vision. Define max and min x,y , and check if units position is inside this rectangle, for example using rect.has-point() function.

hey, I couldn’t find the has_point() function for my nodes or signals anywhere… but the explanation gave me this idea and it worked out

if not_died and self.position.y >= current_scene.get_viewport().size.y:

so it only runs the code viewport exit function if the enemy if out of screen already but queue free wont run it since it will die inside the screen =D

again, not sure that was the best way… but it works. I’ll work on it
thx for the insight.

Surtarso | 2021-02-25 16:47

Yeah, it is one of many ways it could be done, all are fine :slight_smile:

has-point() is function that belongs to Rect2 variable type. You can define Rect2() with 4 arguments meaning 4 corners of rectangle and than use has_point for checking if something is in those borders. The only difference would be a slightly shorter code, so don’t bother :slight_smile:

Inces | 2021-02-25 17:03

actually the problem remains… in a much lesses scale… but remains…

I guess my problem now is with timers running on reload current scene? shouldnt they stop?

I added this to physics process:

out_of_screen = true if self.position.y >= _current_scene.get_viewport_rect().size.y else false

and the rest:

func _got_hit(): #gets hit by Player Bullet
	died = true #toggle for NOT triggering _on_viewport_exited()
	$AnimationPlayer.play("death") #removes mob (queue_free on anim)

func _on_viewport_exited(_viewport):
	if not died and out_of_screen:
		$RespwnTimer.start() 
		yield($RespwnTimer,"timeout")
		self.position = Vector2(n,-32) #port to top

still gives me “timer active not found etc” when I reload my scene (try again button)

Surtarso | 2021-02-25 17:06

I thought You would get rid of on-viewport-exited, this signal seems to trigger on reloading the scene. Since You already use process(), than You should check exits and execute behavior in this function. Of course You will have to use additional boolean in order to trigger timer and respawn once instead of each frame
var once = true # defined in the beginning

process():
     if outofscreen == true and once == true ( and not dead etc ) :
             once = false
             timer somethin, respawn etc.
             once = true

Nicer way to do it would be by defining setter function for outofscreen value, but this should work just as fine

Inces | 2021-02-25 17:28

actually I simplified the thing… removed the died boolean and straight up just test if I’m on screen on viewport exited (no more process)… After all those changes the error seems a bit more random… sometimes it would fire sometimes not… so now I added another check to see if exists…

func _on_viewport_exited(_viewport):
	can_shoot = false
	if self.is_inside_tree():
	  if self.position.y > _current_scene.get_viewport_rect().size.y: 
		$RespwnTimer.start() 
		yield($RespwnTimer,"timeout")
		self.position = Vector2(n,-32) #port to top
		can_shoot = true

SO FAR SO GOOD… if self.is_inside_tree() no errors like that. I didnt want to add too much code coz I’m doing this game exactly as an exercise not to right too much code for simple stuff lmao… thx again

Surtarso | 2021-02-25 17:58

the function now is ridiculously like this:

func _on_viewport_exited(_viewport):
	if self.is_inside_tree():
		if self.position.y > _current_scene.get_viewport_rect().size.y: 
			if !self.is_inside_tree():
				return
			else:
				$RespwnTimer.start() 

and STILL I get, sometimes, cant pinpoint, "timer was not added to the scene tree, condition !is_inside_tree() returned true"

I think it only happens when 2 different enemies are respawning at the same time… but its weird since they are only re-skins with no code from the same class enemy

Surtarso | 2021-02-26 01:29

It may be good for now, but You will propably add some more features or units, and I can predict, that using this signal on_viewport_exited will give You more trouble later :). I believe You should check for what You really want to check, not for something that only resembles it. You don’t want to check if object disappeared from whole game, You want to check if object is no longer visible for the player, if its location moved out of screen borders :wink:

Inces | 2021-02-26 13:49

I totally agree and my stubbornness was only useful to learn a few tricks the hard way…
I got rid of the viewport signal and added an onready var once = true and:

func _physics_process(_delta):
    if self.position.y > _current_scene.get_viewport_rect().size.y and once: 
    	once = false
    	_respwn()

and:

func _respwn():
	$RespwnTimer.start() 
	yield($RespwnTimer,"timeout")
	self.position = Vector2(n,-32) #port to top
	once = true

thx for the right answer =)

Surtarso | 2021-02-26 15:01