How to program a 2D car?

If you are new I wouldn’t suggest jumping right into building games, rather to instead learn as much possible and then, trust me it feels much better. I did the same thing as you, I went into gamedev rather new and wanted to build slither.io, I asked for help, found a tutorial and built the game, it was super fun to play it, however I had no idea what was going on, there was for each loops, variables being used confusingly, and a ton of complex instantiation. Now however, after I had taken loads of tutorials in could easily do a snake game, and it feels a lot better.

For your game, you would just have to find the variable that controls the speed of the road and change that, that’s the simple version, also make sure to use time.deltatime, for the cluster of cars, id chack instantiation statements, and functions for the car clusters and tweek the values.

Hope that can lead you in the right direction!

I don’t know why this happens. I’d have to look into it. I have not made a build, just a very quick ‘sample’ game.

Edit
It looks like vsync is messing with the game. I don’t know why. If you turn it off it will run at the same (similar) speed as the editor. I’ve even seen the road being janky if the vsync is on

Check the speed on the ‘other car’ script. Remember that some cars (direction = 1) are moving in the same direction as the player and they spawn in front of the player. That is off the screen to the right. If they are too fast they will stay off screen until the road (player) moves faster than they do and only then will they start moving onto the screen. In the time that they were off the screen, more cars could have spawned where they are, causing a cluster to form

2 Likes

Thanks for the recommendation, Christopher. But how would I learn if I don’t make games?

I don’t understand this. Can you please explain a little more clearly? Thanks.

The road is moving to the left. The car is moving to the right, but it is also being moved to the left by the road. If the car is faster than the road it will stay off the screen because it is fighting the road and winning. Eventually the road is moving fast enough to win the race, so the car starts moving to the left because it’s now slower than the road. This is when it starts to appear on the screen. In the time that it was winning, more cars spawned and they are all bunched up.

Think of it like running on a treadmill. If you run faster than the treadmill, you will move forward. If you run the same speed as the treadmill, you will stay where you are. And if you run slower than the treadmill, you will fall off the back. At the start, the cars are faster than ‘the treadmill’ but ‘the treadmill’ is speeding up. Every second another runner gets on ‘the treadmill’ and because no-one has fallen off yet we are starting to bunch up on ‘the treadmill’. When ‘the treadmill’ gets fast enough, we fall off in a bunch, but people are still getting on ‘the treadmill’ and are no longer bunching up because everyone is falling off.

The faster the ‘other car’ is, the longer it will take for the road to push them backwards. This is only a problem for cars moving to the right. cars moving to the left move in the same direction as the road and will move themselves onto the screen

That’s a good point, I would recommend learning because it’s very fun and very rewarding, in my opinion, however if you don’t know how to code, which is fine, there’s plenty of game making studios that are really simple and require no code, that would still allow you to build games without actually knowing how to build games, but like I said you should try, it’s very rewarding, best of luck in your journeys!:grin:

I see. Is there any way of solving this issue? I don’t want to do this but in case there are no other options, what if we can “turn off” defined CarSpawnPoints for a dedicated amount of time and bring them back on after that to avoid the car bunch-up situation?

I love coding. Sometimes it can definitely get frustrating for me. The problem with me is that I have a hard time grasping hard and/or complicated topics. Like if someone is explaining a very difficult syntax or command, I will loose my focus and would rewind that video multiple times. Even then, I can’t figure it. Even though it seems like it should be easy, I still have difficulties trying to focus when I’m grasping hard concepts. But I don’t like giving up.

1 Like

Is there a way for me to update my text when the game starts from ‘0 km/h’ to ‘60 km/h’? You can see (in the screenshot) I have a text field in the bottom that says 0 km/h. Is there a way for me to update the text from ‘0’ to ‘60’ in sequence and be capped at ‘60’. Think of this as like a speedometer. Or maybe if we can do a ‘dynamic speedometer’ where if you press the ‘A’ button, the speed goes down and stays consistent unless you press another button but when you press the ‘D’ button, the speed goes up to 60?

Well if you keep pushing through, then you will be able to figure it out, I believe that you will push through and be able to figure out the syntax, and beyond that, even harder topics!

Yeah, don’t let the cars move too fast.

Sure. Disable the spawning for an amount of time. You’d also need to do this when the car crashes because the road stops and starts again when that happens. To make it a little less messy - it’s gonna get messier if we stick to what it’s doing now - we need to change a few things.

NOTE
The code I post in here differs slightly from the code in the repo because the code in the repo was made after the posts above and I’m trying to stick to what’s posted here. I am updating the code in the repo as we make changes here

