What's the "best" way to keep a Rigidbody upright using counterforces?

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

I’m making a game where the player is piloting a flying vehicle from a first person view. I originally tried using the rigidbody in character mode to stop it from rolling like crazy when it hits something, but then it’s difficult to rotate the player when they use mouselook.

So I want to try a different approach: counter-forces that bring the player upright after they are rotated on the z axis. That way, I can simply add torque on the x and y axis to spin the player where they want to go using mouselook.

What is the best way of doing this in GDScript?

:bust_in_silhouette: Reply From: Raffiepro

just do:

rotation.z = 0

:bust_in_silhouette: Reply From: Rilic

Future me here!

I asked this two years ago when I was just starting out in gamedev programming outside of simple scripts. Here, I was looking for a simple way to calculate how much force was needed to make a rigidbody rotate to and stop at a specific rotation - this was for a submarine game prototype.

Now that I’ve taught myself a thing or two, I can answer it myself!

A good method (as a starting point) for this would be to use a PID controller, which is an algorithm used in cruise control for cars and many guided rocket systems. However, it’s really simple to implement in code for things like submarine rotation.

Instead of keeping track of drag, inertia, and all these other variables outside your field of control, it just keeps track of the value you want to adjust and thrusts up or down to keep the value at a desired target. In this case, we want our Z rotation to stay around 0.

Basically it spits out the amount of input you’d want (usually between -1 and 1, but you can change that), which you can then multiply by your maximum turn speed. This is done by adjusting the proportional, integral, and derivative values (P, I and D!).

  • The proportional value is the maximum amount of thrust that you want, between 0 and 1.

  • The Integral is the value that helps with accounting for a constant force against the value, like gravity or wind. It’s not too helpful for this particular use case, but it’s good to know for other use cases!

  • The derivative is the modifier that helps dampen the movement so it doesn’t overshoot or wobble around the target. It counteracts against the proportional force so it comes to a smooth stop.

All values are between 0 and 1.

OK, but how do I program it?

Since the PID controller I wrote was for Unity instead of Godot, I don’t have any readily available code to paste into here. What I do have is the fantastic tutorial I used that teaches the theory behind it as well as an implementation in Unity C#. I also found a GDScript example on GitHub so you can see how the two implementations relate to each other. For maximum effect, I’d look at both in the order shown:

Here is that fantastic Unity video by Vazgriz: https://www.youtube.com/watch?v=y3K6FUgrgXw

The GitHub GDScript example by fire, forked from DarkEngineer: https://github.com/fire/godot-pid/blob/master/PID_Controller/PID_Controller.gd

As for how I would personally do it, I’d make a new class in GDScript that inherits from Node, much like the above. However, instead of using a scene with it that includes timer to skip the first run calculating the derivative, I’d do what they do in the video and place the derivative calculation into an if statement and make a bool that checks if it has been ran first, like this:

var first_run: bool = true

[...]

if not first_run:
    # calculate derivative here
else:
    first_run = false

Then make the P, I, D, and Integral Saturation (the video goes over that!) exported values so you can edit them in the inspector.

Lastly, instead of making deltatime an exported value like in the GitHub example, I’d pass it as an extra argument inside the script you want to use this node’s functionality in. So you’d call it within your submarine’s movement script here:

extends Rigidbody
export var rotation_force: float  = 10
var rotation_pid_controller = $RotationPID

func _physics_process(delta: float) -> void:
    var input: float = rotation_pid_controller.calculate(transform.basis.get_euler( ).z, 0, delta)
    var z_torque: float = rotation_force * input
    add_torque(Vector3(0, 0, z_torque))

Yes, I did write this whole answer as part of a self-affirming exercise. :>

If anyone finds anything wrong here, please let me know since I’m still learning!