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
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
All the sprites here have their order set to -10 so that they appear behind everything
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
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)
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
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
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.