The first thing we will do is to delay the start of the car spawning by some amount. We will make more changes here in a moment

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
    [SerializeField] float _startDelay = 1f; // how long to wait before spawning cars

    bool _isActive = false;
    float _spawnCooldown = 0f;

    private void Start()
    {
        StartSpawning();
    }

    private void Update()
    {
        if (!_isActive) return;

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

    private void SetActive()
    {
        _isActive = true;
    }

    private void StartSpawning()
    {
        Invoke(nameof(SetActive), _startDelay);
    }

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

With this, the car spawner will now wait 1 second (default value) before it starts to spawn cars

Next, we need to stop the spawner when the player crashes. We will change a few things here. First thing would be the CollisionDetector. We will no longer tell the road to stop from here. We only let the player car know that we crashed, and destroy the car we crashed with

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

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

We will then update the player car to fire 2 events; one when it crashes and one when it’s recovered from the crash. Because we only have a single player car, I am going to make these events static. It will just make things a little easier for now.

public class PlayerController : MonoBehaviour
{
    public static event System.Action PlayerCrashed;
    public static event System.Action PlayerRecovered;

    [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()
    {
        PlayerCrashed?.Invoke();

        // 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;

        PlayerRecovered?.Invoke();
    }
}

Now, when the player crashes it will send a PlayerCrashed event which we can listen for to know that we should stop the road and the car spawning. When it’s done spinning it will send a PlayerRecovered event that we can use to start the road and car spawning again

Here is the RoadSpawner that will handle the player’s events

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();
        _isActive = true;
    }

    private void OnEnable()
    {
        // Subscribe to the player's events
        PlayerController.PlayerCrashed += OnPlayerCrashed;
        PlayerController.PlayerRecovered += OnPlayerRecovered;
    }

    private void OnDisable()
    {
        // Unsubscribe from the player's events
        PlayerController.PlayerCrashed -= OnPlayerCrashed;
        PlayerController.PlayerRecovered -= OnPlayerRecovered;
    }

    private void Update()
    {
        if (!_isActive) return;

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

    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);
        }
    }

    private void OnPlayerCrashed()
    {
        // The player crashed. Stop the road
        _isActive = false;
        _currentSpeed = 0f;
    }

    private void OnPlayerRecovered()
    {
        // The player recovered, start the road again
        _isActive = true;
    }
}

And the CarSpawner with the changes from earlier

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
    [SerializeField] float _startDelay = 1f; // how long to wait before spawning cars

    bool _isActive = false;
    float _spawnCooldown = 0f;

    private void Start()
    {
        StartSpawning();
    }

    private void OnEnable()
    {
        // Subscribe to the player's events
        PlayerController.PlayerCrashed += OnPlayerCrashed;
        PlayerController.PlayerRecovered += OnPlayerRecovered;
    }

    private void OnDisable()
    {
        // Unsubscribe from the player's events
        PlayerController.PlayerCrashed -= OnPlayerCrashed;
        PlayerController.PlayerRecovered -= OnPlayerRecovered;
    }

    private void Update()
    {
        if (!_isActive) return;

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

    private void SetActive()
    {
        _isActive = true;
    }

    private void StartSpawning()
    {
        Invoke(nameof(SetActive), _startDelay);
    }

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

    private void OnPlayerCrashed()
    {
        // The player crashed. Stop the car spawning
        _isActive = false;
    }

    private void OnPlayerRecovered()
    {
        // The player recovered, start the spawning again
        StartSpawning();
    }
}

With all of this, the road and car spawners will stop doing their things when the player car crashes, and start doing it again when the player car has done its spinning and has recovered

Sure. The speed of the road is _currentSpeed and there is a max called _maxSpeed. We can ‘remap’ these values to be what you want. We can think of it in ‘normalised’ terms. The speed is a value between 0 and max speed, but it’s easier when it’s between 0 and 1. The simplest is to use an inverse lerp;

var speedFactor = Mathf.InverseLerp(0f, _maxSpeed, _currentSpeed);

This is going to give you a number between 0 and 1 that corresponds the the current speed in relation to the max speed. For example, if the max speed is 60km/h and the current speed is 30km/h, the inverse lerp is going to return 0.5f. The current speed is 0.5f times (half) the max speed. You can now just multiply this value with whatever you want your max speed to display as.

[SerializeField] float _displayMaxSpeed = 60f;

private void Update()
{
    var speedFactor = Mathf.InverseLerp(0f, _maxSpeed, _currentSpeed);
    Debug.Log($"Speed: {speedFactor * _displayMaxSpeed:0}km/h");
}

