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!
Table of contents
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.
FREE COURSES AT ZENVA LEARN GAME DEVELOPMENT, PYTHON AND MORE 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):
Select Packages > Unity Registry to access all the Unity packages:
And install the Input System:
Creating Input Actions
Once theInput System Packageis installed, we canright-click on the Project window (inside the Assets folder) and click on Create > Input Actions.
We’re going to call this “PlayerControls”anddouble-click to open it up:
This is going to open up theInput Actionswindow. On the left panel, we can create anAction Mapby clicking on the+ button. This is basically a category for all your different inputs:
We’ll call the first action map “Main“, and create a newActionconnected to the “Main” action map. Actions define what we want to do when pressing certain keys:
So first of all, we’re going to create an action called “Move“:
Then we want to go over to theProperties panel and set the Action typetoButton:
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:
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:
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:
To bind a key to an input action, click on Path > Listen, and hit the corresponding keyboard button (W, A, S, and D):
Make sure that all input actions are assigned a key:
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:
If you have Xbox or PlayStation controllers, you can choose the Gamepad option instead of the Keyboard for binding the keys:
For specific gamepad controls’ information, refer to the documentation: https://docs.unity3d.com/Packages/[emailprotected]/manual/Gamepad.html
You can also separate the window for each type of controller by adding a new Control Scheme:
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‘:
Refer to the table below to complete all control schemes:
Accelerate | Up Arrow [Keyboard] |
Accelerate | Button South [Gamepad] |
Turn | Left Stick/X [Gamepad] |
Turn | Left/Right Arrow (-/+) [Keyboard] |
Make sure to clickSave 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:
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”:
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):
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:
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:
Remember to assign a ‘Player’ tag to the car object too, as follows:
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)
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:
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:
Then, we can go to the Main Camera object and right-click on Transform > Paste Component Values:
Now you can tweak the position and rotation values of the Main Camera to set the default camera angle:
Make sure that you have assigned the Input Action Asset and the Main Camera to the Player Input component:
Car Controller Script
Once you have the script attached to our Car GameObject, let’s start editing the script:
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:
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 whenthe Accelerate input is detected:
// called when we press down the accelerate inputpublic 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 inputpublic 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 inputpublic 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:
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 andturnSpeed:
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 Gamecourse.
Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it! FREE COURSES FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.