TD4 Racing cars

In these TDs we will implement a racing game in Unity 3D step by step.

You’ll be able to control a car with reasonable physics to compete against simple AI players, count the laps of the cars to determine the winner and start the race with a countdown. Finally we will see some techniques you can use (particles, sound effects & music, animations…) to juice up your game in order to make it more lively and fun !

These TDs are split into 3 parts : the first part covers the basics of a racing game (TD4), the second part will add opponents (TD5) and the final part is focused on shiny and fun effects to make your game stand out (TD6). It will cover all the essential things you need to know before starting your own project.

 Minimum Viable Product 🚙

 Drive a car around a race track + count laps

Buckle your belt, because you’re about to make a racing game !

Create a drivable car 🚙

  1. Model

First of all, our car needs at least some visuals in order to be recognizable as a car.

We could create and import a model from blender or take one from the internet, but for now let’s keep it simple.

We will just use some primary shapes (cubes and cylinders) in unity and use the scale and position transforms to make it appear like it.

Tip: You can use empty game objects as parents to have a clean hierarchy

This is what I got :

All white ? This is a bit sad..

Let’s add some colors to your primitive shapes to make it pop !

In unity, models can have one and + materials. A material is an asset that dictates to your model how it will render (color, reaction to the light, transparency, texture, etc.). In this simple case, we will create some simple materials to apply to our shapes (1 for the car paint, 1 for the metal parts, 1 for the glass, 1 for the tires, and 2 for the front and rear lights).

  1. Physics

Now that we have a cool car to drive around, let’s make it drivable, shall we ?

The cool thing about using a game engine like unity is that you don’t need to code all the physics, unity does already a lot for you.

To make our car react to some physics, we need 3 things :

This component will tell unity that our car needs to react to physics like detect collisions, apply gravity and also drag forces (the forces that resists motion of an object, to simulate air friction for example. To illustrate, a feather has huge drag forces applied to it compared to a bowling bowl).

It represents the bounds / sizes of the object that should react to physics.

Collisions in game engines are heavy to calculate, so we often simplify the collisions of the object by using primitive colliders in order to simplify the physics and avoid weird behaviors.

This custom script will listen to keyboard inputs and add forces to the rigidbody in order to move and steer the car.

Let’s get started ! 🚀

You should see the car fall and rest on the plane !

Great ! So now, let’s add a motor to this physical car ! 🚗

public class CarController : MonoBehaviour
{
   
private float inputX;
   
private float inputY;
   
void Update() // Get keyboard inputs
   {
       inputY = Input.GetAxis(
"Vertical");
       inputX = Input.GetAxis(
"Horizontal");
       Debug.Log(inputX +
"," + inputY);
   }
}

If you don’t have arrows on your keyboard or want also to control your car with the keys QZSD, you will have to go into Edit > Project Settings > Input manager and change the “Alt negative/positive Button” for the “Vertical” and “Horizontal” inputs like so :

Great, we can catch keyboard inputs ! So now let’s use the inputY (up/down keys) to move the car :

All variables that are public are displayed as a property in the inspector in the unity editor. That makes connection between components and tweaking of values on your component much easier !        

So just by adding this variables in our script :

public Rigidbody rg;
public float forwardMoveSpeed;

If you save and go back to the editor you should see the properties display like so :

Go back to the script and let’s move the car !

Here is the new code :

public class CarController : MonoBehaviour
{
   
public Rigidbody rg;
   
public float forwardMoveSpeed;

   
private float inputX;
   
private float inputY;
   
void Update() // Get keyboard inputs
   {
       inputY = Input.GetAxis(
"Vertical");
       inputX = Input.GetAxis(
"Horizontal");
       Debug.Log(inputX +
"," + inputY);
   }
   
   
void FixedUpdate() // Apply physics here
   {
       
float speed = inputY > 0 ? forwardMoveSpeed : -forwardMoveSpeed;
       
if (inputY == 0) speed = 0;
       rg.AddForce(
this.transform.forward * speed, ForceMode.Acceleration);
   }
}

Your car can now go forward or backward ! 👏

Now let’s steer the vehicle ! ⬅️ ➡️

public class CarController : MonoBehaviour
{
   
public Rigidbody rg;
   
public float forwardMoveSpeed;
   
public float backwardMoveSpeed;
   
public float steerSpeed;

   
private float inputX;
   
private float inputY;
   
void Update() // Get keyboard inputs
   {
       inputY = Input.GetAxis(
"Vertical");
       inputX = Input.GetAxis(
"Horizontal");
   }
   
   
void FixedUpdate() // Apply physics here
   {
       
// Accelerate
       
float speed = inputY > 0 ? forwardMoveSpeed : backwardMoveSpeed;
       
if (inputY == 0) speed = 0;
       rg.AddForce(
this.transform.forward * speed, ForceMode.Acceleration);
       
// Steer
       
float rotation = inputX * steerSpeed * Time.fixedDeltaTime;
       transform.Rotate(
0, rotation, 0, Space.World);
   }
}

