How to program a 2D car?

Hello.

I want to make an endless 2D game with a car that is controlled by the player driving on a road with incoming traffic. Here are my questions:

  1. How can I program a car that increases speed gradually as the player holds down the ‘D’ key?

  2. I want the car to slow down gradually as the player lets go of the key and eventually come to a stop.

  3. I also want the car to move up and down if the player presses the ‘W’ or the ‘S’ key but the car should not rotate.

  4. I want to do some UI that represents the car’s speed (mainly just text in the bottom right).

  5. I want the velocity to cap at a certain number (for example the car cannot exceed 60 km/h).

Thank you for the help. It would be much appreciated if you can answer these questions separately so that its easy for me to understand and implement. I take inspiration from the ‘Midnight Motorist’ minigame in Five Nights at Freddy’s 6. If you want, you can refer to it.

Cheers.

1 Like

To program a car that increases speed gradually as the player holds down the ‘D’ key, you can use Unity’s built-in input system and apply force to the car’s Rigidbody component. Here’s an example code snippet that you can use:

public float acceleration = 10f; // The rate of acceleration
public float maxSpeed = 60f; // The maximum speed the car can reach

private Rigidbody2D rb;

void Start()
{
    rb = GetComponent<Rigidbody2D>();
}

void Update()
{
    if (Input.GetKey(KeyCode.D))
    {
        rb.AddForce(transform.right * acceleration * Time.deltaTime, ForceMode2D.Force);
        rb.velocity = Vector2.ClampMagnitude(rb.velocity, maxSpeed); // Cap the velocity
    }
    else if (Input.GetKeyUp(KeyCode.D))
    {
        rb.velocity = Vector2.zero; // Stop the car gradually
    }
}

To slow down the car gradually as the player lets go of the key, you can set the car’s velocity to zero when the player releases the ‘D’ key.

To move the car up and down if the player presses the ‘W’ or the ‘S’ key but the car should not rotate, you can apply force in the up or down direction based on the player’s input. Here’s an example code snippet:

public float verticalSpeed = 5f; // The rate of vertical movement

void Update()
{
    if (Input.GetKey(KeyCode.W))
    {
        rb.AddForce(transform.up * verticalSpeed * Time.deltaTime, ForceMode2D.Force);
    }
    else if (Input.GetKey(KeyCode.S))
    {
        rb.AddForce(transform.up * -verticalSpeed * Time.deltaTime, ForceMode2D.Force);
    }
}

To display the car’s speed in the UI, you can use a Text component and update its text property based on the car’s velocity. Here’s an example code snippet:

public Text speedText; // The Text component that displays the speed

void Update()
{
    speedText.text = "Speed: " + Mathf.Round(rb.velocity.magnitude) + " km/h";
}

This will round the kilometers to the nearest integer

This is mostly a concept to help you get the idea, so similar stuff could be used for 3D

I hope this is the solution for you!

Much thanks for the reply, Christopher.

But I was wondering if I can define a float to determine how fast the car comes to a stop? And how should I implement it?

Also, what if I want the car to keep going forwards or backwards, but is able to go up and down simultaneously?

I want the car to feel like the racing game that used to come with a handheld back in the days (that I cannot remember the name of). I’ll attach a picture of a look-alike in-case you’ve played it and remember how the car used to feel.

Sorry for the inconvenience.

1 Like

Ur good, I’m committed to getting people their solutions, so here’s my response( please let me know if I misunderstood)

Yes, you can define a float to determine how fast the car comes to a stop. One way to implement this is to add another public float variable called deceleration to the CarController class, and use it to gradually slow down the car when the player releases the ‘D’ key. Here is an example code snippet:

