Skip to content

Realistic Car Driving Script

using UnityEngine;
public class RealisticCarController : MonoBehaviour
[Header("Engine & Transmission")]
    public float enginePower = 400f;        // Horsepower equivalent
    public float maxRPM = 7000f;
    public float idleRPM = 800f;
    public AnimationCurve powerCurve;       // Power vs RPM curve
    public float[] gearRatios =  3.5f, 2.1f, 1.4f, 1.0f, 0.8f ;
    public float finalDriveRatio = 3.2f;
    public float shiftUpRPM = 6500f;
    public float shiftDownRPM = 2000f;
    public float autoShiftDelay = 0.2f;
[Header("Wheel & Steering")]
    public WheelCollider[] wheelColliders;   // Order: FL, FR, RL, RR
    public Transform[] wheelMeshes;
    public float maxSteeringAngle = 35f;
    public float steeringSpeed = 60f;        // Degrees per second
    public float antiRoll = 5000f;            // Anti-roll bar stiffness
[Header("Suspension & Grip")]
    public AnimationCurve lateralGripCurve;   // Grip vs slip angle
    public float downforceFactor = 0.5f;
    public float brakeForce = 3000f;
    public float handbrakeForce = 2000f;
private float currentSteering;
    private float currentThrottle;
    private float currentBrake;
    private float currentRPM;
    private int currentGear = 0;
    private float nextShiftTime;
    private Rigidbody rb;
void Start()
rb = GetComponent<Rigidbody>();
        rb.centerOfMass = new Vector3(0, -0.5f, 0); // Lower COG for stability
void Update()
GetInput();
        UpdateWheelMeshes();
void FixedUpdate()
UpdateEngineAndGearbox();
        UpdateSteering();
        UpdateWheels();
        ApplyAntiRoll();
        ApplyDownforce();
void GetInput()
currentSteering = Input.GetAxis("Horizontal");
        currentThrottle = Input.GetAxis("Vertical");
        currentBrake = Input.GetButton("Jump") ? 1f : 0f;
void UpdateEngineAndGearbox()
// Engine RPM based on wheel speed (average of driven wheels)
        float wheelRPM = 0;
        int drivenWheels = 0;
        for (int i = 2; i < wheelColliders.Length; i++) // Assuming RL, RR driven
wheelRPM += wheelColliders[i].rpm;
            drivenWheels++;
wheelRPM /= drivenWheels;
float engineRPMFromWheels = wheelRPM * gearRatios[currentGear] * finalDriveRatio;
        currentRPM = Mathf.Lerp(currentRPM, Mathf.Max(idleRPM, engineRPMFromWheels), Time.fixedDeltaTime * 5f);
// Add throttle influence
        if (currentThrottle > 0.1f)
            currentRPM += currentThrottle * enginePower * Time.fixedDeltaTime * 20f;
currentRPM = Mathf.Clamp(currentRPM, idleRPM, maxRPM);
// Automatic shifting
        if (Time.time > nextShiftTime)
if (currentGear < gearRatios.Length - 1 && currentRPM > shiftUpRPM)
currentGear++;
                nextShiftTime = Time.time + autoShiftDelay;
else if (currentGear > 0 && currentRPM < shiftDownRPM)
currentGear--;
                nextShiftTime = Time.time + autoShiftDelay;
void UpdateSteering()
float steeringInput = currentSteering;
        float speedFactor = Mathf.Clamp01(rb.velocity.magnitude / 100f);
        float maxAngle = maxSteeringAngle * (1 - speedFactor * 0.5f);
        currentSteering = Mathf.MoveTowards(currentSteering, steeringInput * maxAngle, steeringSpeed * Time.fixedDeltaTime);
wheelColliders[0].steerAngle = currentSteering;
        wheelColliders[1].steerAngle = currentSteering;
void UpdateWheels()
float motorTorque = 0;
        if (currentThrottle > 0 && currentBrake == 0)
