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;