How to detect an object has left the screen and then return it to its initial position

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

I’m new to godot, but I’m having a problem with something I thought would be simple. I have a ball that is falling off the screen. When it falls off the screen, I want to emit a signal that it has fallen. Then I want to place the ball back into its initial position and let that flow continue repeatedly. I thought that the visibilitynotifier2d would be the right way to do this, but when the ball exits the screen, the signal that it has exited the screen repeats indefinitely and resetting the position of the ball doesn’t seem to stop the signal. This consequently results in the ball just flickering in its starting position.

extends RigidBody2D

signal damage
var initialTransform

func resetBallPosition():
	set_transform(initialTransform)
	
func _ready():
	initialTransform = transform
	
func _on_visible_on_screen_notifier_2d_screen_exited():
	print("ball fell!")
	damage.emit()
	resetBallPosition()

What am I missing from this little task?

:bust_in_silhouette: Reply From: trailqul

Okay, after diving into a lot of little bits of information, I managed to piece together a solution that takes a different approach. For a RigidBody, you can’t just change the position because the physics simulation isn’t happy with that. You need to use _integrate_forces(state) and use the state’s position. Here is the updated code that works

extends RigidBody2D

signal damage
var initialTransform

func reset_ball(state):
	# You need to reset the forces that you don't want to maintain after
	# resetting the position. 
	linear_velocity = Vector2.ZERO
	angular_velocity = 0
	state.transform = initialTransform
	
func _ready():
	initialTransform = transform

func _integrate_forces(state):
	if position.y > get_viewport_rect().size.y:
		damage.emit()
		reset_ball(state)
:bust_in_silhouette: Reply From: crossbito

Hi!

Here is a solution to your problem:

extends RigidBody2D

signal damage
var initialTransform
var resetTransform = false

func _ready():
	initialTransform = transform

func _on_visible_on_screen_notifier_2d_screen_exited():
	print("Ball fell! ", linear_velocity)
	self.set_axis_velocity(Vector2.ZERO)
	damage.emit()
	resetTransform = true

func _integrate_forces(state):
	if resetTransform:
		state.transform = initialTransform
		resetTransform = false

Now, why didn’t your code work? Because RigidBody2D calculates forces on its own, and those forces are applied to its transform (position, rotation, etc). So, RigidBody2D reads that data and modifies it on its own. When you change the transformation, RigidBody2D does it too, overwriting what you did to the object.

To do it safely, you have to modify the properties of the RigidBody2D in _integrate_forces. This function is for that purpose. state represents the current state of the object, and you can change the transform, position, etc., there.

When you try this code, you will also see that here I only reset the transform, but it would be useful to reset the speed of the object because the object still has the speed gained from gravity.

To solve that, you can modify the function as follows:

func _integrate_forces(state):
	if resetTransform:
		state.transform = initialTransform
		resetTransform = false
		state.linear_velocity = Vector2.ZERO

I hope this helps!