How to balance a RigidBody2D

4.2.1

Question

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

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

``````

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

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

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

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

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:

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