public class CarController : MonoBehaviour {
public float acceleration = 5f;
public float deceleration = 5f;
public float maxSpeed = 60f;
private float currentSpeed = 0f;

void Update() {
    if (Input.GetKey(KeyCode.D)) {
        currentSpeed += acceleration * Time.deltaTime;
        currentSpeed = Mathf.Clamp(currentSpeed, 0f, maxSpeed);
    } else {
        currentSpeed -= deceleration * Time.deltaTime;
        currentSpeed = Mathf.Clamp(currentSpeed, 0f, maxSpeed);
    }

    transform.Translate(currentSpeed * Vector2.right * Time.deltaTime);

    if (Input.GetKey(KeyCode.W)) {
        transform.Translate(Vector2.up * Time.deltaTime);
    } else if (Input.GetKey(KeyCode.S)) {
        transform.Translate(Vector2.down * Time.deltaTime);
    }
}

}

In this code, we first add a public float variable called deceleration, which determines how fast the car slows down when the player releases the ‘D’ key. We then modify the Update() function to decrease the currentSpeed variable by the deceleration value multiplied by the Time.deltaTime property when the ‘D’ key is not being held down.

To make the car keep going forwards or backwards while also moving up and down simultaneously, you can use the transform.Translate() method to move the car in the up or down direction (depending on whether the ‘W’ or ‘S’ key is being held down) while also moving it in the forward direction (which is the direction that the car is facing) by multiplying the right vector (Vector2.right) by the currentSpeed variable and the Time.deltaTime property. Here is an example code snippet:

public class CarController : MonoBehaviour {
public float acceleration = 5f;
public float deceleration = 5f;
public float maxSpeed = 60f;
public float upDownSpeed = 2f;
private float currentSpeed = 0f;

void Update() {
    if (Input.GetKey(KeyCode.D)) {
        currentSpeed += acceleration * Time.deltaTime;
        currentSpeed = Mathf.Clamp(currentSpeed, 0f, maxSpeed);
    } else {
        currentSpeed -= deceleration * Time.deltaTime;
        currentSpeed = Mathf.Clamp(currentSpeed, 0f, maxSpeed);
    }

    transform.Translate(currentSpeed * Vector2.right * Time.deltaTime);

    if (Input.GetKey(KeyCode.W)) {
        transform.Translate(Vector2.up * upDownSpeed * Time.deltaTime);
    } else if (Input.GetKey(KeyCode.S)) {
        transform.Translate(Vector2.down * upDownSpeed * Time.deltaTime);
    }
}

}

In this code, we first add a public float variable called upDownSpeed, which determines how fast the car moves up and down. We then modify the Update() function to move the car in the up or down direction (depending on whether the ‘W’ or ‘S’ key is being held down) by multiplying the up vector (Vector2.up) or the down vector (Vector2.down) by the upDownSpeed variable and the Time.deltaTime property.

I hope any of this helps!

I’m using keys such as W or S but if you want to be up and down arrows just change the key code value.

Unfortunately, this is not the solution. I’ve attached a video of a game that I’m trying to reference. Maybe you can take a look at the video and determine how the car is moving.

Gameplay Reference

1 Like

What is ‘not the solution’? Quickly looking at @Christopher_Powell’s code and the video you supplied, it looks like it should be fine. Except that there’s no deceleration in the video. What exactly is not working to your liking?


Edit
Unless you mean the abrupt stop when the car crashes

1 Like

The car’s movement is weird. It launches to the top when I press D. Also the car is not moving up and down properly. It slides and doesn’t stop, just like when I press the ‘D’ button.

1 Like

OK, I had a look at the video again (with a more ‘analytic eye’ this time).

The way this works is that the road and other cars are moving from right to left, gradually increasing in speed until it reaches the maximum speed. For that you would have to create a script that will endlessly create the road and add random cars.

The player car itself just moves on the x and y axis based on WASD input. It’s not technically going anywhere. It’s just moving around the same origin while the road moves underneath it.

If you give me a few minutes I can get you some scripts going

1 Like

Can we not make the player move instead of the environment? Same gameplay, but the player moves instead of the environment. I think that would be a bit easy for a beginner like me.

1 Like

You can, but you’ll run out of space (if you do the same game as in the video) and there are still other things to consider.


So, there are quite a few things that went into it. I really put this together rather quickly so it is a little messy.

