How to Create a Race Car in Unity

Racing games are a classic, whether found on modern consoles or in old-timey arcades.

Regardless of their rendition though, one of the few fundamentals they have in common is their cars. Cars are one of the key buildings blocks, and will probably be one of the first things you want to learn to make your own racing game.

In this tutorial, we’re going to cover just that: building a race car using the popular Unity Engine. We’ll be covering everything from setting up movement with Unity’s Input System, to adding physics for sterring.

Let’s dive in!

Project Files

You can download a copy of the source code files for the project done in this tutorial here. Note that this tutorial requires good familiarity with Unity and C# scripting.

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Setting Up the Input System

After creating a new 3D project in Unity, we need to install the Input System Package first of all.

Installing Input System Package

Let’s open up the Package Manager window (Window > Package Manager):

Accessing Package Manager from the Window tab in Unity

Select Packages > Unity Registry to access all the Unity packages:

Select Unity Registry to get to the Input System Package

And install the Input System:

Installing Input System in Unity

Creating Input Actions

Once the Input System Package is installed, we can right-click on the Project window (inside the Assets folder) and click on Create > Input Actions.

Creating Input Actions in Unity

We’re going to call this “PlayerControls” and double-click to open it up:

Rename it to "PlayerControls"

This is going to open up the Input Actions window. On the left panel, we can create an Action Map by clicking on the + button. This is basically a category for all your different inputs:

Creating an Action Map in Unity

We’ll call the first action map “Main“, and create a new Action connected to the “Main” action map. Actions define what we want to do when pressing certain keys:

Creating our first action map named "Main" in Unity

So first of all, we’re going to create an action called “Move“:

Create an action called "Move" to the 'Main' action map

Then we want to go over to the Properties panel and set the Action type to Button:

Setting the Action Type to be Button

Now we can click on Binding > Path > Listen and then press any button on the keyboard to register and bind it with our Input Action:

Click on Listen to bind a key to the new action Press the desired button to bind it to the Move action

Alternatively, if we want our movement to be represented with a Vector2 value between -1 and 1, we can change the Action Type to Value and set the Control Type to Vector2:

Representing the action with a Vector2 in Unity

In that case, we can determine which direction the player needs to move in by adding 2D Vector Composite to the Move action. This will create a new binding with four children bindings called Up, Down, Left, and Right:

Adding a 2D Vector Composite to bind the 4 possible moving directions

Bindings for moving up, down, left, and right

To bind a key to an input action, click on Path > Listen, and hit the corresponding keyboard button (W, A, S, and D):

Binding keys to each of the directions

Make sure that all input actions are assigned a key:

Basic move actions for the Unity Input System

Additional Inputs

What if we want to plug in a controller for our second player? In that case, we can click on the + (Add) button next to the Actions heading, and then click on Add Binding:

Including additional inputs for a second player

If you have Xbox or PlayStation controllers, you can choose the Gamepad option instead of the Keyboard for binding the keys:

Binding the keys with the Gamepad option, instead of using keyboard entries

For specific gamepad controls’ information, refer to the documentation:  https://docs.unity3d.com/Packages/[email protected]/manual/Gamepad.html

You can also separate the window for each type of controller by adding a new Control Scheme:

Adding a new Control Scheme to separate the inputs from each type of controller

By clicking on the Control Scheme dropdown at the top-left corner, you can switch over to different control schemes (Keyboard/Controller/Joystick/etc).

If you have multiple control schemes, you need to make sure that each key binding is used in the appropriate control scheme. This can be done by enabling/disabling the checkboxes under ‘Use in Control Scheme‘:

Checking that the bindings are coherent for each control scheme in Unity

Refer to the table below to complete all control schemes:

AccelerateUp Arrow [Keyboard]
AccelerateButton South [Gamepad]
TurnLeft Stick/X [Gamepad]
TurnLeft/Right Arrow (-/+) [Keyboard]

Control schemes for our game in Unity

Make sure to click Save Asset before closing the window:

Click on "Save Asset" before closing the window

Car GameObject

To set up a new GameObject for our car, let’s create a new empty GameObject called “Car”. Then we want to add a Rigidbody component for the physics, a Sphere Collider for the collision, and a Player Input for detecting inputs:

