Unity Drift Simulator Tutorial

After posting a work in progress youtube video about my game “Drift Quest” I had a lot of requests on how to do a car drifting simulator.Unfortunately since I’m very busy in real life making a living I don’t have enough time to polish the game, this is why I’ve decided to write this tutorial which will cover the important bits needed in order to have a fully functioning car with drift flavor, heads up before we begin that this tutorial will give you an arcade sim like vehicle dynamics which will be derived from Unity’s Car Tutorial ~Alternatephysicsmodel portion.

In this tutorial you will be introduced on how to implement a quick sim-cade style drift game with only manual transmission, before we dive any deeper it is recommend that you have average knowledge about Unity Game Engine and C# programming language.

The outcome of this tutorial should give you a similar car drifting physics to my work in progress game which you can see here:

 


TABLE OF CONTENT

  1. Project Setup.

  2. Assets Import.

  3. Scene/Map creation.

  4. Vehicle Model Setup.

  5. Camera.

  6. Vehicle Colliders.

  7. Wheel Module.

  8. Car Controller.

  9. Drivetrain.

  10. Voice Controller.

  11. Anti Roll Bar / Aero Dynamic Resistance / Traction Helper.

  12. Extra Features.

Without any further due, let’s begin:

  • Project Setup.

    For this tutorial I’m running with the following specs:
    Windows 7 – 64 bit
    Intel Core i7-870 @2.93 GHz
    GeForce Nvidia 950 GTX
    16 GB RAM DDR3
    512 GB SAMSUNG SSD
    1 TB HDD

  • Softwares that you may need (some are optional):
    3Ds Max/Sketchup/Blender
    Photoshop/Paint/GIMP
    Audacity
    Unity 5.4.1f1 (64-bit) – Personal Edition
    ——————————————————–
    To begin with, open up Unity and create a new project, you don’t need to add any asset package, we will download them from the asset store directly from Unity:
    Unity - Create Project - Drift Simulator Tutorial
    To be more organized we will create the basic folders hierarchy where we will be storing all of our assets, we may create more as we progress:
    Project Structure


  • Assets Import.

    The following assets will be required for this tutorial:

    • Vehicle Model from Unity Car Tutorial.
    • Track Model. (Optional)

    Unity’s Car Tutorial

    Only import the PhysicsMaterial and the ~AlternatePhysicsModel portion of the asset as shown below:

    After you import it, you’re likely to get errors that needs to be fixed as you will be prompted with a dialog of “API Update Required”, simply hit “I made a backup , Go ahead!” and it should fix these errors, once everything is clear we move on to the next step.

    You’re free to select whatever vehicle model you would like to use in this tutorial, I will use the AE86 Trueno from Sketchup Warehouse by D3NKE:

    https://3dwarehouse.sketchup.com/model/1ff47bd0854deee3283ffcfc40c29975/Toyota-AE86-Trueno-Initial-D

    For the track, you can either search online for tracks or build your own, one of the websites I remember is a Russian website called https://gamemodels.ru/ you can find plenty of models for instance Autumn Ring from Gran Turismo 4 https://gamemodels.ru/files/file/2446-autumn-ring/ , which I’m going to use in my tutorial.

  • Scene/Map creation.

    You have two choices, either use the track you’ve downloaded and add Mesh Collider to it and you most likely will be done OR create a terrain and add things to it, I will be explaining the first choice but you can pretty much download drift tracks online.

    – Import the track and place it in your models folder under a new folder called tracks, if the Russian website is down here’s an alternative download link as a unity package (43.67 MB):
    https://www.mediafire.com/?ypc6jpc514j9e8d

    – In your scene, in the hierarchy add an empty gameobject and call it “track“.
    – Under AutumnRing folder drag AutumnRing into the hierarchy to be a child of “Track” gameobject.
    – To fix the trees and remove the black box click on any black boxed tree and under Shader change the Rendering Mode to Cutout or Fade, this will affect the rest of the trees.
    – Add mesh colliders to every game object you want the car to be able to collide into, mainly the ground and the road as well any other obstacle, you will also need to add the track collider material to the mesh collider and other objects.
    – You may adjust the lighting as well add a skybox to make the map more lively, lighting by itself is a dense subject as it can heavily impact performance, it is best that you keep it in mind if you wish to publish your game on mobile devices.
    – Save the scene as Autumn Ring to preserve your work.

  • Vehicle Model Setup.

    Setting up the vehicle might seem confusing at the beginning however if you follow the steps I include, you will find it quite easy to do for every future vehicle.

    – The first step is to create an empty game object, call it AE86 or your vehicle’s name.
    – Click on AE86 and  create another empty game object under it and name it “Vehicle“, make sure the position is set to zero.
    – Attach to “Vehicle” a rigidbody component with mass 950 and angular drag of 0.
    – Drag the vehicle model to be under “Vehicle“, adjust and scale it to your desired level but before you do so if you’re using the AE86 from the link I’ve provided then you may want to center the vehicle using sketchup, here’s a link a neat plugin that just does the job:

    https://extensions.sketchup.com/en/content/clf-center-origin

    – Set the model position and rotation values to zero.
    – Scale the car in accordance to the wheels radius, the radius I’m using for the AE86 is 0.31 , you can use the built in wheel collider as a measurement tool.

     

  • Camera.

    Generally when working with Cameras you’re free to experiment and implement your own version, there are no restrictions on this one, I will just give you a quick and dirty way to implement one that is similar to the DQ video.

    Create a c# script and name it SmoothFollow , clear it and paste the following script:

    using UnityEngine;
    using System.Collections;
    
    public class SmoothFollow : MonoBehaviour {
      public Transform target;
      public float distance = 1.6f;
      public float height = 0.4f;
      public float damping = 0.7f;
      public bool smoothRotation = true;
      public bool followBehind = true;
      public float rotationDamping = 50.0f;
    
      void FixedUpdate () {
      
        Vector3 wantedPosition;
        if(followBehind)
          wantedPosition = target.TransformPoint(0, height, -distance);
        else
          wantedPosition = target.TransformPoint(0, height, distance);
        
        transform.position = Vector3.Lerp (transform.position, wantedPosition,  damping);
    
        if (smoothRotation) {
          Quaternion wantedRotation = Quaternion.LookRotation(target.position - transform.position, target.up);
          transform.rotation = Quaternion.Slerp (transform.rotation, wantedRotation, Time.deltaTime * rotationDamping);
        }
        else transform.LookAt (target, target.up);
      }
    }

     

    – Create a game object “Main Camera” and attach the SmoothFollow script to it.
    – Move the “Main Camera” object to be a child of AE86.
    – Modify the Main Camera SmoothFollow values to:
    Distance 1.6
    Height 0.4
    Damping 0.7
    Smooth Rotation Ticked
    Follow Behind Ticked
    Rotation Damping 50
    – Under vehicle create another transform object and call it “Camera Follow”, this is what the main camera will be following, position this transform object behind the car.
    – Click on the Main Camera and drag the Camera Follow game object into the script target.

  • Vehicle Colliders.

    In order to simulate collision you will need to add colliders to your vehicle, create an empty gameobject called colliders and place it under vehicle, add box collider and attach a car metal material, resize it as you see fit to cover the car boundaries such as the following:

  • Wheel Module.

    For each wheel you will need to create a an empty transform gameobject in place of each tire and rename them to tire location respectively such as FR/FL/RR/RL, for each gameobject placement you can use the model parts transform values, for the sake of organization place them all under a new gameobject called “Wheels”.

    Now for each gameobject you will attach the wheel script and drag the model object representing the tire under that gameobject as the following including the values, just a slight note that this step is very adhoc so there might be some slight changes you may require such as increasing the wheel radius if the tires start “sinking” too much, you can use the built in wheel collider as a measurement tool, if you wish for a more easier drifting experience you may lower the grip value to 1 or to your desired level:

    Front Wheels

    Rear Wheels

    rear wheels

  • Car Controller with Drift Setup.

    From this point you will be adding additional scripts to the “Vehicle” gameobject where you will be controlling what the car does, the car controller script as the name suggests is responsible for controlling various vehicle operations such as car throttle, braking, gear change, clutch…etc.

    The next step here is to attach the car controller script to the vehicle object, this will also automatically attach the drivetrain script, update the car controller script with the below version:

    using UnityEngine;
    using UnityEngine.UI;
    using System.Collections;
    
    
    // This class is repsonsible for controlling inputs to the car.
    [RequireComponent(typeof(Drivetrain))]
    public class CarController : MonoBehaviour {
    
        // Add all wheels of the car here, so brake and steering forces can be applied to them.
        public Wheel[] wheels;
    
        // A transform object which marks the car's center of gravity.
        // Cars with a higher CoG tend to tilt more in corners.
        // The further the CoG is towards the rear of the car, the more the car tends to oversteer. 
        // If this is not set, the center of mass is calculated from the colliders.
        public Transform centerOfMass;
    
        // A factor applied to the car's inertia tensor. 
        // Unity calculates the inertia tensor based on the car's collider shape.
        // This factor lets you scale the tensor, in order to make the car more or less dynamic.
        // A higher inertia makes the car change direction slower, which can make it easier to respond to.
        public float inertiaFactor = 1.5f;
    
        // current input state
        [HideInInspector]
        public float brake;
        [HideInInspector]
        float throttle;
        float throttleInput;
        float clutch;
        [HideInInspector]
        public float steering;
        float lastShiftTime = -1;
        [HideInInspector]
        public float handbrake;
    
        // cached Drivetrain reference
        Drivetrain drivetrain;
    
        // How long the car takes to shift gears
        public float shiftSpeed = 0.8f;
    
    
        // These values determine how fast throttle value is changed when the accelerate keys are pressed or released.
        // Getting these right is important to make the car controllable, as keyboard input does not allow analogue input.
        // There are different values for when the wheels have full traction and when there are spinning, to implement 
        // traction control schemes.
    
        // How long it takes to fully engage the throttle
        public float throttleTime = 1.0f;
        // How long it takes to fully engage the throttle 
        // when the wheels are spinning (and traction control is disabled)
        public float throttleTimeTraction = 10.0f;
        // How long it takes to fully release the throttle
        public float throttleReleaseTime = 0.5f;
        // How long it takes to fully release the throttle 
        // when the wheels are spinning.
        public float throttleReleaseTimeTraction = 0.1f;
    
        // Turn traction control on or off
        public bool tractionControl = false;
    
        // Turn ABS control on or off
        public bool absControl = false;
    
        // These values determine how fast steering value is changed when the steering keys are pressed or released.
        // Getting these right is important to make the car controllable, as keyboard input does not allow analogue input.
    
        // How long it takes to fully turn the steering wheel from center to full lock
        public float steerTime = 1.2f;
        // This is added to steerTime per m/s of velocity, so steering is slower when the car is moving faster.
        public float veloSteerTime = 0.1f;
    
        // How long it takes to fully turn the steering wheel from full lock to center
        public float steerReleaseTime = 0.6f;
        // This is added to steerReleaseTime per m/s of velocity, so steering is slower when the car is moving faster.
        public float veloSteerReleaseTime = 0f;
        // When detecting a situation where the player tries to counter steer to correct an oversteer situation,
        // steering speed will be multiplied by the difference between optimal and current steering times this 
        // factor, to make the correction easier.
        public float steerCorrectionFactor = 4.0f;
    
    
        private bool gearShifted = false;
        private bool gearShiftedFlag = false;
    
        // Used by SoundController to get average slip velo of all wheels for skid sounds.
        public float slipVelo
        {
            get
            {
                float val = 0.0f;
                foreach (Wheel w in wheels)
                    val += w.slipVelo / wheels.Length;
                return val;
            }
    
        }
    
        // Initialize
        void Start() {
            if (centerOfMass != null)
                GetComponent<Rigidbody>().centerOfMass = centerOfMass.localPosition;
    
            GetComponent<Rigidbody>().inertiaTensor *= inertiaFactor;
            drivetrain = GetComponent(typeof(Drivetrain)) as Drivetrain;
        }
    
        void Update() {
    
            // Steering
            Vector3 carDir = transform.forward;
            float fVelo = GetComponent<Rigidbody>().velocity.magnitude;
            Vector3 veloDir = GetComponent<Rigidbody>().velocity * (1 / fVelo);
            float angle = -Mathf.Asin(Mathf.Clamp(Vector3.Cross(veloDir, carDir).y, -1, 1));
            float optimalSteering = angle / (wheels[0].maxSteeringAngle * Mathf.Deg2Rad);
            if (fVelo < 1)
                optimalSteering = 0;
    
            float steerInput = 0;
            if (Input.GetKey(KeyCode.LeftArrow))
                steerInput = -1;
            if (Input.GetKey(KeyCode.RightArrow))
                steerInput = 1;
    
            if (steerInput < steering) {
                float steerSpeed = (steering > 0) ? (1 / (steerReleaseTime + veloSteerReleaseTime * fVelo)) : (1 / (steerTime + veloSteerTime * fVelo));
                if (steering > optimalSteering)
                    steerSpeed *= 1 + (steering - optimalSteering) * steerCorrectionFactor;
                steering -= steerSpeed * Time.deltaTime;
                if (steerInput > steering)
                    steering = steerInput;
            } else if (steerInput > steering) {
                float steerSpeed = (steering < 0) ? (1 / (steerReleaseTime + veloSteerReleaseTime * fVelo)) : (1 / (steerTime + veloSteerTime * fVelo));
                if (steering < optimalSteering)
                    steerSpeed *= 1 + (optimalSteering - steering) * steerCorrectionFactor;
                steering += steerSpeed * Time.deltaTime;
                if (steerInput < steering)
                    steering = steerInput;
            }
    
    
            bool accelKey = Input.GetKey(KeyCode.UpArrow);
            bool brakeKey = Input.GetKey(KeyCode.DownArrow);
    
            if (drivetrain.automatic && drivetrain.gear == 0) {
                accelKey = Input.GetKey(KeyCode.DownArrow);
                brakeKey = Input.GetKey(KeyCode.UpArrow);
            }
    
            if (Input.GetKey(KeyCode.LeftShift)) {
                throttle += Time.deltaTime / throttleTime;
                throttleInput += Time.deltaTime / throttleTime;
            } else if (accelKey) {
    
                if (drivetrain.slipRatio < 0.10f)
                    throttle += Time.deltaTime / throttleTime;
                else if (!tractionControl)
                    throttle += Time.deltaTime / throttleTimeTraction;
                else
                    throttle -= Time.deltaTime / throttleReleaseTime;
    
                if (throttleInput < 0)
                    throttleInput = 0;
                throttleInput += Time.deltaTime / throttleTime;
            } else {
                if (drivetrain.slipRatio < 0.2f)
                    throttle -= Time.deltaTime / throttleReleaseTime;
                else
                    throttle -= Time.deltaTime / throttleReleaseTimeTraction;
            }
    
            throttle = Mathf.Clamp01(throttle);
    
            if (brakeKey) {
                if (drivetrain.slipRatio < 0.2f)
                    brake += Time.deltaTime / throttleTime;
                else
                    brake += Time.deltaTime / throttleTimeTraction;
                throttle = 0;
                throttleInput -= Time.deltaTime / throttleTime;
            } else {
                if (drivetrain.slipRatio < 0.2f)
                    brake -= Time.deltaTime / throttleReleaseTime;
                else
                    brake -= Time.deltaTime / throttleReleaseTimeTraction;
            }
    
            brake = Mathf.Clamp01(brake);
            throttleInput = Mathf.Clamp(throttleInput, -1, 1);
    
            // Handbrake
            handbrake = (Input.GetKey(KeyCode.Space) || Input.GetKey(KeyCode.JoystickButton2)) ? 1f : 0f;
    
            // Gear shifting
            float shiftThrottleFactor = Mathf.Clamp01((Time.time - lastShiftTime) / shiftSpeed);
    
            if (drivetrain.gear == 0 && Input.GetKey(KeyCode.UpArrow)) {
                throttle = 0.4f;// Anti reverse lock thingy??
            }
            
            if (drivetrain.gear == 0)
                drivetrain.throttle = Input.GetKey(KeyCode.UpArrow) ? throttle : 0f;
            else
                drivetrain.throttle = Input.GetKey(KeyCode.UpArrow) ? (tractionControl ? throttle : 1) * shiftThrottleFactor : 0f;
    
            drivetrain.throttleInput = throttleInput;
    
            if (Input.GetKeyDown(KeyCode.A)) {
                lastShiftTime = Time.time;
                drivetrain.ShiftUp();
            }
    
            if (Input.GetKeyDown(KeyCode.Z)) {
                lastShiftTime = Time.time;
                drivetrain.ShiftDown();
            }
    
            //play gear shift sound
            if (gearShifted && gearShiftedFlag && drivetrain.gear != 1) {
                GetComponent<SoundController>().playShiftUp();
                gearShifted = false;
                gearShiftedFlag = false;
            }
    
    
            // ABS Trigger (This prototype version is used to prevent wheel lock , currently expiremental)
            if (absControl)
                brake -= brake >= 0.1f ? 0.1f : 0f;
    
            // Apply inputs
            foreach (Wheel w in wheels) {
                w.brake = Input.GetKey(KeyCode.DownArrow) ? brake :  0;
                w.handbrake = handbrake;
                w.steering = steering;
            }
    
            // Reset Car position and rotation in case it rolls over
            if (Input.GetKeyDown(KeyCode.R)) {
                transform.position = new Vector3(transform.position.x, transform.position.y + 2f, transform.position.z);
                transform.rotation = Quaternion.Euler(0, transform.localRotation.y, 0);
            }
    
    
            // Traction Control Toggle
            if (Input.GetKeyDown(KeyCode.T)) {
    
                if (tractionControl) {
                    tractionControl = false;
                } else {
                    tractionControl = true;
                }
            }
    
            // Anti-Brake Lock Toggle
            if (Input.GetKeyDown(KeyCode.B)) {
                if (absControl) {
                    absControl = false;
                } else {
                    absControl = true;
                }
            }
        }
    
        // Debug GUI. Disable when not needed.
        void OnGUI() {
            GUI.Label(new Rect(0, 60, 100, 200), "km/h: " + GetComponent<Rigidbody>().velocity.magnitude * 3.6f);
            tractionControl = GUI.Toggle(new Rect(0, 80, 300, 20), tractionControl, "Traction Control (bypassed by shift key)");
        }
    }
    

    Once all done updating, tweak the following parameters in the car controller script:

    – Attach all 4 wheels to the wheels array.
    – Create a new gameobject under vehicle called CoG, this is your center of gravity transform object which will impact how your vehicle will behave during weight transitions, center it just on top of the gear leaver and tweak it later as desired, lastly attach it to the center of gravity parameter.
    – Tweak the rest of the values as following:

  • Drivetrain.

    The drivetrain script is responsible for the car engine’s operation and delivering power to the wheels, you can tweak it to deliver power for all wheels or only selected wheels, for the AE86 we will be tweaking it to deliver power to the rear wheels, each layout has its own advantages and disadvantages so its up to you which kind of drifting vehicle you wanna build.

    Update the drivetrain script with this modified version that will allow for an improved power delivery:

    using UnityEngine;
    using UnityEngine.UI;
    using System.Collections;
    
    // This class simulates a car's engine and drivetrain, generating
    // torque, and applying the torque to the wheels.
    public class Drivetrain : MonoBehaviour {
    
        // All the wheels the drivetrain should power
        public Wheel[] poweredWheels;
    
        // The gear ratios, including neutral (0) and reverse (negative) gears
        public float[] gearRatios;
    
        // The final drive ratio, which is multiplied to each gear ratio
        public float finalDriveRatio = 3.23f;
    
        // The engine's torque curve characteristics. Since actual curves are often hard to come by,
        // we approximate the torque curve from these values instead.
    
        // powerband RPM range
        public float minRPM = 800;
        public float maxRPM = 6400;
    
        // engine's maximal torque (in Nm) and RPM.
        public float maxTorque = 664;
        public float torqueRPM = 4000;
    
        // engine's maximal power (in Watts) and RPM.
        public float maxPower = 317000;
        public float powerRPM = 5000;
    
        // engine inertia (how fast the engine spins up), in kg * m^2
        public float engineInertia = 0.3f;
    
        // engine's friction coefficients - these cause the engine to slow down, and cause engine braking.
    
        // constant friction coefficient
        public float engineBaseFriction = 25f;
        // linear friction coefficient (higher friction when engine spins higher)
        public float engineRPMFriction = 0.02f;
    
        // Engine orientation (typically either Vector3.forward or Vector3.right). 
        // This determines how the car body moves as the engine revs up.	
        public Vector3 engineOrientation = Vector3.right;
    
        // Coefficient determining how muchg torque is transfered between the wheels when they move at 
        // different speeds, to simulate differential locking.
        public float differentialLockCoefficient = 0;
    
        // inputs
        // engine throttle
        public float throttle = 0;
        // engine throttle without traction control (used for automatic gear shifting)
        public float throttleInput = 0;
    
        //clutch
        public float clutch;
        private float clutchTorque;
    
        // shift gears automatically?
        public bool automatic = true;
    
        // state
        public int gear = 2;
        public float rpm;
        public float slipRatio = 0.0f;
        float engineAngularVelo;
    
        public Turbocharger turbo;
        public bool enableTurbo = false;
    
        float Sqr(float x) { return x * x; }
    
        // Calculate engine torque for current rpm and throttle values.
        float CalcEngineTorque() {
            float result;
            if (rpm < torqueRPM)
                result = maxTorque * (-Sqr(rpm / torqueRPM - 1) + 1);
            else {
                float maxPowerTorque = maxPower / (powerRPM * 2 * Mathf.PI / 60);
                float aproxFactor = (maxTorque - maxPowerTorque) / (2 * torqueRPM * powerRPM - Sqr(powerRPM) - Sqr(torqueRPM));
                float torque = aproxFactor * Sqr(rpm - torqueRPM) + maxTorque;
                result = torque > 0 ? torque : 0;
            }
            if (rpm > maxRPM) {
                result *= 1 - ((rpm - maxRPM) * 0.006f);
                if (result < 0)
                    result = 0;
            }
            if (rpm < 0)
                result = 0;
            return result;
        }
    
        void Awake() {
            if (enableTurbo) {
                turbo.SetWhistleAudio(gameObject.AddComponent<AudioSource>());
                turbo.SetBlowOffAudio(gameObject.AddComponent<AudioSource>());
            }
        }
    
        public bool backFireEnabled;
        private float backfireTracker = 0f;
        private float lastEngFricTorq = 0f;
        public Light backfireLight;
        public ParticleSystem backfirePS;
    
        void FixedUpdate() {
            float ratio = gearRatios[gear] * finalDriveRatio;
            float inertia = engineInertia * Sqr(ratio);
            float engineFrictionTorque = engineBaseFriction + rpm * engineRPMFriction;
            float engineTorque = (CalcEngineTorque() + Mathf.Abs(engineFrictionTorque)) * throttle;
    
    
            if (backFireEnabled) {
    
                //backfire calculation
                if (engineFrictionTorque > lastEngFricTorq) { //increasing
                    backfireTracker -= Mathf.Abs(engineFrictionTorque - lastEngFricTorq);
                    backfireTracker = Mathf.Clamp(backfireTracker, 0f, backfireTracker);
                } else if (engineFrictionTorque < lastEngFricTorq) {// decreasing
                    backfireTracker += Mathf.Abs(engineFrictionTorque - lastEngFricTorq);
                }
    
                if (backfireTracker > 32f) {
                    backfirePS.Emit(5);
                    GetComponent<SoundController>().playBackFire();
                    backfireTracker = 0f;
                }
    
                lastEngFricTorq = engineFrictionTorque;
            }
    
            slipRatio = 0.0f;
    
            // TURBO //
            float turboPower = 1f;
            if (enableTurbo) {
                float temp = turbo.CalculateTorque((rpm / powerRPM), throttle);
                turboPower = 1f + temp;
            }
    
            turbo.angularVelocity = engineAngularVelo;
    
            if (ratio == 0 || (clutch == 1 && (int)(GetComponent<Rigidbody>().velocity.magnitude * 3.6f) > 5)) {
    
                // Neutral gear - just rev up engine
                float engineAngularAcceleration = (engineTorque - engineFrictionTorque) / engineInertia;
                engineAngularVelo += engineAngularAcceleration * Time.deltaTime;
    
                if ((int)GetComponent<Rigidbody>().velocity.magnitude * 3.6f == 0 && engineAngularVelo < 0f)
                    engineAngularVelo = 0f;
    
                // Apply torque to car body
                GetComponent<Rigidbody>().AddTorque(-engineOrientation * engineTorque * 2.5f);
    
            } else {
                float drivetrainFraction = 1.0f / poweredWheels.Length;
                float averageAngularVelo = 0;
                foreach (Wheel w in poweredWheels)
                    averageAngularVelo += w.angularVelocity * drivetrainFraction;
    
    
                float engineAngularAcceleration = (engineTorque - engineFrictionTorque) / engineInertia;
                // Apply torque to wheels
                foreach (Wheel w in poweredWheels) {
                    float lockingTorque = (averageAngularVelo - w.angularVelocity) * differentialLockCoefficient;
                    w.drivetrainInertia = inertia * drivetrainFraction;
                    w.driveFrictionTorque = engineFrictionTorque * Mathf.Abs(ratio) * drivetrainFraction;
                    w.driveTorque = engineTorque * ratio  * drivetrainFraction + lockingTorque;
                    slipRatio += w.slipRatio * drivetrainFraction;
                }
    
    
    
                engineAngularVelo = averageAngularVelo * ratio;
    
            }
    
            // update state
            slipRatio *= Mathf.Sign(ratio);
            rpm = engineAngularVelo * (60.0f / (2 * Mathf.PI));
            rpm = Mathf.Clamp(rpm, 0f, maxRPM + minRPM); //limit excess rpm
    
            // very simple simulation of clutch - just pretend we are at a higher rpm.
            float minClutchRPM = minRPM;
            if (gear != 1 && turbo.isSteeringWheelNo2) { //steering wheel with pedals
                minClutchRPM += throttle * (maxRPM - minRPM) * clutch;
            } else if (gear == 2) { //keyboard
                minClutchRPM += throttle * 4200f;
            }
    
            if (rpm < minClutchRPM)
                rpm = minClutchRPM;
    
    
    
            // shake car on low speeds
            if (gear > 1 && (int)(GetComponent<Rigidbody>().velocity.magnitude * 3.6f) <= 20) {
                GetComponent<Rigidbody>().AddTorque(-engineOrientation * engineTorque * 2.5f);
            }
    
            // Automatic gear shifting. Bases shift points on throttle input and rpm.
    
            if ( Input.GetKey(KeyCode.X)) {
               automatic = automatic ? false : true;
            }
    
            if (automatic) {
                if (rpm >= powerRPM * (0.5f + 0.5f * throttleInput)) {
                    ShiftUp();
                } else if (rpm <= maxRPM * (0.25f + 0.4f * throttleInput) && gear > 2) {
                    ShiftDown();
                }
                if (throttleInput < 0 && rpm <= minRPM)
                    gear = (gear == 0 ? 2 : 0);
            }
    
        }
    
        public void ShiftUp() {
    
            if (gear < gearRatios.Length - 1) {
                gear++;
                GetComponent<SoundController>().playBOV();
                GetComponent<SoundController>().playShiftUp();
            }
        }
    
        public void ShiftDown() {
            if (gear > 0) {
                gear--;
                GetComponent<SoundController>().playShiftDown();
            }
        }
    
        // Debug GUI. Disable when not needed.
        void OnGUI() {
            GUILayout.Label("RPM: " + rpm);
            GUILayout.Label("Gear: " + (gear - 1));
            automatic = GUILayout.Toggle(automatic, "Automatic Transmission");
        }
    }
    
    [System.Serializable]
    public class Turbocharger {
        public float angularVelocity = 0f;
        public float prevAngularVelocity = 0f;
        public float brakingCoeff = 0.92f;
    
        public float rpm = 0f;
        private float maxRpm = 8000f;
        private float rpmNormalized = 0f;
    
        public float boost = 2f;
        public float pressure = 0f;
        [HideInInspector]
        public float prev_pressure = 0f;
    
        private AudioSource WhistleSource;
        public AudioClip WhistleSound;
    
        private AudioSource blowOffSource;
        public AudioClip blowOffSound;
    
        [HideInInspector]
        public float prev_throttle;
    
        public bool isSteeringWheelNo2 = false;
    
        public void SetWhistleAudio(AudioSource s) {
            if (!WhistleSound)
                Debug.LogError("no turbo whistle sound!");
    
            s.playOnAwake = true;
            s.loop = true;
            s.priority = 0;
            s.clip = WhistleSound;
            s.spatialBlend = 1f;
            s.dopplerLevel = 0f;
            s.Play();
    
            WhistleSource = s;
        }
    
        public void SetBlowOffAudio(AudioSource s) {
            if (!blowOffSound)
                Debug.LogError("no turbo blow off sound!");
    
            s.playOnAwake = false;
            s.loop = false;
            s.priority = 0;
            s.clip = blowOffSound;
            s.spatialBlend = 1f;
            s.dopplerLevel = 0f;
            blowOffSource = s;
        }
    
        public float CalculateTorque(float engineRpmNormalized, float throttle) {
            float inertia = 0.01f;
    
            float angularAcceleration = throttle * engineRpmNormalized * 300f / inertia;
            angularVelocity += angularAcceleration * Time.deltaTime;
    
            angularVelocity += -brakingCoeff * rpmNormalized * 60f;
    
            rpm = angularVelocity * 60f;// rpm
    
            rpmNormalized = rpm / maxRpm;
            prev_pressure = pressure;
            pressure = rpmNormalized * boost * throttle + (isSteeringWheelNo2 ? 3.5f : 0f);
    
            CalculateBlowOff(throttle, prev_pressure);
    
            WhistleSource.pitch = engineRpmNormalized;
            WhistleSource.volume = rpmNormalized * 0.2f;
    
            return pressure * boost * 0.1f;
        }
    
        public void CalculateBlowOff(float throttle, float pressure) {
            prevAngularVelocity = angularVelocity;
    
            if (pressure > (isSteeringWheelNo2 ? 10.5f : 11.5f) && throttle <= (isSteeringWheelNo2 ? 0.6f : 0)) {
                if (!blowOffSource.isPlaying) {
                    blowOffSource.volume = rpmNormalized * 0.13f - (isSteeringWheelNo2 ? 0.375f : 0.075f);
                    blowOffSource.pitch = pressure / (boost * 8f) + 0.176f;
                    blowOffSource.Play();
    
                    angularVelocity = 0f;
                } else {
                    blowOffSource.volume = rpmNormalized * 0.13f - (isSteeringWheelNo2 ? 0.375f : 0.075f);
                    blowOffSource.pitch = pressure / (boost * 8f) + 0.176f;
                    blowOffSource.Stop();
                    blowOffSource.Play();
                }
            }
        }
    }

    Save your changes and tweak the following parameters:

    – First, tweak the powered wheels parameter, add 2 wheels and select the rear right and rear left wheels, you can modify this to control where power should go as in RWD,4WD and FWD..etc.
    – Set the gear ratios size to 7 and tweak it as the following, these ratios are for a stock AE86:

    [0] -> -3.484
    [1] -> 0
    [2] -> 3.587
    [3] -> 2.022
    [4] -> 1.384
    [5] -> 1
    [6] -> 0.861

    Final drive ratio should be set to 4.3
    Minimum RPM Value: 1100
    Maximum RPM Value: 8000
    Max Torque: 578
    Torque RPM: 4800
    Max Power: 304100
    Power RPM: 7000
    Engine Inertia: 0.18
    Engine Base Friction: 30
    Engine RPM Friction: 0.02

    Rest of the values as shown in the image below:

    Drivetrain script values

  • Voice Controller.

    No car game should go without car sounds, at this step we will be adding the sound controller that will emit multiple noises making the game more fun.
    Before we begin you should download the sound pack that we will be needing for this step, the following link contains a sound package with multiple sounds:

    https://www.mediafire.com/file/sg33ag5imava4cl/Sounds.unitypackage

    Import this package and move the files to the SFX folder under sounds.

    Next, add the SoundController script to your Vehicle game object and update the sound controller with this modified script:

    using UnityEngine;
    using System.Collections;
    
    // Simple class to controll sounds of the car, based on engine throttle and RPM, and skid velocity.
    [RequireComponent(typeof(Drivetrain))]
    [RequireComponent(typeof(CarController))]
    public class SoundController : MonoBehaviour {
    
        public AudioClip engine1;
        //public AudioClip engine2;
        public AudioClip skid;
        public AudioClip shiftUp;
        public AudioClip shiftDown;
        public AudioClip blowOffValve;
        public AudioClip[] transmission;
        public AudioClip[] backfire;
    
        AudioSource engineSource1;
        AudioSource engineSource2;
        AudioSource skidSource;
        AudioSource shiftUpSource;
        AudioSource shiftDownSource;
        AudioSource blowOffValveSource;
        AudioSource transmissionOnSource;
        AudioSource transmissionOffSource;
        public AudioSource backfireSource;
    
        CarController car;
        Drivetrain drivetrain;
    
        AudioSource CreateAudioSource(AudioClip clip, string name) {
            GameObject go = new GameObject(name);
            go.transform.parent = transform;
            go.transform.localPosition = Vector3.zero;
            go.transform.localRotation = Quaternion.identity;
            go.AddComponent(typeof(AudioSource));
            go.GetComponent<AudioSource>().clip = clip;
            go.GetComponent<AudioSource>().loop = true;
            go.GetComponent<AudioSource>().volume = 0.20f;
            go.GetComponent<AudioSource>().spatialBlend = 1f;
            go.GetComponent<AudioSource>().dopplerLevel = 0f;
            go.GetComponent<AudioSource>().Play();
            return go.GetComponent<AudioSource>();
        }
    
        AudioSource CreateAudioSourceShift(AudioClip clip, string name) {
            GameObject go = new GameObject(name);
            go.transform.parent = transform;
            go.transform.localPosition = Vector3.zero;
            go.transform.localRotation = Quaternion.identity;
            go.AddComponent(typeof(AudioSource));
            go.GetComponent<AudioSource>().clip = clip;
            go.GetComponent<AudioSource>().loop = false;
            go.GetComponent<AudioSource>().volume = 0.75f;
            go.GetComponent<AudioSource>().spatialBlend = 1f;
            go.GetComponent<AudioSource>().dopplerLevel = 0f;
            return go.GetComponent<AudioSource>();
        }
    
        void Start() {
            engineSource1 = CreateAudioSource(engine1, "Engine Audio 1");
            engineSource1.transform.localPosition = new Vector3(0f, 0.5f, 1.3f);
            //engineSource2 = CreateAudioSource(engine2 , "Engine Audio 2");
    
            skidSource = CreateAudioSource(skid, "Skidding Audio");
            car = GetComponent(typeof(CarController)) as CarController;
            drivetrain = GetComponent(typeof(Drivetrain)) as Drivetrain;
    
            shiftUpSource = CreateAudioSourceShift(shiftUp, "Shift Up Audio");
            shiftUpSource.volume = 0.1f;
            shiftDownSource = CreateAudioSourceShift(shiftDown, "Shift Down Audio");
            shiftDownSource.volume = 0.1f;
    
            blowOffValveSource = CreateAudioSourceShift(blowOffValve, "Blow Off Valve Audio");
            blowOffValveSource.transform.localPosition = new Vector3(-0.3f, 0.8f, 1.3f);
    
            transmissionOnSource = CreateAudioSource(transmission[2], "Transmission On Audio");
            transmissionOffSource = CreateAudioSource(transmission[1], "Transmission Off Audio");
        }
    
        public void playShiftUp() {
            shiftUpSource.Play();
        }
    
        public void playShiftDown() {
            shiftDownSource.Play();
        }
    
        public void playBOV() {
            if ((drivetrain.rpm / drivetrain.maxRPM) > 0.80f)
                blowOffValveSource.Play();
        }
    
        public void playBackFire() {
            backfireSource.clip = backfire[Random.Range(0, 3)];
            backfireSource.Play();
        }
    
        int currSpeed, lastSpeed, difference;
    
        void Update() {
    
            engineSource1.pitch = 0.30f + drivetrain.rpm / drivetrain.powerRPM; //was 0.20 and 0.40
            engineSource1.volume = 0.35f + 0.60f * drivetrain.throttle;
    
    
            float skidVol = Mathf.Clamp01(Mathf.Abs(car.slipVelo) * 0.15f - 0.95f);
            skidSource.volume = skidVol >= 0.3f ? skidVol : 0f;
    
            transmissionOnSource.pitch = 0.20f + drivetrain.rpm / drivetrain.maxRPM;
    
            transmissionOffSource.pitch = 0.20f + drivetrain.rpm / drivetrain.maxRPM;
    
            blowOffValveSource.pitch = 0.28f + drivetrain.rpm / drivetrain.maxRPM;
    
            currSpeed = (int)(GetComponent<Rigidbody>().velocity.magnitude * 3.6f);
    
            // sound generated when slowing down
            if (currSpeed > (lastSpeed + 3)) { // speeding up
                lastSpeed = currSpeed;
                //high
                transmissionOffSource.volume -= 0.10f;
    
                if (transmissionOffSource.volume == 0f) {
                    transmissionOffSource.Stop();
                }
            } else if ((float)currSpeed < (float)((float)lastSpeed - 3.2f)) { //slowing down
                lastSpeed = currSpeed;
                //low
                transmissionOffSource.volume = 0.25f;
    
                if (!transmissionOffSource.isPlaying) {
                    transmissionOffSource.Play();
                }
            }
        }
    }
    

    After saving the script, tweak the sound controller parameters as shown in the following image:

    Sound Controller Script

    Now you can test drive the car with sounds, directional buttons to accelerate/brake/turn, A + Z to shift gears, Space bar for handbrake.

  • Anti Roll Bar / Aero Dynamic Resistance / Traction Helper.

    These additional scripts are essential for a better drift experience, as you have probably noted by now that the car flips around corners, this is why manufacturers put anti roll bars to prevent vehicles from rolling over, you will need to set this up so the car can drift without rolling issues,this step is very quick and easy.

    Simply drag all these scripts with but add the anti roll bar script twice, tweak the values just as shown in the below image:

    anti roll bar aerodynamic resistance traction helper

  • Extra Features.

    More features such as lighting for headlights/brakes/Environment, smoke, tread marks, mini map, camera work, different controls (Gamepad and Steering Wheel), RPM/Speed/Gear gauges,advanced collision,backfire,turbo spool and blow off valve,tire grip on different surfaces, drift zones…etc will be introduced in future posts, I will be updating this tutorial with links to them when they’re available, in the mean time I hope you’ve learned a lot from this, if you liked this tutorial then rig up your own favorite vehicle, youtube it and comment the link below for me to see it. 🙂