First, I added a sprite, clipped the image from the video and used that as the player car. I have the sprite as a child of the root object
image
The root object has the PlayerController script that controls the car on the screen

public class PlayerController : MonoBehaviour
{
    [SerializeField] float _moveSpeed = 10f;

    bool _isActive = true;

    private void Update()
    {
        // If the car is not active, do nothing
        if (!_isActive) return;

        // Get the movement from WASD
        var moveVector = new Vector3(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")).normalized;
        // calculate the new position
        var position = transform.position;
        position += moveVector * _moveSpeed * Time.deltaTime;

        // Keep the car within the bounds. These values should really be set in the inspector
        // or calculated from the screen. If the car is outside, don't move it
        if (position.x <= -8.05f || position.x >= 8.05f || position.y <= -4.05f || position.y >= 4.05f) return;

        // set the new position
        transform.position = position;
    }

    public void Crash()
    {
        // this is used when we crash. Disable control and play a routine to spin the car
        if (!_isActive) return;
        _isActive = false;
        StartCoroutine(CrashRoutine());
    }

    private IEnumerator CrashRoutine()
    {
        // this is the routine that spins the car. when it's done, it resets the car's rotation
        // and allows control again
        var crashTime = 1f; // spin for about 1 second
        for (var timer = 0f; timer / crashTime <= 1f; timer += Time.deltaTime)
        {
            // spin 3 times
            transform.rotation = Quaternion.Euler(0f, 0f, Mathf.Lerp(0f, 360f * 3, timer / crashTime));
            yield return null;
        }
        transform.rotation = Quaternion.Euler(Vector3.zero);
        _isActive = true;
    }
}

Next, I just made up a road slice to use to construct the road. More shameless video-clipping theft occurred here
image
All the sprites here have their order set to -10 so that they appear behind everything
image

I made this into a prefab so that I can spawn many of these in the road spawner

The environment consists of 2 parts:

