How to stop and reset RigidBody2D

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

Hello,

I am a total noob in Godot and I am working on a simple 2D game.
In this game I have a ball which is a RigidBody2D (with the mode set to RIGID) and a platform which is a StaticBody2D.
The ball is launched in the air and bounces from the platform.
I need to reset the ball to the initial position after 2 bounces.
I do this by setting the ball’s mode to STATIC and changing its position.
Then I need to relaunch the ball from the initial position.
This doesn’t work for some reason, when I switch the ball back to RIGID mode it starts from the last position where it was before I reset the position.
Could you please explain what I am doing wrong here?

The code:

extends Node2D

# The goal of this script is to:
# 1. Launch the ball after waiting for 5 seconds
# 2. Reset the ball's position to what it was in the beginning after 2 collisions with the platform
# 3. Go to 1

onready var ball_rigid_body_2d = $BallRigidBody2D
onready var ball_collision_shape = $BallRigidBody2D/CollisionShape2D 

var initial_ball_position:Vector2 = Vector2.ZERO
var collision_count:int = 0
var reset:bool = false

func _ready():
	initial_ball_position = ball_rigid_body_2d.position
	create_timer()

func create_timer():
	var timer = Timer.new()
	timer.connect("timeout", self, "_on_timer_timeout") 
	add_child(timer)
	timer.start(5)

# Should relaunch the ball every 5 seconds
func _on_timer_timeout():
	ball_rigid_body_2d.mode = RigidBody2D.MODE_RIGID
	ball_collision_shape.disabled = false
	# Reset velocities
	ball_rigid_body_2d.linear_velocity *= 0
	ball_rigid_body_2d.angular_velocity *= 0
	
	# Reset the collision counter
	collision_count = 0
	var impulse_vector = Vector2(1, -1)
	ball_rigid_body_2d.apply_central_impulse(impulse_vector * 100)

func _physics_process(delta):
	if reset:
		reset = false
		ball_rigid_body_2d.mode = RigidBody2D.MODE_STATIC
		ball_rigid_body_2d.linear_velocity *= 0
		ball_rigid_body_2d.angular_velocity *= 0
		ball_rigid_body_2d.position = initial_ball_position
		ball_collision_shape.disabled = true

func _on_BallBody_body_entered(body):
	print("Collision!")
	collision_count += 1
	# Reset after 2 collisions
	if collision_count > 1:
		reset = true

Here is a short animation of the issue.

I created a super simple test project to demonstrate the problem

Thank you.

Here is the working solution (big thanks to zenbobilly) :
godot-launch-ball-test

I added the source code, in case the file is no longer available:
SceneRoot.gd

extends Node2D

# The goal of this script is to:
# 1. Launch the ball
# 2. Reset the ball's position after 2 collisions with the platform to what it was in the beginning 
# 3. Go to 1

onready var ball_rigid_body_2d:Ball = $BallRigidBody2D

var ball_initial_position: = Vector2.ZERO
var collision_count: = 0
var timer_interval: = 7

func _ready():
	ball_initial_position = ball_rigid_body_2d.position
	ball_rigid_body_2d.connect("body_entered", self, "_on_BallRigidBody2D_body_entered")
	launch_ball()
	create_timer()

func launch_ball() -> void:
	var strength: = 220
	var impulse_vector = Vector2(1, -1)
	ball_rigid_body_2d.make_rigid_and_apply_impulse(impulse_vector * strength)

func reset_ball() -> void:
	ball_rigid_body_2d.make_static_and_set_position(ball_initial_position)

func create_timer():
	var timer = Timer.new()
	timer.connect("timeout", self, "_on_timer_timeout") 
	add_child(timer)
	timer.start(timer_interval)

# Relaunch the ball on timer
func _on_timer_timeout():
	collision_count = 0
	launch_ball()

func _on_BallRigidBody2D_body_entered(body:Node):
	print("Collision!")
	collision_count += 1
	# Reset after 2 collisions
	if collision_count > 1:
		reset_ball()

BallRigidBody2D.gd

extends RigidBody2D
class_name Ball

onready var ball_collision_shape = $CollisionShape2D 

var _position: = Vector2.ZERO
var _reset_position: = false 

var _impulse: = Vector2.ZERO
var _apply_impulse: = false

func make_ridid_deferred() -> void:
	set_deferred("mode", RigidBody2D.MODE_RIGID)
	ball_collision_shape.set_deferred("disabled", false)