Download the tutorial project from the following google drive link:

Unity Drift Simulator Tutorial Project (97 MB – 7zip)

If you have any questions regarding building a car drift game then leave a comment below and I’ll get back to you.

See you in future posts!

Related posts

17 Thoughts to “Unity Drift Simulator Tutorial”

  1. kai

    how do you make the car countersteer drift and make it last through the corner

    1. NoCakeNoCode

      In short: skills.

      Practice makes perfect. But to further elaborate this point during gameplay, it usually depends on the cars characteristics, you have to understand and possibly tweak the car’s layout, setup, power, torque, maximum turning angle, weight distribution and weight transfer, center of gravity, anti roll bars settings, tire grip level, speed, gear selection, handbrakes, clutch and probably a few more things such as camera view.

      We as humans have a passion for motor control and cars are our favorite toys where we apply our knowledge into making them do what we desire.

      Car tuning is a vast field where many enthusiasts spend their time fine tuning their cars to achieve the best drifting experience possible and it does take a fair amount of time to understand it.

      My recommendation is to use a steering wheel with pedals and a gear shifter to practice drifting, tweak values as you go and eventually you will be able to drift through corners effortlessly, you can try the settings in my tutorial to achieve what you’ve seen in my video.

  2. Azuu

    Hey, firstly I want to thank you very much for this tutorial, I started to learn Unity not so long ago and it helps a lot 😀

    After following all your steps, I have 2 major problems left and I wanted to know if you could help me to identify what I’ve done wrong.
    I’ve tried the same conditions as in your tutorial : Autumn Ring with Mesh Colliders and the AE86.

    – When CarController is activated and I enter in Play Mode, the car… well, seems fixed by the front right wheel on the road and make slow barrels, like pushed by the wind (funny to watch though). Impossible to move the car, wheels move but don’t change anything.

    – The wheels when I enter Play Mode are too “low” under the car, like a Monster Truck but with normal wheels. They also pass through the road and there’s only a small part of the tire that is still visible.

    I hope my English is good enough for you to understand, and again thanks a lot for this tutorial 😀

    1. Azuu

      Oh, and I forget to mention for the first point, when CarController is deactived, the car fall on the ground (nothing strange because I’ve tried to raise the car a bit on the Y axis) and seems to act normally : no movement, transform position values are stable, “idle” sound playing.

    2. NoCakeNoCode

      I’m glad that my tutorial has benefited you.

      Allow me to answer your problems. While this tutorial covers a lot of things it sadly misses a few points that influence the overall result, one of the most noticeable things during my remake is the vehicle’s size, I haven’t done a lot of “proper” sizing and positioning and only relied on my “instincts” which is a big mistake in proper simulation.

      – To address your first problem I will definitely need to take a look at your project, I can however speculate that you’ve positioned the center of gravity object near the front right wheel which is what I’m thinking you’re trying to say, another possibility could be the box collider touching the ground, one more would be the wheels radius and suspension travel .

      – Proper car sizing along wheels positioning, radius and suspension travel will solve this issue.

      I will update this tutorial and post a sample project link so you can and everyone else have a look and play around it.

      – Update –
      Download link added at the end of the tutorial, enjoy 😀

      1. Azuu

        Thank you for the answer and for the link 😀

        I just had a look into your project and tried to import your AE86 in my project, and that works nice, so I know that something is wrong with my car.
        I’ve tried several things inspired by your work, but I still cannot seems to find how to correct it.

        – I have checked the center of gravity, it was placed “by eye” but not that bad (at least I think). But I just have notice that, as you said, the box collider touch the ground, ignoring the wheels physics. Reducing the box collider height makes the car goes deeper in the ground.

        – The wheels problem is solved when I set the suspension travel from 0.11(front)/0.12(rear) to 0, but I feel that will have an impact on the gameplay like this haha. I also changed the radius from 0.31 to 0.074 to have something matching the Wheel Collider tool of Unity.

        I have uploaded the actual state of my project, if you want to take a look at it : http://www.mediafire.com/file/w5ld635lf6ff3j8/Unity%20Drift%20Test.rar

        1. NoCakeNoCode

          Hi Azuu,

          I downloaded your project but had an error trying to open the rar file, kindly re-zip and upload it again.

          1. Azuu

            Ups, sorry, maybe that’s because it’s a .rar file. I reuploaded it in .zip here : http://www.mediafire.com/file/c7wmhyhpik77366/Drift%20Test.zip
            Thanks again 😀

          2. enesoz

            Could you please Update link

  3. Frago

    Hello, your project download link doesn’t work, could you fix that please? Thanks a lot 🙂

  4. Philip

    hello NoCakeNoCode, this is an awesome tutorial but i have one problem, when i turn the car it does not turn immediately it starts gliding the direction i’m trying to go then it turns, it feels like the car is constantly on ice and is really hard to control. Could you help me with what you think could fix this problem

  5. Hello NoCakeNoCode, this tutorial was awesome most of it works, i have but a small problem the car when driven by only the 2 back wheels and at low speeds is like driving on ice, the car glides it does not turn. For example if i start to drive from a standstill and decide i want to turn it will glide diagonally forwards for a bit before turning the actual car, when i send power to all 4 wheels this problem fixes itself at high speeds but come back when you slow down, this problem is constant when it’s only powered by 2 wheels. please help

    Thanks

  6. Sebben

    Amazing, but my car makes drift stopped :(, can you do this physics works with wheel colliders?

  7. Eduardo

    Amazing, but my car makes drift stopped 🙁 can you make this physics work with wheel collider??

  8. Dazz

    Continue this tutorial plzz

  9. enes

    Hello,I can use scripts perfectly but when I add the wheel meshes in the Wheel script wheels are dissappointing.How can I solve this,Could you help me please.

  10. enesoz

    Hello ,I can use scripts perfectly but when I add wheel meshes in Wheel Script Wheels are dissappointing.How can I solve it could you help me.

Leave a Reply