Workshop I - Suspending a RigidBody (Mirror)


This post is a mirror of a message board post I wrote in 2020, reposted as-is. There are no errors I'm aware of, but it would be different if I wrote it all again. I'm copying it here for preservation and because it makes sense. 🙂

Suspending a RigidBody

I first thought I might make this thread a more detailed tutorial, but that is too much for me to commit to, so in the interest of brevity, this thread will be more like "Car Physics for Games" (link above), explaining the necessary concepts and math in a way that I hope will be helpful, and tricks I learned along the way. I would like to explain how I built a simulator in Godot rather than how to use Godot. If you're interested in learning Godot, I recommend taking a look at the documentation site, or any other tutorial you like. For a couple alternatives, I found this website helpful, and KidsCanCode is another I see passed around.

If you have used a 3D game making program like Unity, this stuff will probably be familiar. If you prefer using Unity or UE4 or whatever, much of it (all of it?) should still be applicable.

So please forgive me for zipping right into it -- how to suspend a RigidBody?

The first solution I tried is apparently a common first attempt, and not really the ideal solution -- what is called a constraint-based model. I had a RigidBody for the car and four more RigidBodies for the wheels, suspended together with Joint nodes. It was promising at first, but didn't pan out. You can get regular friction-based traction and steering and all for low-speed physics -- and even add a tire formula on top -- but no matter what values or tricks I tried, it was unstable and unpredictable. I was spending more time fighting with the physics engine than anything else, so I went back to the drawing board.

A raycast-type suspension is a proven solution, but I think I found something even better than a regular raycast -- Godot's SpringArm node. Not only does it cast a ray, it will automatically move its children nodes to the position where the collision took place, which is useful for positioning the wheel mesh (with zero code!). It is intended for camera control, where a third person camera "springs" out while sliding against walls so that it will not clip through them. Its standard orientation points to Z+ (where a camera would be), so it must be rotated to point down. Unfortunately, this means Z becomes Y and Y becomes Z in all its vector calculations.

What makes the SpringArm extra handy is that you can cast a shape; this avoids issues with using a single raycast model, while remaining simple. I've got it casting a sphere out, with a radius matching the wheel radius. (I haven't found a way to make it cast a cylinder in the correct orientation.)

The node's "Spring Length" value is the maximum suspension travel; position the SpringArm where the wheel would be when the suspension is fully compressed. If you've added the mesh already, it will help you find the right position.

The SpringArm node can report its compression ratio with "1 - (get_hit_length() / spring_length)", where 1 is fully compressed and 0 is fully extended. get_hit_length() is the result of the raycast (in meters). Then I apply Hooke's Law:

spring_force = stiffness * compression

Since I've measured compression as a ratio, it is multiplied by the SpringArm's spring_length:

    var compress = 1 - (get_hit_length() / spring_length)  # 1 is fully compressed
    y_force = spring * compress * spring_length

Next is damping, which relates to the velocity of the suspension movement, so you multiply the damping value by the change in compression divided by time. All you have to do is record the previous compression ratio for each physics frame:

damping = damp_value * (compression_ratio - prev_compression_ratio) / delta

If you check the difference between the last compression ratio and new compression ratio, you can determine whether the spring is being compressed or released, which allows you to implement bump damping and rebound damping separately. 🙂

if (compress - prev_compress) >= 0:  y_force += bump * (compress - prev_compress) / delta
else:  y_force += rebound * (compress - prev_compress) / delta

All that is left is to apply the calculated force to the RigidBody from each SpringArm at each corner. If you're familiar with working in 3D, you should know how to get the relative position -- but I have done it a little bit differently. I will explain in a second post.

⏭️ Suspending a RigidBody, Part 2 ⏭️

Get GDSim

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.