Workshop IV - Rays Go Round - Adding Things Up (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. ๐Ÿ™‚

Rays Go Round - Adding Things Up

In "Car Physics for Games", Marco describes a simplistic way of applying torque and accelerating a car, then reading the new RPM back from the wheels. He also briefly outlines a more advanced way of doing it, which is to sum up the torques acting upon the wheels and keep track of the wheel's angular velocity as a result of these competing forces. I think I've developed a pretty solid way of doing that, while avoiding the common consequence of feedback effects in physics-based game design.

The way I've organized my project is to have everything run in one _physics_process() from the RigidBody, so I can make sure all four wheels are updated at once and everything is done in order. All this suspension and tire stuff gets called in a custom method. The RigidBody also passes inputs (from a third node that collects them, for convenience) and simulates the powertrain. Once everything is ready, the RigidBody calls another method on the SpringArm to add up the torques from the engine, tire friction, and braking.

My reasoning: you can initialize the torque for the current frame with friction from your tire model, then add torque from the drivetrain, and finally apply braking torque against those. If braking torque exceeds the other two and the wheel's angular velocity is already almost zero, I just stop it. This mitigates "fidgeting" on stopped wheels from competing forces, so long as the user is on the brakes.

Here is where we calculate "spin" for the slip ratio, and complete the basic necessities for a tire model.

The first step is easy. The longitudinal tire force ("z_force") multiplied by the wheel radius gives a torque. To that, you can go ahead and add the torque from the engine:

   var net_torque = z_force * radius
   net_torque += drive_torque

For drive torque, you can use a static value multiplied by the throttle input to get started.

Applying the brake torque was a little trickier. I mentioned the part about stopping the wheel (first line); the other part is keeping it from spinning the wheels in reverse! ๐Ÿ™‚ To do that, I multiply the brake torque by the sign of "spin". Again, a static value multiplied by input will suffice to start with:

   if abs(spin) < 5 and brake_torque > abs(net_torque):  spin = 0
   else:
       net_torque -= brake_torque * sign(spin)
       spin += delta * net_torque / wheel_moment

Now what's going on in that last line? That is the relationship of angular velocity with torque:

torque = moment_of_inertia * acceleration
acceleration = Torque / moment_of_inertia = delta_velocity / delta_time
delta_velocity = delta_time * torque / moment_of_inertia

The wheel's moment of inertia (rotational inertia) is calculated from its mass (in kilograms), with an export variable to tweak the mass and a quick calculation in _ready() at the start. A simple moment of inertia for a solid disc-shaped object is 0.5 * mass * radius^2, which is close enough for our purposes:

wheel_moment = 0.5 * wheel_mass * radius * radius

With that, the wheels can have some mass to them and react to friction, throttle input, and brake input. To confirm, animate the angular velocity upon your wheel mesh. ๐Ÿ™‚ This part can go into an ordinary _process() thread because it's only visual:

$Mesh.rotate_x(-spin * delta)

Ackermann Steering

Now's about the time to implement steering, so I'll share my simple formula for an approximation of Ackermann steering, given a negative Ackermann variable for the front right wheel:

func steer(input, max_steer, ackermann):
   rotation.y = max_steer * (input + (1 - cos(input * 0.5 * PI)) * ackermann)

Adjust the variable to suit whichever model you're using. In my case for now, it is 0.15.

That's pretty much the basic core of the model! I've trimmed some details out to make it easier to follow, piece by piece. I don't know how successful I've been in that objective. ๐Ÿ˜…

Next time I can share an example I followed that helped me find an effective way to update the engine RPM.

โญ๏ธ Start Your Engines โญ๏ธ

Get GDSim

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.