  • A road spawner, and
  • A car spawner - for the other cars

The road spawner spawns pieces of road to the right of the screen, and then moves them across to the left. When they reach a defined position, they are moved back to the right to repeat the process. Here is the script (this script also move the cars that will be spawned by the car spawner later. It goes on a separate game object in the scene

public class RoadSpawner : MonoBehaviour
{
    [SerializeField] float _spawnX;
    [SerializeField] float _destroyX;
    [SerializeField] float _roadWidth;
    [SerializeField] GameObject _roadPrefab;
    [SerializeField] float _acceleration = 0.001f;
    [SerializeField] float _maxSpeed = 1f;

    private bool _isActive = false;
    private float _currentSpeed = 0f;

    private void Awake()
    {
        InitialiseRoad();
    }

    private void Update()
    {
        // slowly increase the speed of the road
        _currentSpeed = Mathf.Min(_maxSpeed, _currentSpeed + _acceleration * Time.deltaTime);
        // move the road
        MoveRoad();
    }

    public void Stop()
    {
        // this resets the speed when the player crashes. We'll use this later
        _currentSpeed = 0f;
    }

    private void InitialiseRoad()
    {
        // create the road pieces that will be moved
        var offset = _destroyX;
        while (offset <= _spawnX)
        {
            var pos = new Vector3(offset, 0f, 1f);
            Instantiate(_roadPrefab, pos, Quaternion.identity, transform);
            offset += _roadWidth;
        }
    }

    private void MoveRoad()
    {
        // Move the road
        foreach (Transform child in transform)
        {
            child.Translate(Vector2.left * _currentSpeed);
            if (child.CompareTag("Car")) continue; // ignore cars. we don't want to move them to the front
            if (child.position.x < _destroyX) child.position = new Vector3(_spawnX, 0f, 0f);
        }
    }
}

This now creates a road that moves from right to left. The player can also ‘drive’ around on this road
car_road

Next we need to spawn some cars. I just made another prefab with a sprite (as before) but it doesn’t get the player controller script. It gets a OtherCarController script to control it. It really just moves it in the correct direction and destroys it when it goes off the back of the screen

public class OtherCarController : MonoBehaviour
{
    [SerializeField] float _speed = 2.5f;
    float _direction = 1f;

    float _startX;

    private void Awake()
    {
        _startX = transform.position.x;
    }

    public void SetDirection(float direction)
    {
        // we'll set this when we spawn the car
        _direction = Mathf.Sign(direction);
        transform.localScale = new Vector3(_direction, 1f, 1f);
    }

    private void Update()
    {
        // Move the car. If it's moved off the back, destroy it
        transform.Translate(Vector3.right * _speed * Time.deltaTime * _direction);
        if (Mathf.Abs(transform.position.x) > _startX + 1f) Destroy(gameObject);
    }
}

We also need a script to spawn them. Initially I was just spawning cars everywhere, but they appear to be in lanes, so I added ‘spawn points’ where the should spawn. These have a CarSpawnPoint script on them

public class CarSpawnPoint : MonoBehaviour
{
    [SerializeField] int _direction = 1;

    public void SpawnCar(OtherCarController prefab, Transform parent)
    {
        var car = Instantiate(prefab, transform.position, Quaternion.identity);
        car.transform.SetParent(parent, true);
        car.SetDirection(_direction);
    }
}

In the video you linked, the top cars move from left to right, even though the environment movement pushes them to the left. This result in them moving back slower then the bottom cars which move from right to left - along with the environment movement. Each spawn point have the direction the car will move in. It can then spawn a car when instructed to do so (the car spawner will do that). The spawn points are arranged to the right like this (yellow diamonds)
image
The top 4 have their direction set to 1, and the bottom four have their direction set to -1. They sit in their own parent game object. This is what the hierarchy looks like
image

Next, we will spawn some cars. What I did here was to have a little cooldown and only spawn a car every second

public class CarSpawner : MonoBehaviour
{
    [SerializeField] OtherCarController _carPrefab; // the other car prefab
    [SerializeField] CarSpawnPoint[] _carSpawnPoints; // a list of all the spawn points
    [SerializeField] float _spawnInterval = 1f; // how often to spawn a car

    float _spawnCooldown = 0f;

    private void Update()
    {
        _spawnCooldown += Time.deltaTime;
        if (_spawnCooldown < _spawnInterval) return;
        SpawnRandomCar();
        _spawnCooldown = 0f;
    }

    private void SpawnRandomCar()
    {
        // Pick a random spawn point and ask it to spawn a car
        _carSpawnPoints[Random.Range(0, _carSpawnPoints.Length)].SpawnCar(_carPrefab, transform);
    }
}

Hook everything up in the inspector and we have cars flying at us

car_road_cars

The crash? Well, let’s do that.

On the ‘other car’ I added a box collider and set it to ‘Is Trigger’.

I did the same for the player car, but I also added a Rigidbody2D to the player and set it to ‘Kinematic’


This is because we need a rigidbody to detect collisions.

On the player car, I added a CollisionDetector script

public class CollisionDetector : MonoBehaviour
{
    [SerializeField] PlayerController _playerController;
    [SerializeField] RoadSpawner _roadSpawner;

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (!collision.gameObject.CompareTag("Car")) return;
        _roadSpawner.Stop();
        _playerController.Crash();
        Destroy(collision.gameObject);
    }
}

This will detect a collision, stop the road and tell the player to spin. We set these up previously in the scripts

Here’s a video with the ‘final’ thing

I’m sure this is very vague and I did smash it together in a short time.

1 Like

Oh I guess I’m seeing now that the code I gave would be a simple up/down left/right type of movement, whereas it seems as if you want a more fluid motion, luckily it seems like @bixarrio provided the “Solution” for you!

Thanks for the reply. But, after I’ve applied the RoadSpawner script to my Road Prefab, it seems like Unity is not initializing my game for some reason. It freezes as soon as I hit play. Does this have something to do with Video Memory or something of that matter. I’ve never experienced this before.

1 Like

Some things to troubleshoot:

Check for errors in the console: When Unity freezes, it may be due to an error in your code. Check the console window for any error messages that may provide insight into what’s causing the issue.

Check your resource usage: High resource usage, such as high video memory usage, can cause Unity to freeze. Check your resource usage in the Task Manager or Activity Monitor to see if there are any spikes or high usage.

Check your code for infinite loops: If your code is stuck in an infinite loop, it can cause Unity to freeze. Check your code for any loops that may not terminate.

Simplify your code: If your code is too complex, it may be causing Unity to freeze. Try simplifying your code to see if that helps resolve the issue.

Disable the RoadSpawner script: Try disabling the RoadSpawner script and see if the game initializes properly. If it does, it may be an issue with the script itself.

Hope this helps!

The RoadSpawner script does not go on the Road prefab. It goes on a game object in the scene.

It’s my bad, I did say it goes on a separate game object in the scene, but I may not have explained everything properly


Edit

It froze because the road prefab had a spawner on it - which it shouldn’t have had. The road spawner would create road instances which had more spawners on it. These would then create more road instances which had more spawners on it. All this happened in a single frame which never ended because it was just creating more spawners that created more spawners that created more spawners, etc.

1 Like

I already have that setup, i need to add some sort of constraints, if you watch the videos like shakedown miami and super pixel racers you will notice how the vehicles move to the sprites not rotating a static image.
At the moment if i press up, i can flip the car on the spot to go down i dont want this, same for if im going left
and press right it will flip it on the spot.

1 Like

Here is the project:

Perhaps it would be easier to just look at everything there to see how it goes together

1 Like

What did you attach in the ‘Car Prefab’ slot in the Car Spawner script? I can see a script attached to it but not a prefab. Also, how do I define the sprite for my “Other Cars”? I want to have multiple sprites that I want to use and want the game to randomly choose between those sprites and generate them. I have a few other questions but let me clear this one up.

Thank you.

Edit: And where does the ‘Other Car Controller’ script go? Does it go on the Road game object?

1 Like

The other car prefab

Like I said, I made another prefab similar to the player car but it didn’t get the player controller script, but a OtherCarController script.

Once you have the ‘other car’ prefab you can create prefab variants and just change the sprite for each to a different one. Then you can update the car spawner to have a list of prefabs (instead of just one) and pick one from there. Here’s an updated CarSpawner

public class CarSpawner : MonoBehaviour
{
    [SerializeField] OtherCarController[] _carPrefabs; // the other car prefabs
    [SerializeField] CarSpawnPoint[] _carSpawnPoints; // a list of all the spawn points
    [SerializeField] float _spawnInterval = 1f; // how often to spawn a car

