Making a RigidBody stick on a moving KinematicBody

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

I am looking for a way to make a RigidBody2D stick on the “surface” of a moving KinematicBody2D as soon as it collides it. Once sticked, it would follow the movement of the moving collider as if it were part of it, ignoring gravity and other forces. I guess it boils down to find a way to apply one object’s transform to another.

In my test scene, the RigidBody2D is called stickingBall. The KinematicBody2D, called colliderRotatingDisc, simply rotates. The first falls on the latter. This image shows my scene setup:

test scene setup

Ideally, I would like the solution to work whatever the movement of the collider is, and not only simple rotations. (That’s why I didn’t want to apply the collided object velocity (body_state.get_contact_collider_velocity_at_position()) on a circular path.)

So far I have tried two lines of research:

  • Detach the root node of StickingBall from the scene tree and attach it as a child of colliderRotatingDisc, using the same global position and rotation. This way, Sticking ball indeed follows the movement of colliderRotatingDisc. However, in my actual game, StickingBall has a Camera2D, and this operation makes the camera shake nastily. In addition, this method seems unpractical and quite savage to me. ^^’
  • Change the transform matrice of StickingBall, trying one way or another to apply the transform of the object it collides. There is obviously something I don’t understand with them, as I didn’t achieve the desired result,

My scripts :

Script of the StickingBall :
(For now, it just activates the custom integrator and stops the ball when the ball collides.)

extends RigidBody2D

var is_sticking = false


func _ready():
	# Enable the logging of 5 collisions.
	set_contact_monitor(true)
	set_max_contacts_reported(5)
	
	# Apply Godot physics at first
	set_use_custom_integrator(false) 


func _integrate_forces( body_state ):
	
	if body_state.get_contact_count() > 0 :
		is_sticking = true
		
		# Ignore Godot physics once the ball sticks
		set_use_custom_integrator(true) 
	
	if is_sticking :
		# TODO: Instead of immobilizing the ball, I would like to make it "stick" to the rigidbody it collides.
		body_state.set_linear_velocity( Vector2(0,0) )
		body_state.set_angular_velocity( 0 )
	
	
	

Script of the colliderRotatingDisc :

extends Node2D

export var rot_speed = PI / 4 # rad/sec

func _ready():
	set_process(true)

func _process(delta):
	get_node("KinematicBody2D").rotate(rot_speed * delta)
	
:bust_in_silhouette: Reply From: Lorèloi

I finally got it! :smiley:

Demonstration gif here

Let’s detail the reasonning behind.

First step : At the moment of the collision, get the position of the ball relatively to the collider, as it will remain unchanged.

In the script of the ball’s rigid body:

# Get the rigid body on which the ball will stick
body_on_which_sticked = body_state.get_contact_collider_object(0)
		
# Some transforms (tr) at the collision instant (ci)...

# from the world coordinate system to the ball coordinate system
var tr_ci_world_to_ball = get_global_transform()
# from the world cs to the collider cs
var tr_ci_world_to_collider = body_on_which_sticked.get_global_transform() 

# compute the transform form the collider to the ball.
# collider->ball = collider->world then world->ball = inverse(world->collider) then world->ball
tr_ci_collider_to_ball = tr_ci_world_to_collider.inverse() * tr_ci_world_to_ball 

We have the transform matrix from the collider to the ball! This matrix, and body_on_which_sticked, will be kept as member variables of the rigidbody script, for later use.

Second step : Each frame, compute the new ball transform so it takes into account the collider movement.

# We take the last transform of the moving collider, and we "add" the same relative position of the ball to the collider it had at the collision instant.
# In other words: "world->collider (at latest news), and then, collider->ball (like at the collision instant)".
global_transform = body_on_which_sticked.get_global_transform() * tr_ci_collider_to_ball

It’s important to set the global transform of the object here, not the local one. And we are done!

Here is the full script of the ball’s rigid body :

extends RigidBody2D

var is_sticking = false

# rigid body on which the ball will stick
var body_on_which_sticked

# (used when sticked) Transform (tr) at the collision instant (ci) from the collider to the ball
var tr_ci_collider_to_ball = Transform2D()

func _ready():
	# Enable the logging of 5 collisions.
	set_contact_monitor(true)
	set_max_contacts_reported(5)
	
	# Apply Godot physics at first
	set_use_custom_integrator(false) 


func _integrate_forces( body_state ):
	
	# We turn on the "sticking mode" as soon as the ball collides
	if is_sticking == false && body_state.get_contact_count() == 1 :
		is_sticking = true
		
		# Ignore Godot physics once the ball sticks
		set_use_custom_integrator(true) 
		
		# Get the rigid body on which the ball will stick
		body_on_which_sticked = body_state.get_contact_collider_object(0)
		
		# For debug, check on which we are sticking
		print("The ball is sticking on a ", body_on_which_sticked.get_name())
		
		# Some transforms (tr) at the collision instant (ci)
		var tr_ci_world_to_ball = get_global_transform() # from the world coordinate system to the ball coordinate system
		var tr_ci_world_to_collider = body_on_which_sticked.get_global_transform() # from the world cs to the collider cs
		tr_ci_collider_to_ball = tr_ci_world_to_collider.inverse() * tr_ci_world_to_ball # Because: collider->ball = collider->world then world->ball = inverse(world->collider) then world->ball
		
	
	# Behavior when sticking
	if is_sticking :
		# We take the last transform of the moving collider, and we keep the same relative position of the ball to the collider it had at the collision instant.
		# In other words: "world->collider (at latest news), and then, collider->ball (like at the collision instant)".
		global_transform = body_on_which_sticked.get_global_transform() * tr_ci_collider_to_ball

Thank you so much for providing the solution! I was banging my head against the wall trying to solve a similar problem, but now things start become a little clearer. Much appreciated

robro | 2020-02-11 13:53