Setting up a new GameObject for the car

We also need to attach a new C# script, which will detect inputs and enable us to drive the car around. Let’s call this script “CarController”:

Attaching a new script to our car

Next, we’re going to right-click on our Car object and click on Create Empty to create a new empty child object. This is going to allow our car model to maintain upright rotation (instead of spinning around with the parent Car object):

CarModel as a child of the Car object

The car models can be found in the Project Files link above, and you can import them into a Assets > Models > Cars folder. Let’s drag it into our CarModel object:

Importing the model to our Unity project

Ensure that the model‘s Y-rotation is correctly facing forward by setting it to 180. Also, adjust the Sphere Collider to match the model’s size. The model should be positioned at the bottom of the sphere:

Adjusting the model to be at the bottom of the Sphere Collider

Remember to assign a ‘Player’ tag to the car object too, as follows:

Assigning the Player tag to the car in Unity

Rigidbody Settings

The Rigidbody component controls the car’s position through physics simulation. By default, it is set to be pulled downward by gravity and to react to collisions with other objects.

To simulate a realistic car’s behavior, we’re going to adjust these properties:

  • Drag: 0 → 0.5 (This acts as wind resistance/ground friction in forwarding momentum)
  • Angular Drag: 0.05 → 3 (Same as Drag, but for rotation)
  • Interpolate: None → Interpolate (Smoothes out the effect of running physics at a fixed frame rate)

Car's Rigidbody settings

Feel free to tweak the values as you like.

Camera Settings

The Main Camera in the scene will be moving based on the car’s position and rotation, keeping a certain distance away from the car. We’re going to call this ‘Camera Offset‘.

First, we need to create an empty object that will contain our camera as a child object:

Placing the camera as a child object

For the Main Camera to orbit around the car, we need to have the parent container object located at the exact same position as the car. We can copy the position values of the car by Right-click on Transform > Copy Component:

Copying the position values of the Car object

Then, we can go to the Main Camera object and right-click on Transform > Paste Component Values:

Pasting over the Component Values of the Car's position

Now you can tweak the position and rotation values of the Main Camera to set the default camera angle:

Main Camera now rotates around the car

Make sure that you have assigned the Input Action Asset and the Main Camera to the Player Input component:

Player Input component settings in Unity

Car Controller Script

Once you have the script attached to our Car GameObject, let’s start editing the script:

The Car Controller script has to be attached to the Car GameObject

Creating Variables

To control our car, we need to define some variables. Take a look at the list below for a brief description of each variable:

  • Acceleration (float) – the rate at which the car accelerates forward
  • Turn speed (float) – the rate at which the car rotates left and right when steering
  • Car model (Transform) – the reference to the car’s model, which we want to keep stationary in terms of rotation.
  • Start Model Offset (Vector3) – The distance offset from the car model to its parent object to update the model’s position on every frame.
  • Ground Check Rate (float) – The rate of raycasting in order to determine if the car is on the ground.
  • Last Ground Check Time (float) – The time the previous ground check was performed.
  • Current Y Rotation (float) – The current Y rotation of the car model.
  • Accelerate Input (bool) – Input state for acceleration
  • Turn Input (float) – Input state for turning
  • Rigidbody
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CarController : MonoBehaviour
{
    public float acceleration;
    public float turnSpeed;

    public Transform carModel;
    private Vector3 startModelOffset;

    public float groundCheckRate;
    private float lastGroundCheckTime;

    private float curYRot;

    private bool accelerateInput;
    private float turnInput;

    public Rigidbody rig;
}

Once all the variables are declared, we can save the script, go back to the editor, and set up the public variables’ values in the Inspector:

Setting up the public variables' values in Unity's Inspector

Detecting Inputs

Since we’re using UnityEngine.InputSystem, we can create some functions that we can connect to the Player Input component attached to the Car object.

The name of the function can be whatever you want, but it should have a parameter of InputAction.CallbackContext.

For example, let’s start working on the function to be called when the Accelerate input is detected:

// called when we press down the accelerate input
public void OnAccelerateInput (InputAction.CallbackContext context)
{
}

Here, the “context” sends over all the information regarding the input, such as if the button has just been pressed down, the duration of holding down the key, etc.

