Night Racer
What is Night Racer?
For this project, we needed to create a racing game. Together with another developer and three artists, we created this race game. For this project we got 5/6 weeks, we decided to make a cyberpunk like race game. Where you drive a hover craft over a high speed sci-fi track. Our goal was to polish the game even more, but unfortunately we did not have enough time to do so.
Hovercraft movement
using UnityEngine;
public class CarMovement : MonoBehaviour
{
[Header("Ship Handeling")]
[SerializeField] private float accelaration = 50f;
[SerializeField] private float maxSpeed = 200f;
private float oldMaxSpeed;
[SerializeField] private float maxBoost = 300f;
[SerializeField] private float brakeSpeed = 70f;
[SerializeField] private float turnSpeed = 10f;
[SerializeField] private float airTurnSpeed = 3f;
private float input = 0f; // horizontal axis
[SerializeField] private float currentSpeed;
private bool isDead;
private bool canBoost;
[Header("Hovering")]
[SerializeField] private LayerMask groundLayer; // layer we can hover on
private Rigidbody rb;
private Vector3 prevUpDir; // stores transform.up
[SerializeField] private float hoverHeight = 3f; // distance from the ground
[SerializeField] private float heightSmoothing = 10f; // how fast the ship will readjust to "Hoverheight"
[SerializeField] private float normalRotSmoothing = 50f; // how fast the ship will adjust its rotatioin to match ground normal
private float smoothY = 1f;
[SerializeField] private float startDelay = 0.2f;
[SerializeField] private bool isGrounded;
[Header("dropping")]
[SerializeField] private float dropOffTime = 0.2f;
private bool isDroppingOff;
private float oldDropOffTime;
[SerializeField] private float rotationLerp = 7f; // how fast we will rotate towards the ground if the ship is in the air
[SerializeField] private float positionLerp = 5f; // how fast ship will sink down if is in the air
private float oldPositionLerp;
private bool dropOffForceAdded;
private void Start()
{
// ship handeling
currentSpeed = 0f;
oldMaxSpeed = maxSpeed;
canBoost = true;
isDead = false;
//hovering
rb = GetComponent<Rigidbody>();
//rb.useGravity = false;
isGrounded = true;
//dropping
isDroppingOff = false;
oldDropOffTime = dropOffTime;
oldPositionLerp = positionLerp;
dropOffForceAdded = false;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
DropOff();
}
if (startDelay >= 0f)
{
startDelay -= Time.deltaTime;
}
input = Input.GetAxis("Horizontal");
if (startDelay <= 0)
{
DrivingBehaviour();
}
}
private void DrivingBehaviour()
{
if (isDead)
{
//stops movement if dead
currentSpeed = 0;
rb.velocity = Vector3.zero;
GetComponent<CarMovement>().enabled = false;
transform.gameObject.isStatic = true;
return;
}
if (isDroppingOff)
{
//can boost while on ground
canBoost = true;
//input
if (HasInput())
{
// moving
if (!IsBoosting())
{
maxSpeed = oldMaxSpeed;
Accelerate(accelaration);
}
else
{
maxSpeed = maxBoost;
Accelerate(accelaration * 2);
}
if (currentSpeed >= maxSpeed)
{
Brake(brakeSpeed);
}
}
else
{
// no input? brake
Brake(brakeSpeed);
}
// set velocity to zero if not moving and if not dropping off
if (currentSpeed <=0f && isGrounded)
{
rb.velocity = Vector3.zero;
}
// get current up dir for fixedupdate calculations
prevUpDir = transform.up;
}
else
{
// if dropping off stop boosting
canBoost = false;
//add little force to velocity once we take off
if (!dropOffForceAdded)
{
rb.velocity += new Vector3(0, 250, 0);
dropOffForceAdded = true;
}
//start brake, so we cant fly in the air
// also decrease brakesped a little amount so we can glide a little longer
Brake(brakeSpeed / 1.5f);
// measure time we are in the air
dropOffTime -= Time.deltaTime;
}
}
private void FixedUpdate()
{
if (isDead)
{
return;
}
// turning
if (IsTurning())
{
if (isDroppingOff)
{
TurnShip(airTurnSpeed);
}
else
{
TurnShip(turnSpeed);
}
}
else
{
// if we dont turn, then stop the angular rotation
rb.angularVelocity = Vector3.Lerp(rb.angularVelocity, Vector3.zero, 0.05f * Time.deltaTime);
}
// if we are not in the air
if (!isDroppingOff)
{
//normal alignment
RaycastHit hit;
if (Physics.Raycast(transform.position, /*-prevUpDir*/ Vector3.down * 10, out hit, 10f, groundLayer))
{
// we hit the groudn
isGrounded = true;
//smooth new up vector
Vector3 desiredUp = Vector3.Lerp(prevUpDir, hit.normal, Time.deltaTime * normalRotSmoothing); // fixedDeltaTime??\
//get the angle
Quaternion tilt = Quaternion.FromToRotation(transform.up, desiredUp) * transform.rotation;
// apply rotation
transform.localRotation = tilt;
// smoothly adjust height
smoothY = Mathf.Lerp(smoothY, hoverHeight - hit.distance, Time.deltaTime * heightSmoothing); // fixedDeltaTime??
smoothY = smoothY <= 0.01f ? 0f : smoothY;
transform.localPosition += prevUpDir * smoothY;
}
else
{
// if we dont hit anything that means we are in the arir
DropOff();
}
}
else
{
//if we are in the air
RaycastHit rotationHit;
// rotate towards ground normal
if (Physics.Raycast(transform.position, Vector3.down * 10, out rotationHit, groundLayer))
{
Vector3 desiredUp = Vector3.Lerp(transform.up, Vector3.up, Time.deltaTime * normalRotSmoothing); // fixedDeltatime?
// get the angel
Quaternion tilt = Quaternion.FromToRotation(transform.up, desiredUp) * transform.rotation;
//apply rotation
transform.localRotation = Quaternion.Lerp(transform.localRotation, tilt, rotationLerp * Time.deltaTime); // fixedDeltaTime?
Debug.DrawRay(transform.position, Vector3.down * 10, Color.red);
Debug.Log(groundLayer);
}
// only sink for a given amount ot time so we dont get extremely high values
if (dropOffTime <= 0)
{
// increase lerp value over time so we sink faster and faster
positionLerp += 10 * Time.deltaTime; // fixed delta time?
//start sinking
transform.localPosition += Vector3.down * 9.81f * positionLerp * Time.deltaTime; // fixed?
RaycastHit hit;
// check distance ground
if (Physics.Raycast(transform.position, Vector3.down * 10, out hit, groundLayer))
{
// if we are at our hoverheight againt
if (hit.distance <= hoverHeight)
{
dropOffTime = oldDropOffTime;
// reset bool for next dropoff
dropOffForceAdded = false;
positionLerp = oldPositionLerp;
//get back to the ground
Debug.Log("raycast" + hit);
DropOn();
}
}
}
}
// now actually move the ship
MoveShip(currentSpeed * 80);
}
private void TurnShip(float speed)
{
rb.AddTorque(prevUpDir * speed * input * Time.fixedDeltaTime, ForceMode.Impulse);
}
private bool IsTurning()
{
return (Input.GetAxis("Horizontal") > 0 || Input.GetAxis("Horizontal") < 0);
}
private void Accelerate(float speed)
{
// if current speed is greater than max speed, only add 0f so we dont get faster and faster
// else apply our acceleration
currentSpeed += (currentSpeed >= maxSpeed) ? 0f : speed * Time.deltaTime;
}
private void MoveShip(float speed)
{
// move ship forard with current speed
rb.velocity = transform.forward * speed * Time.fixedDeltaTime;
}
private void Brake(float brakeSpeed)
{
if (currentSpeed > 0)
{
//decrease only if it's greater than 0, so we dont start flying backwards
currentSpeed -= brakeSpeed * Time.deltaTime;
}
else
{
//set speed to 0 if we cant brake anymore
currentSpeed = 0;
}
}
private bool HasInput()
{
return (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow));
}
private bool IsBoosting()
{
//only boost if we get correct input and we can boost
return ((Input.GetKey(KeyCode.B)) && canBoost);
}
private void DropOff()
{
if (!isDroppingOff)
{
//not on the ground
isGrounded = false;
isDroppingOff = true;
}
}
private void DropOn()
{
// on the ground again
isGrounded = true;
isDroppingOff = false;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
public class LapHandler : MonoBehaviour
{
[SerializeField] private int cpAmmount;
public GhostHolder holder;
public float lapTime;
private void Awake()
{
PlayerPrefs.SetFloat("LastLap", 0);
}
private void Start()
{
lapTime = -3f;
}
private void FixedUpdate()
{
lapTime += Time.deltaTime;
if (Input.GetKeyDown(KeyCode.O))
{
SaveToJSON();
}
}
private void OnTriggerEnter(Collider other)
{
OnFinish(other);
}
private void OnFinish(Collider other)
{
if (other.GetComponent<KartLap>())
{
KartLap kart = other.GetComponent<KartLap>();
if (kart.CheckpointIndex == cpAmmount)
{
kart.CheckpointIndex = 0;
UIManager.Instance.UpdateLapCountUI();
UIManager.Instance.UpdateCheckpointUI();
SaveLastLapTime();
SaveRecordTime();
ResetLapTime();
}
}
}
private void ResetLapTime()
{
lapTime = 0;
}
private void SaveLastLapTime()
{
PlayerPrefs.SetFloat("LastLap", lapTime);
UIManager.Instance.UpdateLastLapUI();
}
private void SaveRecordTime()
{
float recordTime = PlayerPrefs.GetFloat("RecordTime");
if (recordTime == 0 || lapTime <= PlayerPrefs.GetFloat("RecordTime"))
{
PlayerPrefs.SetFloat("RecordTime", lapTime);
UIManager.Instance.UpdateRecordLapUI();
Debug.Log(PlayerPrefs.GetFloat("RecordTime"));
}
}
public void SaveToJSON()
{
string ghostPos = JsonUtility.ToJson(holder.ghostPos);
string filePath = Application.persistentDataPath + "/GhostPos.data";
Debug.Log(filePath);
System.IO.File.WriteAllText(filePath, ghostPos);
Debug.Log("Ghost Saved");
}
}
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
public class UIManager : MonoBehaviour
{
#region singleton
public static UIManager Instance;
#endregion
[SerializeField] private TextMeshProUGUI lapCount;
[SerializeField] private TextMeshProUGUI checkpoint;
[SerializeField] private TextMeshProUGUI lapTime;
[SerializeField] private TextMeshProUGUI lastLap;
[SerializeField] private TextMeshProUGUI recordLap;
[SerializeField] private TextMeshProUGUI speedText;
[SerializeField] private TextMeshProUGUI countDown;
[SerializeField] private GameObject[] countDownOverlay;
[SerializeField] private GameObject[] UI;
[SerializeField] private Slider slider;
[SerializeField] private GameObject speedOutline;
[SerializeField] private GameObject mainButton;
[SerializeField] private GameObject resumeButton;
private float speedMeter;
[SerializeField] public int countDownTime = 3;
private bool isPause = false;
[SerializeField] private AudioSource startAudio;
KartLap kartLap;
LapHandler lapHandler;
Movement movementScript;
[SerializeField] private GameObject player;
private void Awake()
{
Instance = this;
}
public void Start()
{
StartCoroutine(StartCountDown());
player = GameObject.FindGameObjectWithTag("Player");
movementScript = player.GetComponent<Movement>();
//kartLap = player.GetComponent<KartLap>();
//movementScript = GameObject.Find("Player").GetComponent<Movement>();
kartLap = GameObject.FindGameObjectWithTag("Player").GetComponent<KartLap>();
//_lapHandler = GameObject.FindGameObjectWithTag("Finish");
lapHandler = GameObject.FindGameObjectWithTag("Finish").GetComponent<LapHandler>();
UpdateCheckpointUI();
UpdateLapCountUI();
UpdateLastLapUI();
UpdateRecordLapUI();
UpdateSpeedMeter();
}
private IEnumerator StartCountDown()
{
DeactivateUI();
while(countDownTime > 0)
{
countDown.text = countDownTime.ToString();
//Overlay();
if (countDownTime == 3)
{
startAudio.Play();
countDownOverlay[0].SetActive(true);
countDownOverlay[1].SetActive(false);
countDownOverlay[2].SetActive(false);
}
else if (countDownTime == 2)
{
countDownOverlay[1].SetActive(true);
countDownOverlay[2].SetActive(false);
countDownOverlay[0].SetActive(false);
}
else if (countDownTime == 1)
{
countDownOverlay[2].SetActive(true);
countDownOverlay[1].SetActive(false);
countDownOverlay[0].SetActive(false);
}
yield return new WaitForSeconds(1f);
countDownTime--;
}
countDown.text = "GO!";
yield return new WaitForSeconds(1f);
ActivateUI();
countDown.gameObject.SetActive(false);
for (int i = 0; i < countDownOverlay.Length; i++)
{
countDownOverlay[i].SetActive(false);
}
}
private void ActivateUI()
{
for (int i = 0; i < UI.Length; i++) // activeert alle elementen die in de ui array zitten
{
UI[i].gameObject.SetActive(true);
}
}
private void DeactivateUI()
{
for (int i = 0; i < UI.Length; i++) // deactiveert alle elementen die in de ui array zitten
{
UI[i].gameObject.SetActive(false);
}
}
public void UpdateSpeedMeter()
{
speedMeter = movementScript.currentSpeed;
slider.value = speedMeter;
}
public void UpdateLapCountUI()
{
//lapCount.text = "Lap: " + kartLap.lapNumber.ToString();
}
public void UpdateCheckpointUI()
{
checkpoint.text = "Checkpoint: " + kartLap.CheckpointIndex.ToString();
}
public void UpdateLastLapUI()
{
lastLap.text = "Last Lap: " + PlayerPrefs.GetFloat("LastLap").ToString("F2");
}
public void UpdateRecordLapUI()
{
recordLap.text = "Record Lap: " + PlayerPrefs.GetFloat("RecordTime").ToString("F2");
}
public void PauseGame()
{
if (isPause)
{
Time.timeScale = 0f;
mainButton.gameObject.SetActive(true);
resumeButton.gameObject.SetActive(true);
DeactivateUI();
}
else
{
Time.timeScale = 1;
mainButton.gameObject.SetActive(false);
resumeButton.gameObject.SetActive(false);
ActivateUI();
}
}
public void ResumeButton()
{
isPause = !isPause;
PauseGame();
}
void Update()
{
lapTime.text = "Laptime: " + lapHandler.lapTime.ToString("F2");
UpdateSpeedMeter();
if (Input.GetKeyDown(KeyCode.Escape))
{
isPause = !isPause;
PauseGame();
}
}
}