Camera 🎥

Okay, that’s cool but we quickly lose the car from our camera field of view.

public class FollowPlayer : MonoBehaviour
{
   
public Transform player;
   
public Vector3 marginFromPlayer;

   
void Update()
   {
       transform.position = player.transform.position + marginFromPlayer;
   }
}

Each frame, this script sets the position of the camera to the position of the player with a vector3 (a vector with 3 axis) to add some space between the camera and the player.

Tip: You can run play and change public properties on your script at runtime, it makes it easy to test which good values you need to put in your scripts ! Be careful, during play changes in script properties are NOT saved ! So remember or copy them before stopping the game !

Yay ! Here is our drivable car with the following camera ! 🚗 🎥

Build a track 🛣️

Driving in a blank scene is quite boring and a real race game should have at least one race track ! So let’s place one !

  1. Basic structure

A race track has already been made, you can grab it in Assets/Prefabs/TD1/TrackTD1Todo and drop it in your scene. Then on the track gameObject Right click Prefab > Unpack to make sure you can add gameObjects in your track.

As you can see, it is only a composition of a plane with 3D blocks to make the walls and some cylinders to do some obstacles, nothing fancy.

  1. Textured finish line

Let’s just add a finish line onto the track.

Count laps 🔢

Now that we have a drivable car and a racetrack with a finish line, let’s count the laps that we make on it !

  1. Count laps for one car

In a race game we can’t say the player has completed a lap when he reaches the finish line.

In fact, with such a technique, a player would just have to cross the line by going forward and backward multiple times, and that’s not what we want !

A simple technique we can use is to drop some intermediate checkpoint doors on the track that the player will have to cross in order to make sure the player has completed the lap before crossing the finish line again.

using UnityEngine.Events; // needed to use UnityEvent
using UnityEngine; // as usual
public class SimpleCheckpoint : MonoBehaviour
{
   
public UnityEvent<GameObject, SimpleCheckpoint> onCheckpointEnter;
   
void OnTriggerEnter(Collider collider)
   {
       
// if entering object is tagged as the Player
       
if (collider.gameObject.tag == "Player")
       {
           
// fire an event giving the entering gameObject and this checkpoint
           onCheckpointEnter.Invoke(collider.gameObject,
this);
       }
   }
}

using System.Collections.Generic;
using UnityEngine;
public class LapManager : MonoBehaviour
{
   
public List<Checkpoint> checkpoints;
   
public int totalLaps = 3;
   
private int lastPlayerCheckpoint = -1;
   
private int currentPlayerLap = 0;

   
void Start()
   {
       ListenCheckpoints(
true);
   }

   
private void ListenCheckpoints(bool subscribe)
   {
       
foreach (Checkpoint checkpoint in checkpoints)
       {
           
if (subscribe) checkpoint.onCheckpointEnter.AddListener(CheckpointActivated);
           
else checkpoint.onCheckpointEnter.RemoveListener(CheckpointActivated);
       }
   }

   
public void CheckpointActivated(GameObject car, Checkpoint checkpoint)
   {
       
// Do we know this checkpoint ?
       
if (checkpoints.Contains(checkpoint))
       {
           
int checkpointNumber = checkpoints.IndexOf(checkpoint);
           
// first time ever the car reach the first checkpoint
           
bool startingFirstLap = checkpointNumber == 0 && lastPlayerCheckpoint == -1;
           
// finish line checkpoint is triggered & last checkpoint was reached
           
bool lapIsFinished = checkpointNumber == 0 && lastPlayerCheckpoint >= checkpoints.Count - 1;
           
if (startingFirstLap || lapIsFinished)
           {
               currentPlayerLap +=
1;
               lastPlayerCheckpoint =
0;

               
// if this was the final lap
               
if (currentPlayerLap > totalLaps) Debug.Log("You won");
               
else Debug.Log("Lap " + currentPlayerLap);
           }
           
// next checkpoint reached
           
else if (checkpointNumber == lastPlayerCheckpoint + 1) lastPlayerCheckpoint += 1;
       }
   }
}

  1. Display current lap on UI

Instead of a boring console, let’s display the lap number in a UI in our game !

You should see your UI correctly in the scene now

👏🚗🎉 Congratulations ! 🎉🚗👏

You have your first racing game MVP done in unity3D !