    float _spawnCooldown = 0f;

    private void Update()
    {
        _spawnCooldown += Time.deltaTime;
        if (_spawnCooldown < _spawnInterval) return;
        SpawnRandomCar();
        _spawnCooldown = 0f;
    }

    private void SpawnRandomCar()
    {
        // Pick a random car
        var chosenCar = _carPrefabs[Random.Range(0, _carPrefabs.Length)];
        // Pick a random spawn point and ask it to spawn the chosen car
        _carSpawnPoints[Random.Range(0, _carSpawnPoints.Length)].SpawnCar(chosenCar, transform);
    }
}

It goes on the other car prefab.


Edit
I have updated the repository with the multi-‘other car’ change

1 Like

So I built a very early version of the game out, and I am seeing a couple of problems.

  1. The road in the Unity Editor seems to be moving way faster than in the exported build. This is also happening with the ‘Player Car’.

  2. The Car Spawner, for some reason; is generating a cluster of cars after probably around 1 - 2 seconds after the game starts (no cars appear for 1 - 2 seconds after the game starts, which is something I’m fine with). After this initial cluster, it goes back to normal.

I’m so sorry for troubling you guys. I know this is probably overwhelming. I am just new, trying to build games that I want to build. I’ve attached a screenshot for reference.

Thanks for the help.

1 Like

Privacy & Terms