By checking if its phase is InputActionPhase.Performed, we can ensure that the key is being pressed down:

// called when we press down the accelerate input
public void OnAccelerateInput (InputAction.CallbackContext context)
{
    if(context.phase == InputActionPhase.Performed)
        accelerateInput = true;
    else
        accelerateInput = false;
}

Similarly, we can read the turn input as a float value (negative = left, positive = right):

// called when we modify the turn input
public void OnTurnInput (InputAction.CallbackContext context)
{
    turnInput = context.ReadValue<float>();
}

Let’s save the script, and bind the input functions with specific keys. Go to the Player Input component, assign the Car object to the input events (under Events > Main), and select both OnAccelerateInput and OnTurnInput:

Event settings for the Player Input in Unity

Settings for the Player Input component in Unity

Now, these event functions are connected to the keys which we have mapped in our Input Action Asset.

Defining Offset

Right now, we have the car’s model attached under its parent Car GameObject. In order to fix the model’s position at the current offset from the parent, we’re going to store the initial localPosition in a Vector3 variable, called startModelOffset.

So let’s define the Start() function to set the startModelOffset‘s value:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CarController : MonoBehaviour
{
    ...
    private Vector3 startModelOffset;
    void Start ()
    {
        startModelOffset = carModel.transform.localPosition;
    }
}

We can then define the FixedUpdate function to add force to our car. Remember, FixedUpdate runs 60 times per second consistently (whereas Update runs every single frame), and hence it is useful for physics calculations.

Here, we’re only adding force into the forward direction if we have ‘accelerateInput‘ as true:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CarController : MonoBehaviour
{
    ...
    void FixedUpdate ()
    {
        if(accelerateInput == true)
        {
            rig.AddForce(carModel.forward * acceleration, ForceMode.Acceleration);
        }
    }
}

Our car model should only rotate around the y-axis based on our turn input. We can directly replace the y-rotation to reflect our turnInput and turnSpeed:

Our car model should only rotate around the y-axis Using our turn inputs for the rotation of the car

So let’s manually update the rotation inside Update:

public class CarController : MonoBehaviour
{
    ...
    void Update ()
    {
        curYRot += turnInput * turnSpeed * Time.deltaTime;

        carModel.position = transform.position + startModelOffset;
        carModel.eulerAngles = new Vector3(0, curYRot, 0);
    }

}

Our final code looks like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CarController : MonoBehaviour
{
    public float acceleration;
    public float turnSpeed;
    
    public Transform carModel;
    private Vector3 startModelOffset;

    public float groundCheckRate;
    private float lastGroundCheckTime;

    private float curYRot;

    private bool accelerateInput;
    private float turnInput;

    public Rigidbody rig;

    void Start ()
    {
        startModelOffset = carModel.transform.localPosition;
    }

    void Update ()
    {
        curYRot += turnInput * turnSpeed * Time.deltaTime;
        
        carModel.position = transform.position + startModelOffset;
        carModel.eulerAngles = new Vector3(0, curYRot, 0);
    }

    void FixedUpdate ()
    {
        if(accelerateInput == true)
        {
            rig.AddForce(carModel.forward * acceleration, ForceMode.Acceleration);
        }
    }

    // called when we press down the accelerate input
    public void OnAccelerateInput (InputAction.CallbackContext context)
    {
        if(context.phase == InputActionPhase.Performed)
            accelerateInput = true;
        else
            accelerateInput = false;
    }

    // called when we modify the turn input
    public void OnTurnInput (InputAction.CallbackContext context)
    {
        turnInput = context.ReadValue<float>();
    }
}

And that’s that for this tutorial!

Conclusion

Congratulations on concluding the tutorial!

You now have a car GameObject inside Unity that you can accelerate forward and steer left and right! You’ve also already tackled setting up Rigidbody, Colliders, and Player Input components, aside from using models and scripting in Unity.

As for the next step, well you’re ready to continue developing your project further! You could start by creating the scenery and road environment, or even start considering splitting the screen to add multiplayer, for instance. There is no limit what you can do with these foundations – so don’t be afraid to experiment!

We wish you the best of luck implementing your future games!

Want to learn more about racing games in Unity? Try our complete Build an Arcade Kart Racing Game course.

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.