Edit
Note that we don’t have to use an inverse lerp. Our start speed is 0 (which makes it easy) so we could just divide the current speed by the max speed

var speedFactor = _currentSpeed / _maxSpeed;
1 Like

I was thinking if we can do it a bit more easily. Is there not a way where I can start updating my Speed Text Field in the Update() function from when the game starts and then just cap the value when it reaches the number of 60? Sorry for explaining it badly earlier, I should’ve been more clear.

Edit: I also wanted to know what the Road Width & Acceleration floats are used for? I’m kinda confused as to what purpose do they serve clearly.

You can, but then it will be wrong and not show the actual speed.

The acceleration is how fast the road speeds up. The current speed increases by this amount every second until it reaches the maximum speed.

The road width is the width of the road segment in the prefab. The width is used to calculate where each road piece goes so that they are placed next to each other.

Instead of debugging, I want to display it in a textmeshpro object. I’m using this code but obviously it’s wrong.

displaySpeedText.SetText (speedFactor * logMaxSpeed.ToString() + " KM/H");

*logMaxSpeed is displayMaxSpeed in your code. I changed it according to my needs.

displaySpeedText.SetText($"{speedFactor * logMaxSpeed:0} KM/H");

Hey, sorry for not posting for so long. The last week’s been very busy.

I’ve added this but unfortunately, it’s not working as intended. The speedometer goes up very very slowly and is not working properly. I was thinking if we can go back to the original idea of just having the speedometer gradually go up to 60 km as the game starts and just stay at 60. I don’t want to make it much more complicated since there are a few gameplay mechanics left to implement that are much more important.

1 Like

I’m just going to suggest that instead of having little knowledge of unity and asking people to create a game for you, I’d get one of Gamedev tvs courses and once you’re done with one(let’s say the 2D one) you’d easily be able to build this game in your sleep. I’m just saying this because it feels like this thread will go on forever and you’ll have never gotten your game. It would feel way better to know this stuff and build your own game trust me.

-I am not trying to be mean btw, just an honest suggestion-

1 Like

No, it works properly and as intended. It’s slow because it takes a while for the car to get up to speed. You have to tune the values.

Create a variable to hold the current display speed, a serialised variable for how fast you want it to go up, and then increase the value gradually

[SerializeField] float _displaySpeedIncrementSpeed = 1f;

private float _currentDisplaySpeed = 0f;

private void Update()
{
    if (_currentSpeed > 0f)
    {
        _currentDisplaySpeed += Time.deltaTime * _displaySpeedIncrementSpeed;
        _currentDisplaySpeed = Mathf.Min(_displayMaxSpeed, _currentDisplaySpeed);
    }
    else
    {
        _currentDisplaySpeed = 0f;
    }
    _displaySpeedText.SetText($"{_currentDisplaySpeed}KM/H");
}

No I totally understand what you’re saying. And I know that I’ve massively inconvenienced both you @bixarrio , but it’s just that building this project with him has been a big learning experience. And I do own the 2D game course, its just I learn way better when I build games that I want. And you’re absolutely right. I should make these games on my own but the help you & @bixarrio have provided has taught me so much, and its amazing to build what I want. I also have little to no time on most days, so building my own games is the way to go since I understand the concept right away. And even though there are still some stuff that’s needed to be added to the game, to a large degree, it’s almost done.

There are a few things on my hitlist to include now:

  1. Variating traffic sprites so the traffic feels more realistic.

  2. A working High Score system.

  3. Adding ‘Road Puddles’ that spawn the same way as the cars do.

  4. Adding trees and other environmental assets the same way as the cars.

  5. Add a secret little “horror minigame” scene.

1 Like

I apologize for the trouble I’ve caused. I know it’s probably been a nightmare since I am constantly asking for help. But a big shout to @bixarrio & you for helping out a newbie. The way that I do it is I comment the code when I write it. And then after a couple of days I re-visit and study that code since I don’t have enough time to watch and follow videos. I had trouble making this project on my own, but when @bixarrio jumped in and “analyzed” the footage from that minigame, it was such a learning experience for as to how to analyze and approach an idea. And since the game is not done, I’m still a little clueless as to how to implement my check-list above. But, learning about is probably going to help me to implement it in my future projects as well. When the game is done, you guys are definitely securing a spot in the credits. My goal right now is to just finish this game and learn from it as I have. I’ve told a few of my friends and they are looking forward to it. I really hope I can finish this.

(sorry for the rambling by the way.)

1 Like

Privacy & Terms