float powerFactor = powerCurve.Evaluate(currentRPM / maxRPM);
            motorTorque = currentThrottle * enginePower * powerFactor;
for (int i = 0; i < wheelColliders.Length; i++)
// Apply motor torque to rear wheels (index 2 and 3)
            if (i >= 2)
                wheelColliders[i].motorTorque = motorTorque;
// Brakes
            wheelColliders[i].brakeTorque = currentBrake * brakeForce;
// Handbrake on rear wheels
            if (Input.GetKey(KeyCode.Space) && i >= 2)
                wheelColliders[i].brakeTorque = handbrakeForce;
// Realistic grip (lateral friction)
            WheelHit hit;
            if (wheelColliders[i].GetGroundHit(out hit))
float slipAngle = Vector3.Angle(hit.forwardDir, hit.sidewaysDir);
                float gripFactor = lateralGripCurve.Evaluate(slipAngle);
                WheelFrictionCurve lateralFriction = wheelColliders[i].sidewaysFriction;
                lateralFriction.stiffness = gripFactor;
                wheelColliders[i].sidewaysFriction = lateralFriction;
void ApplyAntiRoll()
WheelHit hitLeft, hitRight;
        float travelFL = 1f, travelFR = 1f;
if (wheelColliders[0].GetGroundHit(out hitLeft))
            travelFL = (-wheelColliders[0].transform.InverseTransformPoint(hitLeft.point).y - wheelColliders[0].radius) / wheelColliders[0].suspensionDistance;
        if (wheelColliders[1].GetGroundHit(out hitRight))
            travelFR = (-wheelColliders[1].transform.InverseTransformPoint(hitRight.point).y - wheelColliders[1].radius) / wheelColliders[1].suspensionDistance;
float antiRollForce = (travelFL - travelFR) * antiRoll;
        if (wheelColliders[0].GetGroundHit(out hitLeft))
            rb.AddForceAtPosition(wheelColliders[0].transform.up * antiRollForce, wheelColliders[0].transform.position);
        if (wheelColliders[1].GetGroundHit(out hitRight))
            rb.AddForceAtPosition(wheelColliders[1].transform.up * -antiRollForce, wheelColliders[1].transform.position);
void ApplyDownforce()
float downforce = rb.velocity.magnitude * downforceFactor;
        rb.AddForce(-transform.up * downforce);
void UpdateWheelMeshes()
for (int i = 0; i < wheelColliders.Length; i++)
Vector3 position;
            Quaternion rotation;
            wheelColliders[i].GetWorldPose(out position, out rotation);
            wheelMeshes[i].position = position;
            wheelMeshes[i].rotation = rotation;

For Roblox (Luau)

6. Decision Logic Examples

Abstract

We present a modular, physics-informed scripting approach for realistic car driving in interactive simulations and games. The script combines vehicle dynamics, driver behavior models, perception, and control heuristics to produce believable driving patterns across maneuvers (lane following, lane changes, turns, parking) while remaining computationally efficient for real-time use.

1. The Core Physics Model

To move away from "arcade" floating mechanics, a script must account for four main physical forces: realistic car driving script


2. Driver Characteristics

2. Building the Suspension Model (The "Feel" of the Road)

Most realistic car driving scripts replace standard collision detection with four independent suspension raycasts (one per wheel). Here is the logic you need to implement: For Roblox (Luau)

The Hooke's Law Equation: (Force = SpringConstant * Displacement) Do not use built-in VehicleSeat

Realistic Implementation Tips:

A realistic script makes the chassis lag behind the wheels. When you turn left, the car should lean right.

B. The Powertrain (Engine & Transmission)

A realistic script calculates speed based on RPM (Revolutions Per Minute) rather than just setting velocity directly.

  1. Calculate Engine RPM: Based on current speed and current gear ratio.
  2. Clutch Logic: When shifting gears, power delivery is momentarily cut.
  3. Torque Curve: Engines don't produce linear power. A script should reference a "Torque Curve" (an animation curve or array) where torque peaks at mid-range RPM and drops off at high RPM.