func make_static_deferred() -> void:
	set_deferred("mode", RigidBody2D.MODE_STATIC)
	ball_collision_shape.set_deferred("disabled", true)

func make_static_and_set_position(position:Vector2):
	_position = position
	_reset_position = true

func make_rigid_and_apply_impulse(impulse:Vector2):
	_impulse = impulse
	_apply_impulse = true
	# It is important to call the two metods below here, not in the _integrate_forces
	make_ridid_deferred()
	
func _integrate_forces(state):
	if _reset_position:
		_reset_position = false
		
		# Stop all movement
		state.linear_velocity = Vector2.ZERO
		state.angular_velocity = 0
		state.transform.origin = _position
		
		# Make the ball static  and disable collision after the current frame's physics step
		make_static_deferred()
		
	elif _apply_impulse:
		_apply_impulse = false
		apply_central_impulse(_impulse)

Project tree:

  • SceneRoot
    • BallRigidBody2D
      • BallSprite
      • CollisionShape2D
    • PlatformStaticBody2D
      • CollisionShape2D
      • ColorRect

LowFlyingBear | 2022-08-23 08:14

:bust_in_silhouette: Reply From: zenbobilly

I got this working using _integrate_forces () rather than _physics_process () when resetting the RigidBody2D’s state and position. The former is the correct point to change/influence the physics state of a body without causing problems with the Physics Engine.

Furthermore, use create_timer () rather than Timer, because the former doesn’t require the creation of a node.

Thank you very much for your suggestion, zenbobilly. I spent a bit of time on this and noticed that I cannot set mode to MODE_STATIC from __integrate__forces, it just has no effect, and if you set it elsewhere __integrate__forces is not called :frowning: I assume it is not called on static bodies. I need to spend more time on this…

This is what I tried on the RIgidBody2D so far and it didn’t work:

extends RigidBody2D
class_name Ball
onready var ball_collision_shape = $CollisionShape2D 

var _reset_position: = Vector2.ZERO
var _reset_position_requested = false 

var _impulse_to_apply = Vector2.ZERO
var _apply_impulse_requested = false

func _integrate_forces(state):
	if _reset_position_requested:
		_reset_position_requested = false
		mode=RigidBody2D.MODE_STATIC
		state.linear_velocity = Vector2.ZERO
		state.angular_velocity = 0
		state.transform.origin= _reset_position
		ball_collision_shape.disabled = true
	elif _apply_impulse_requested:
		_apply_impulse_requested = false
		mode = RigidBody2D.MODE_RIGID
		apply_central_impulse(_impulse_to_apply)
		ball_collision_shape.disabled = false

func make_static_and_set_position(position:Vector2):
	_reset_position = position
	_reset_position_requested = true

func make_rigid_and_apply_impulse(impulse:Vector2):
	_impulse_to_apply = impulse
	_apply_impulse_requested = true

make_static_and_set_position and make_rigid_and_apply_impulse are called from the parent script. The ball resets now and moves to the initial position, but I cannot make it static again. Maybe I could try setting that in _physics_process? This whole thing is becoming very hacky IMO for such a simple problem :slight_smile:

I tried create_timer() too, but it can only create one shot timer, not a repeatable timer.

LowFlyingBear | 2022-08-18 07:49

With _integrate_forces(), you shouldn’t have to change the type to STATIC when you reset. Simply reset all the forces & position & maybe turn gravity off. Anyway, you’re right about the timer, if you want a repeatable timeout you may as well add the child Timer node to your scene rather than having to create it each time though,but that’s not really your core problem, so it’s not really important.

zenbobilly | 2022-08-18 08:25

Thank you, zenbobilly. I do understand that I can just keep resetting velocity in every physics frame without setting the mode to MODE_STATIC. The reason I wanted to keep it static is to avoid random collisions due to jitter when the ball is on the floor. I think I figured it out how to do without that though, if your RigidBody2D “can sleep” and you set mode to STATIC it automatically starts sleeping, but if I turn off “Can Sleep” property on RIgidBody2D and add some calls to set_deferred(“mode”, RigidBody2D.MODE_STATIC) I can actually set mode to STATIC! It works now! I need some time to clean up the code and I will post the solution. Your answer led me to it. Thank you very much again!

LowFlyingBear | 2022-08-18 08:35