Replies: 14 comments
-
|
Beta Was this translation helpful? Give feedback.
-
|
Allow me to add a few more suggestions.
*this bit doesnt have anything to do with vehicles themselves but still useful
|
Beta Was this translation helpful? Give feedback.
-
|
Thanks for the thorough explanation! If I understand correctly, the most critical suggestion (and that I think would improve vanilla vehicles, too) is improving the engine-wheel physics simulation. However, I think there may be a miscommunication about how it works currently. To make sure we're on the same page:
The difference I see from your suggestion is that the game includes non-grounded wheels (and slipping wheels, since no custom slip system like proposed) in the average RPM calculation. Is the main difference the custom slip? (Note: the physics engine's wheel slip may work similarly: https://docs.unity3d.com/6000.0/Documentation/Manual/wheel-colliders-friction.html) |
Beta Was this translation helpful? Give feedback.
-
|
I'll attach a video. I think it will illustrate the problem with the current system and the way vehicle speeds are handled. The configured gear ratio only allows the vehicle to reach a certain top speed, which is controlled via clamping the engine torque at max engine rpm. However, since vehicle speed is not tied to engine rpm, I can clamp the rpm increase rate and in turn reach much higher speeds than my gear ratio allows. https://www.youtube.com/watch?v=esPnpZdrb90 My proposal is, rework the way vehicle speeds are handled so slowing down the engine's rpm gain would in turn slow down vehicle acceleration. This makes high torque low acceleration setups possible and allows us to control the acceleration speed of vehicles. In short and to the point, the only issue I have with the gear system is that it doesnt let me control the speed at which vehicles accelerate, independently from their top speed. This makes high torque vehicles slingshot forward, accelerate instantly and skip through multiple gears during acceleration, if their gear ratios are close together. |
Beta Was this translation helpful? Give feedback.
-
|
Dang I actually closed this thread! I dont know how I managed to miss that big green Comment button and click on close when posting my reply.. |
Beta Was this translation helpful? Give feedback.
-
|
No worries, I've definitely done that before, too! I guess the million dollar question is what the right way to actually implement that is. The gearbox implementation is based on this article: https://asawicki.info/Mirror/Car%20Physics%20for%20Games/Car%20Physics%20for%20Games.html I think the relevant section is "How do we get the RPM?", for which the game uses the second approach—taking the per-wheel RPM from the physics simulation. Maybe I'm misinterpreting that, however? |
Beta Was this translation helpful? Give feedback.
-
|
Are slipping wheels powered too? To combat the sudden acceleration you could apply the torque gradually after a shift, easing into it, like a clutch engagement would do. |
Beta Was this translation helpful? Give feedback.
-
|
I am by no means a programmer myself, nor do I really know much when it comes to code, but here we go. I've consulted with 2 friends who actually knows code and a good bit about cars then fed the whole convo into Claude.Ai to wrap things up clean and organized. Throttle is binary - either off or full throttle, no gradual input. The engine RPM is simulated independently with its own inertia, then coupled to the wheels. This approach solves common problems like wheels spinning in the air causing unrealistic RPM spikes, and provides a good foundation for handling wheelspin and gear shifts realistically. Part 1: Engine RPM SimulationThe engine RPM changes based on two rates:
These represent the engine's free-running behavior when not under load. This is how engines work in reality as well. You have multiple components inside of it (and some outside) that direct how fast an engine will gain rpm and how fast it will lose rpm, besides the throttle input. Basic RPM Update LogicEach physics update, calculate two RPM values and blend them: 1. Desired RPM (what the throttle wants): float throttleTargetRPM = throttlePressed ? maxRPM : idleRPM;
float rpmChange = throttlePressed ? engineRPM_IncreaseRate : engineRPM_DecreaseRate;
float desiredRPM = Mathf.MoveTowards(currentRPM, throttleTargetRPM, rpmChange * Time.fixedDeltaTime);2. Wheel-driven RPM (what the wheels are forcing): float totalWheelRPM = 0f;
int groundedCount = 0;
foreach (WheelCollider wheel in driveWheels)
{
if (wheel.isGrounded)
{
totalWheelRPM += Mathf.Abs(wheel.rpm);
groundedCount++;
}
}
if (groundedCount == 0)
{
// No wheels grounded - engine revs freely
currentRPM = desiredRPM;
}
else
{
// Convert wheel RPM to engine RPM through gearbox
float avgWheelRPM = totalWheelRPM / groundedCount;
float wheelDrivenRPM = avgWheelRPM * gearRatios[currentGear];
// Blend between desired and wheel-driven with coupling
float couplingStrength = 10f;
currentRPM = Mathf.Lerp(desiredRPM, wheelDrivenRPM, couplingStrength * Time.fixedDeltaTime);
}
currentRPM = Mathf.Clamp(currentRPM, idleRPM, maxRPM);The
This here should give you a decent idea about the whole thing. Engine free rpm change and wheel based rpm change are blended together so neither can increase or decrease without the other. This also lets you simulate engine braking. Engine braking is what happens when your car is in gear and you let off throttle. Instead of the rpm dropping instantly, your rpm now drops slowly based on the wheels dragging the car along, gradually slowing it down. Basically deceleration speed. Enhanced Version with Slip DetectionTo make the system handle wheelspin better, detect slip and reduce coupling during wheelspin: void UpdateEngineRPM()
{
// Calculate desired RPM from throttle
float throttleTargetRPM = throttlePressed ? maxRPM : idleRPM;
float rpmChange = throttlePressed ? engineRPM_IncreaseRate : engineRPM_DecreaseRate;
float desiredRPM = Mathf.MoveTowards(currentRPM, throttleTargetRPM, rpmChange * Time.fixedDeltaTime);
// Calculate wheel-driven RPM
float totalWheelRPM = 0f;
int groundedCount = 0;
float totalSlip = 0f;
foreach (WheelCollider wheel in driveWheels)
{
if (wheel.isGrounded)
{
totalWheelRPM += Mathf.Abs(wheel.rpm);
groundedCount++;
// Get slip value
WheelHit hit;
if (wheel.GetGroundHit(out hit))
{
totalSlip += Mathf.Abs(hit.forwardSlip);
}
}
}
if (groundedCount == 0)
{
// Wheels in air - engine revs freely
currentRPM = desiredRPM;
}
else
{
// Calculate average slip
float avgSlip = totalSlip / groundedCount;
// Reduce coupling during wheelspin
float baseCoupling = 10f;
float slipReduction = Mathf.Clamp01(avgSlip * 3f);
float effectiveCoupling = Mathf.Lerp(baseCoupling, 2f, slipReduction);
// Apply coupling
float avgWheelRPM = totalWheelRPM / groundedCount;
float wheelDrivenRPM = avgWheelRPM * gearRatios[currentGear];
currentRPM = Mathf.Lerp(desiredRPM, wheelDrivenRPM, effectiveCoupling * Time.fixedDeltaTime);
}
currentRPM = Mathf.Clamp(currentRPM, idleRPM, maxRPM);
}During wheelspin, coupling drops from 10 to 2, allowing the engine to rev higher (like a real car with slipping wheels). This makes wheelspin feel more natural and gives better audio/visual feedback. Part 2: Drive Force CalculationOnce you have the current engine RPM, convert it to drive force: Step 1: Get engine torque from the torque curve // Normalize RPM to 0-1 range for the curve
float normalizedRPM = (currentRPM - idleRPM) / (maxRPM - idleRPM);
// Read torque multiplier from AnimationCurve (0-1)
float torqueMultiplier = torqueCurve.Evaluate(normalizedRPM);
// Calculate actual engine torque in Nm
float engineTorque = maxTorque * torqueMultiplier;My torque curve setup (for a 94 Nm engine, 800-6000 RPM range):
Step 2: Multiply through the gearbox float gearboxOutputTorque = engineTorque * gearRatios[currentGear];Step 3: Convert torque to force // Torque = Force × Radius, so Force = Torque / Radius
float totalDriveForce = gearboxOutputTorque / wheelRadius;
float forcePerWheel = totalDriveForce / driveWheels.Length;Step 4: Apply to WheelColliders void ApplyDriveForce()
{
if (!throttlePressed || currentGear < 0) return;
// Get torque from curve
float normalizedRPM = (currentRPM - idleRPM) / (maxRPM - idleRPM);
float torqueMultiplier = torqueCurve.Evaluate(normalizedRPM);
float engineTorque = maxTorque * torqueMultiplier;
// Through gearbox
float gearboxOutputTorque = engineTorque * gearRatios[currentGear];
// To force
float forcePerWheel = (gearboxOutputTorque / wheelRadius) / driveWheels.Length;
// Apply
foreach (WheelCollider wheel in driveWheels)
{
if (wheel.isGrounded)
{
wheel.motorTorque = forcePerWheel * wheelRadius;
}
}
}Part 3: Automatic Transmission with Wheelspin ProtectionAn automatic transmission that shifts purely on RPM creates a major problem during wheelspin: Scenario: You're in 1st gear with wheels slipping heavily
What happens: This causes:
The Solution: Shift Inhibit During WheelspinReal automatic transmissions use wheel speed sensors and won't shift if wheels are slipping but we shouldnt complicate things that much. Here's an alternative implementation: bool CanShift()
{
// Method 1: Direct slip detection
float avgSlip = 0f;
int groundedCount = 0;
foreach (WheelCollider wheel in driveWheels)
{
WheelHit hit;
if (wheel.GetGroundHit(out hit))
{
avgSlip += Mathf.Abs(hit.forwardSlip);
groundedCount++;
}
}
if (groundedCount == 0) return false; // Don't shift with wheels in air
avgSlip /= groundedCount;
// Don't shift if wheels are slipping more than 25%
if (avgSlip > 0.25f) return false;
// Method 2: Speed vs expected speed check
float vehicleSpeed = rb.velocity.magnitude * 3.6f; // km/h
float wheelCircumference = 2f * Mathf.PI * wheelRadius;
float expectedSpeed = (currentRPM / gearRatios[currentGear]) / 60f * wheelCircumference * 3.6f;
// If actual speed is less than 70% of expected, we're slipping badly
if (vehicleSpeed < expectedSpeed * 0.7f) return false;
return true;
}
void CheckGearShift()
{
// Shift up
if (currentRPM >= shiftUpRPM && currentGear < gearRatios.Length - 1)
{
if (CanShift())
{
currentGear++;
}
// If CanShift() returns false, transmission waits for grip
}
// Shift down
if (currentRPM <= shiftDownRPM && currentGear > 0)
{
// Can always shift down (engine braking is safe)
currentGear--;
}
}This gives you a transmission behavior that:
Part 4: Unity WheelCollider SettingsThe behavior of this entire system is heavily influenced by wheel mass and friction curves: Wheel Masswheel.mass = 25f; // kgHeavy wheels (30-40 kg):
Light wheels (15-20 kg):
Friction CurvesWheelFrictionCurve forward = wheel.forwardFriction;
forward.extremumSlip = 0.3f; // Peak grip at 30% slip
forward.extremumValue = 1.2f; // Peak friction coefficient
forward.asymptoteSlip = 0.7f; // Full slide at 70% slip
forward.asymptoteValue = 0.6f; // Sliding friction
forward.stiffness = 1.5f; // Overall grip multiplier
wheel.forwardFriction = forward;These here are some values Claude recommends. Take it with a grain of salt as unity wheel physics setups via AI are very hit or miss and will generally need a lot of tuning to get right. Nonetheless, they work great as starting points. For sim-arcade balance:
More arcade:
More sim:
Complete ImplementationEverything grouped together and written out by Claude.Ai public class VehicleEngine : MonoBehaviour
{
[Header("Engine Settings")]
public float idleRPM = 800f;
public float maxRPM = 6000f;
public float currentRPM;
public AnimationCurve torqueCurve;
public float maxTorque = 94f;
[Header("RPM Change Rates")]
public float engineRPM_IncreaseRate = 2000f;
public float engineRPM_DecreaseRate = 1500f;
[Header("Coupling")]
public float baseCouplingStrength = 10f;
public float minCouplingStrength = 2f;
[Header("Gearbox")]
public float[] gearRatios = { 20f, 7.8f, 4.5f, 3.2f, 2.5f };
public int currentGear = 0;
public float shiftUpRPM = 5500f;
public float shiftDownRPM = 2000f;
public float maxSlipForShift = 0.25f;
[Header("Wheels")]
public WheelCollider[] driveWheels;
public float wheelRadius = 0.525f;
[Header("Input")]
public bool throttlePressed = false;
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
currentRPM = idleRPM;
}
void FixedUpdate()
{
UpdateEngineRPM();
ApplyDriveForce();
CheckGearShift();
}
void UpdateEngineRPM()
{
// Calculate desired RPM from throttle
float throttleTargetRPM = throttlePressed ? maxRPM : idleRPM;
float rpmChange = throttlePressed ? engineRPM_IncreaseRate : engineRPM_DecreaseRate;
float desiredRPM = Mathf.MoveTowards(currentRPM, throttleTargetRPM, rpmChange * Time.fixedDeltaTime);
// Calculate wheel-driven RPM and slip
float totalWheelRPM = 0f;
int groundedCount = 0;
float totalSlip = 0f;
foreach (WheelCollider wheel in driveWheels)
{
if (wheel.isGrounded)
{
totalWheelRPM += Mathf.Abs(wheel.rpm);
groundedCount++;
WheelHit hit;
if (wheel.GetGroundHit(out hit))
{
totalSlip += Mathf.Abs(hit.forwardSlip);
}
}
}
if (groundedCount == 0)
{
currentRPM = desiredRPM;
}
else
{
float avgSlip = totalSlip / groundedCount;
// Reduce coupling during wheelspin
float slipReduction = Mathf.Clamp01(avgSlip * 3f);
float effectiveCoupling = Mathf.Lerp(baseCouplingStrength, minCouplingStrength, slipReduction);
float avgWheelRPM = totalWheelRPM / groundedCount;
float wheelDrivenRPM = avgWheelRPM * gearRatios[currentGear];
currentRPM = Mathf.Lerp(desiredRPM, wheelDrivenRPM, effectiveCoupling * Time.fixedDeltaTime);
}
currentRPM = Mathf.Clamp(currentRPM, idleRPM, maxRPM);
}
void ApplyDriveForce()
{
if (!throttlePressed || currentGear < 0) return;
float normalizedRPM = (currentRPM - idleRPM) / (maxRPM - idleRPM);
float torqueMultiplier = torqueCurve.Evaluate(normalizedRPM);
float engineTorque = maxTorque * torqueMultiplier;
float gearboxOutputTorque = engineTorque * gearRatios[currentGear];
float forcePerWheel = (gearboxOutputTorque / wheelRadius) / driveWheels.Length;
foreach (WheelCollider wheel in driveWheels)
{
if (wheel.isGrounded)
{
wheel.motorTorque = forcePerWheel * wheelRadius;
}
}
}
bool CanShift()
{
float avgSlip = 0f;
int groundedCount = 0;
foreach (WheelCollider wheel in driveWheels)
{
WheelHit hit;
if (wheel.GetGroundHit(out hit))
{
avgSlip += Mathf.Abs(hit.forwardSlip);
groundedCount++;
}
}
if (groundedCount == 0) return false;
avgSlip /= groundedCount;
// Don't shift if slipping too much
if (avgSlip > maxSlipForShift) return false;
// Also check speed vs expected speed
float vehicleSpeed = rb.velocity.magnitude * 3.6f;
float wheelCircumference = 2f * Mathf.PI * wheelRadius;
float expectedSpeed = (currentRPM / gearRatios[currentGear]) / 60f * wheelCircumference * 3.6f;
if (vehicleSpeed < expectedSpeed * 0.7f) return false;
return true;
}
void CheckGearShift()
{
// Shift up
if (currentRPM >= shiftUpRPM && currentGear < gearRatios.Length - 1)
{
if (CanShift())
{
currentGear++;
}
}
// Shift down (always allowed)
if (currentRPM <= shiftDownRPM && currentGear > 0)
{
currentGear--;
}
}
}Tuning GuideThese here are yet again some values Claude recommends. Take it with a grain of salt as it can be very hit or miss and will generally need a lot of tuning to get right. Nonetheless, they work great as starting points. For economy car feel:
For sports car feel:
For arcade feel:
SummaryThis system provides:
The key insight is that wheelspin requires special handling in both the RPM calculation (reduced coupling) and transmission logic (shift inhibit). Without these, you get jarring behavior and unrealistic responses. |
Beta Was this translation helpful? Give feedback.
-
|
I probably missed some things and there will likely be other/new edge cases that need to be handled. Car being in the air, no wheels contacting the ground, one wheel spinning freely while the other is gripping, torque/force during gear change duration, gear change interval etc etc. This is not Assetto Corsa so there are also a lot of things that you shouldn't need to worry about lol |
Beta Was this translation helpful? Give feedback.
-
|
After giving this some more thought and doing some research on Unity and it's WheelCollider system, I've put together a more robust approach that works with Unity's physics rather than against it. Core IssueUnity's WheelColliders operate purely on torque application and calculate wheel RPM based on vehicle movement using the formula: Wheel radius only affects force conversion (smaller wheels = more force, faster acceleration; larger wheels = less force, slower acceleration), but nothing inherently caps top speed except applied torque and drag coefficients. Unity does not automatically enforce RPM-based speed limits. This is why now if I set the engine rpm increase rate to a lower value and the rpm keeps applying torque based on the torque curve at the current rpm, I will achieve my car's top speed at 1200rpm in 1st gear. There's nothing limiting my top speed other than torque and drag and torque is still being applied since my current rpm is not at the max rpm yet. Proposed Solution: Dual RPM SimulationImplement two separate RPM values that interact with each other: 1. Engine RPM (Simulated)
2. Wheel RPM (Unity Calculated)
3. Expected Wheel RPM (Calculated)
Control LogicCompare Expected Wheel RPM against Actual Wheel RPM to determine system behavior: When Expected > Actual (Engine wants to go faster than wheels are moving):
When Expected < Actual (Wheels spinning faster than engine wants):
When Expected ≈ Actual (RPMs matched within threshold):
Torque ApplicationMaintain the existing torque curve implementation where engine RPM defines maximum available torque on a 0-1 scale. However, modulate actual torque application based on the RPM difference to prevent overshooting and create smooth behavior. Benefits
ImplementationI've asked Claude to turn my proposal into code and this is what it gave me: public class RealisticEngineSystem : MonoBehaviour
{
[Header("Engine")]
public float engineRPM = 800f;
public float idleRPM = 800f;
public float maxRPM = 6000f;
public float engineRPM_IncreaseRate = 2000f;
public float engineRPM_DecreaseRate = 1500f;
public AnimationCurve torqueCurve;
public float maxTorque = 94f;
[Header("Gearbox")]
public float[] gearRatios = { 20f, 7.8f, 4.5f, 3.2f, 2.5f };
public int currentGear = 0;
[Header("Wheels")]
public WheelCollider[] driveWheels;
[Header("RPM Matching")]
public float rpmMatchThreshold = 200f;
public float rpmStopThreshold = 500f;
public float rpmMatchStrength = 0.5f;
[Header("Input")]
public bool throttlePressed = false;
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
UpdateEngineRPM();
ApplyTorque();
}
void UpdateEngineRPM()
{
float expectedWheelRPM = engineRPM / gearRatios[currentGear];
float actualWheelRPM = GetAverageWheelRPM();
float rpmDifference = expectedWheelRPM - actualWheelRPM;
float targetRPM = throttlePressed ? maxRPM : idleRPM;
float baseRPMChange = throttlePressed ? engineRPM_IncreaseRate : engineRPM_DecreaseRate;
float effectiveRPMChange = baseRPMChange;
if (throttlePressed && rpmDifference > rpmMatchThreshold)
{
float loadFactor = Mathf.Clamp01((rpmDifference - rpmMatchThreshold) / rpmStopThreshold);
effectiveRPMChange *= (1f - loadFactor * rpmMatchStrength);
if (rpmDifference > rpmStopThreshold)
{
effectiveRPMChange = 0f;
}
}
else if (rpmDifference < -rpmMatchThreshold)
{
float pullRate = 500f;
engineRPM += pullRate * Time.fixedDeltaTime;
}
engineRPM = Mathf.MoveTowards(engineRPM, targetRPM, effectiveRPMChange * Time.fixedDeltaTime);
engineRPM = Mathf.Clamp(engineRPM, idleRPM, maxRPM);
}
void ApplyTorque()
{
if (!throttlePressed || currentGear < 0)
{
foreach (WheelCollider wheel in driveWheels)
{
wheel.motorTorque = 0f;
wheel.brakeTorque = 50f;
}
return;
}
float expectedWheelRPM = engineRPM / gearRatios[currentGear];
float actualWheelRPM = GetAverageWheelRPM();
float rpmDifference = expectedWheelRPM - actualWheelRPM;
if (rpmDifference < -rpmMatchThreshold)
{
foreach (WheelCollider wheel in driveWheels)
{
wheel.motorTorque = 0f;
wheel.brakeTorque = 100f;
}
return;
}
float normalizedRPM = (engineRPM - idleRPM) / (maxRPM - idleRPM);
float torqueMultiplier = torqueCurve.Evaluate(normalizedRPM);
float engineTorque = maxTorque * torqueMultiplier;
float wheelTorque = engineTorque * gearRatios[currentGear];
if (rpmDifference < rpmMatchThreshold)
{
float torqueReduction = Mathf.Clamp01(rpmDifference / rpmMatchThreshold);
wheelTorque *= torqueReduction;
}
float torquePerWheel = wheelTorque / driveWheels.Length;
foreach (WheelCollider wheel in driveWheels)
{
if (wheel.isGrounded)
{
wheel.motorTorque = torquePerWheel;
wheel.brakeTorque = 0f;
}
}
}
float GetAverageWheelRPM()
{
float totalRPM = 0f;
int count = 0;
foreach (WheelCollider wheel in driveWheels)
{
if (wheel.isGrounded)
{
totalRPM += Mathf.Abs(wheel.rpm);
count++;
}
}
return count > 0 ? totalRPM / count : 0f;
}
} |
Beta Was this translation helpful? Give feedback.
-
|
That’s what would be ideal. I like this implementation, it’s more realistic and not too complicated for Unturned. This feels like a good middle ground between almost simulated engines and what we have now. A clutch for example would be overdoing it. |
Beta Was this translation helpful? Give feedback.
-
|
One more thing I forgot to add, a new condition for the gearbox. If wheels are slipping above a certain threshold; prevent the gearbox from switching gears. Or if the rpm of powered wheels differs too much from each other, do the same. It should only switch gears if the engine rpm is above the upShift rpm and wheels arent spinning/slipping freely. This helps vehicles when cornering or going uphill if they have moderate body roll characteristics. My car for example will roll so much that the inner wheels will almost always rotate freely in corners and reach their max engine rpm. This would cause the gearbox to upshift even tho before the corner I was barely at 3k rpm and my speed decreased while taking the turn. |
Beta Was this translation helpful? Give feedback.
-
|
Sorry for my delay, and thanks for the additional input. I knew I'd need to put on my thinking cap for this, so caught up on other needed to-dos this week first. It's interesting that WheelHit includes slip values, will need to add a debug visual for those. I think I'll start by experimenting with the RPM adjustment and shifting restrictions based on wheel slip status. 🤔 |
Beta Was this translation helpful? Give feedback.
-
|
Today's preview update merges a bunch of related features! In testing I found that reducing torque when expected and actual RPM mismatch reduced the wheel slip the most, but I'm definitely out of my element and I'm curious to hear your findings if you get the chance to try these out and are willing to share them. New options are:
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Improving Unturned's Vehicle Physics: A Proposal for Realistic Gearbox Behavior
After five years focused purely on commissions, I finally took a break to work on personal projects. One of these was recreating my childhood car in Unturned with meticulous attention to detail—plugin-less, unity-only brake lights, reverse lights, rain particles, multiple beam settings, and most importantly, realistic handling. The vehicle features accurate weight distribution (1000kg with a forward center of gravity) and wheel collider spring/damper values that mirror real-life characteristics.
The Current System
The gearbox system implemented in the Carr update works adequately for simple vehicles with long gear ratios and 2-3 gears. However, it falls short when creating specialized setups due to a fundamental issue: vehicle speed is tied to torque rather than wheel RPM.
Currently, torque is linked to engine RPM and directly influences acceleration. While this gets the job done for basic vehicles, it creates problems for more sophisticated implementations.
The Problem
Because vehicle speed depends on acceleration, which depends on torque, the system produces unrealistic behavior:
How Real Gearboxes Work
In reality, the gearbox connects the engine's rotational force (RPM) to the wheels through a set of gears that multiply this force. The transmitted torque then rotates the wheels and propels the vehicle forward.
The Role of Differentials
Most cars use an open differential, which connects two wheels while allowing them to rotate at different speeds. Here's a practical example:
Scenario: Your engine is at 4000 RPM (out of 7000 max), and your left wheel loses traction.
In the current system, the torque curve mitigates this: with maximum torque set to zero at max RPM, power is removed from the out-of-control wheel, allowing it to slow down and regain grip. Once both wheels grip again, the engine returns to lower RPM where torque is higher, and acceleration resumes.
A locked differential prevents this scenario by keeping both wheels locked together at the same RPM. The same torque curve and power loss dynamics apply if both wheels slip simultaneously.
ABS/ESP Systems
Modern vehicles use wheel speed sensors to detect slipping. The system monitors each wheel's RPM, factors in the steering angle (outer wheels naturally spin faster during cornering), and applies braking force to any wheel spinning faster than it should.
The Proposed Solution
Core Mechanic Changes
Tie vehicle speed to the average RPM of grounded, powered wheels. This prevents issues where a single wheel momentarily lifting would cause the entire vehicle speed to drop to zero, while still maintaining realistic behavior during actual wheel slip.
Gear ratio becomes the multiplier between engine RPM and wheel RPM.
Torque remains the acceleration force—the force the engine exerts on wheels to increase their RPM. The torque curve should continue to function as it does now, with higher curve values producing faster acceleration.
Handling Wheel Slip: A Hybrid Approach
The key to realistic behavior is implementing a progressive slip system that smoothly transitions between grip and loss of traction:
Slip Detection and Calculation
For each powered wheel, the system calculates:
Traction Loss Multiplier
When the slip ratio exceeds a threshold (e.g., 10%), apply a traction loss multiplier to that wheel's torque contribution:
Vehicle Speed Calculation
Why This Works
Smooth degradation: Wheels progressively lose effectiveness as they slip, rather than instantly cutting out
Handles momentary lift: Brief air time won't kill your speed since other wheels are still factored in
Realistic burnouts: If you dump the clutch with too much power, all wheels can spin and you go nowhere
Surface variation: Works naturally with different traction surfaces—wheels on ice slip more than wheels on asphalt
Prevents false speed readings: Spinning wheels contribute less to the speed calculation, preventing the game from thinking you're going faster than you are
Benefits of This System
Combined with the existing engine RPM increase and decrease speeds, this system would provide perfect control over vehicle acceleration and top speed, enabling truly specialized vehicle configurations:
Trucks: Very high torque, short engine RPM range, slow RPM increase rate = powerful but slow to accelerate. Great pulling power with realistic wheel spin management under heavy loads.
Modern Tanks (e.g., Abrams): Even higher torque, 10+, very short forward gears, slow acceleration, decent top speed. The slip system prevents unrealistic instant acceleration despite massive torque.
F1 Cars: The current vanilla system actually works well for these, but the new system would add realistic wheel spin management on launch and coming out of corners, requiring proper throttle control.
Everyday Cars: Good top speed with 5-6 gears, but gradual acceleration to reach that speed. Wheel slip during aggressive starts feels natural rather than instant.
Old Cars: Slow acceleration due to clamped engine RPM increase rate, but sufficient power to eventually reach top speed. Lower-powered engines mean less wheel slip to manage.
Rally Cars: Faster acceleration than regular cars with similar top speeds. The slip system truly shines here—allowing for controlled slides and power management on loose surfaces.
Additional Suggestions and Considerations for Enhanced Vehicle Systems
Multiple Reverse Gear Ratios
Current Limitation
The existing system uses a single reverse gear ratio, forcing a one-size-fits-all approach that doesn't reflect how different vehicles handle in reverse.
Proposed Solution
Implement multiple reverse gear ratios following the same system as forward gears. Certain use cases such as military vehicles or trucks due to their nature of having short gears and high torque, multiple gears are needed to get them up to their desired top reverse speed. In our case, just a single reverse gear ration would instantly properl a high torque vehicle to it's max reverse speed.
Implementation: Reverse gears are separate from forward gear count, with visual indicators showing "R1," "R2," etc., and distinct audio for reverse gear changes.
Gear Change Audio Clips
Current State
Gear changes are currently silent, removing important feedback and immersion.
Proposed Solution
Add customizable gear change audio clips with parameters for volume, pitch variation, and separate sounds for upshifts, downshifts, and reverse engagement:
Different vehicle types would have characteristic sounds: heavy trucks with deep mechanical clunks, dual-clutch transmissions with sharp clicks, and vintage cars with grindier engagement noises. Pitch randomization prevents repetitive audio, and volume scales with engine RPM.
Idle Volume System for Engine Audio
Current Limitation
Engine audio plays at consistent volume regardless of throttle input, making vehicles sound constantly under full throttle even when coasting.
Proposed Solution
Implement dynamic volume mixing based on throttle input:
When throttle is released, volume smoothly transitions to IdleVolume, allowing environmental audio to come through and reducing audio fatigue. When throttle is pressed, volume scales with input percentage, reaching MaxVolume at max engine rpm. This enables stealth gameplay, improves tactical awareness (distinguishing accelerating vehicles from coasting ones), and creates more realistic urban driving where engines quiet between stoplights.
Vehicle Event Hook Gearbox Event Functions
Current Limitation
The existing vehicle event hook script lacks specific callbacks for gearbox state changes, limiting the ability to create dynamic responses to gear transitions and transmission states.
Proposed Solution
Expand the vehicle event hook script with the following functions:
Use Cases and Benefits
OnUpshift/OnDownshift: Trigger particle effects (transmission heat, exhaust pops), activate shift lights on dashboard, apply brief camera shake for mechanical feedback, or trigger custom audio beyond the standard gear change sound.
OnReverseGearActive: Activate reverse lights and backup beepers, enable rear-view camera displays, apply different handling characteristics, or trigger warnings for nearby players.
OnNeutralActive: Idk but it would be good to have just in case lol
OnForwardGearActive: Reset reverse-specific effects, re-enable forward driving assists, update UI gear indicators with specific gear number, or apply gear-specific visual effects (low gear = more exhaust smoke).
Implementation Benefits
These hooks enable modders and creators to build sophisticated vehicle behaviors: automatic transmission logic, racing-style shift indicators, realistic wear-and-tear systems that respond to driving style, and immersive cockpit interactions. Combined with the improved gearbox physics, these events provide complete control over vehicle behavior and presentation.
Conclusion
This change would elevate the gearbox system to the next level, allowing it to serve its actual purpose and enabling creators to build truly use-case-specific gearboxes with precise control over both top speed and acceleration for different vehicle types. The progressive slip system ensures realistic behavior across all driving conditions while maintaining playability and avoiding frustrating edge cases.
These extra features significantly enhance vehicle immersion: multiple reverse gears enable proper vehicle specialization, gear change audio provides crucial performance feedback, and dynamic idle volume creates a more realistic audio environment. Combined with the core gearbox improvements, these create a comprehensive vehicle physics system accessible to all players.
This took a little over 4 hours to type out, format and organize. Some parts have been re-formatted with Claude.Ai for more clarity, consistency and accuracy, especially the part about handling wheel slip. I've been a player of Unturned since 2015 and a modder since 2016. I dont regret the time spent typing this out as I want to see the game grow and improve.
Beta Was this translation helpful? Give feedback.
All reactions