How to balance a RigidBody2D

Godot Version

4.2.1

Question

I’m doing a minecart game, here’s a gif of the minecart movement:

travel

As you can see, sometimes the cart starts rolling. It’s not a big issue because it will always get back to be on correct position, and it actually looks like fun, but I’ve been trying to balance the cart, making it rotate towards the angle perpendicular to the railroad below it, and I don’t have a clue on how to do it.

I already can get the current angle in radians of the slope of the rails with a RayCast2D I use to check if the cart is on the floor, like this:

func _get_current_slope_radians() -> float:
	var rails_collision_normal := Vector2.UP
	if floor_checker.is_colliding():
		rails_collision_normal = floor_checker.get_collision_normal()
	else:
		rails_collision_normal = Vector2.UP
	
	return rails_collision_normal.angle_to_point(Vector2.ZERO) - deg_to_rad(90)

You can see it here (the label shows degrees instead of radians):

slope

Does someone know how to achieve something like that? Thanks a lot.

1 Like

Using apply_torque() or apply_torque_impulse() you can make physics objects rotate along their center of mass.

1 Like

Hi Efi, thanks a lot for your answer! I’m actually trying to find out the correct equation to find the torque amount to apply to balance the cart given its current rotation :blush:

mass * angle_to_destination * speed

2 Likes

Hi Efi. Again, thanks for your help!

This is what I tried inside of the _integrate_forces virtual method:

var torque_correction := mass * _get_current_slope_radians() * state.linear_velocity.x
apply_torque(torque_correction)

But I can’t see any difference, the rotation of the RigidBody doesn’t seem to change nor the cart balance towards the angle.

Oh, when I said “speed” I meant a factor of your choice to change the amount of influence. The speed it should turn at. Not the linear velocity.
In any case, it seems you’ll need a number there that’s quite higher. Either that or you need to use the impulse version of the function. I’ve had trouble understanding when to use which, but impulse seems more reliable to use in “correction” motions (both the force and torque have both versions).

1 Like

Oooh I see! I’m in bed now, so I will try again tomorrow. I was thinking now that that mass of the cart alone is maybe not enough, since the cart is also holding the weight of the wheels and the characters on it, all of them are rigid bodies too! The sum of all the masses should definitely increase the value!

For the difference between the impulse and the regular, the docs say one is time dependant and should be called every frame, and the other is not, so I kind of assume I had to use the time dependant since the _integrate_forces method is called every frame.

Will keep you posted! Thanks a lot!

  • Center of mass: Ensure your RigidBody2D’s center of mass is positioned strategically. Placing it low and closer to the ground helps maintain stability.
  • Torque: Apply torque forces opposite to the direction of imbalance. You can calculate the torque based on the object’s angular velocity and desired center of rotation.
  • Proportional-Integral-Derivative (PID) control: Implement a PID controller that analyzes the angle and angular velocity of the object and applies corrective torque based on their difference from the desired values.
1 Like

Oh, right, I forgot to factor in the current angular velocity…

Thanks a lot!

About the center of mass, you can see where it is in the second gif. The RayCast2D origin is set to the center of mass, so it’s low-left of the cart.

About the other things, they sound like they should do the trick but I’m not a math person and I don’t really know how to calculate the torque based on the angular velocity.

I’ve also read somewhere else about the PID controller, but that sounded even more intimidating to me :slightly_frowning_face:

I will try, though, let’s see what I can get!

Thanks both for your help!

I finally got it! Today I woke up from bed completely unaware that at the end of the day I will be able to understand what a PID controller is and how to implement one in gdscript! Thanks a lot to both of you.

This is a gif of the cart finally balancing itself:

pidcontroller

The PID Controller implementation is rather simple once you understand it, here’s mine, based on this plugin:

class_name PIDController extends RefCounted

var _proportional_factor: float
var _integral_factor: float
var _derivative_factor: float

var _integral := 0.0
var _derivative := 0.0
var _last_error := 0.0

func _init(kp: float = 1.0, ki: float = 1.0, kd: float = 1.0) -> void:
	_proportional_factor = kp
	_integral_factor = ki
	_derivative_factor = kd

func get_output(error: float, delta: float) -> float:
	_integral += error * delta
	_derivative = (error - _last_error) / delta
	_last_error = error
	return (error * _proportional_factor) + (_integral * _integral_factor) + (_derivative * _derivative_factor)

And this is the code inside the _integrate_forces virtual method that applies the torque:

	var target_rotation := current_slope_radians if floor_checker.is_colliding() else DEFAULT_ROTATION # (1)
	var pid_controller := PIDController.new(0.75, 0.0, 0.05) # (2)
	var pid_output := pid_controller.get_output(target_rotation - rotation, get_physics_process_delta_time())
	var combined_masses := (mass + front_wheel.mass + back_wheel.mass) # (3)
	var torque := combined_masses * pid_output * get_physics_process_delta_time() * TORQUE_FACTOR # (4)
	
	apply_torque(torque)
  1. I want to balance to the current slope while touching the floor, or to the default rotation while in air
  2. Playing with these values is fun! This video explains all three incredibly well in less than two minutes and without a single word!
  3. I’m ignoring here the 2 masses of the characters on top of the cart, but they weight very little
  4. The TORQUE_FACTOR is actually quite high, as @Efi suggested, it’s 100K.

And that’s it! I’m quite happy with the result and you both helped me a lot. Thanks again!!

5 Likes

Thanks for sharing your solution.

2 Likes

Sure! Hope it helps!

1 Like