# CSL LAB03 Mario Kart
```unity=
// PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
// Player Property Variable
private Rigidbody _rb;
// Forward Speed Variable
[SerializeField]
private float _currentSpeed = 0f;
[Tooltip("The maximum forward speed the kart can reach.")]
public float MaxSpeed;
[Tooltip("The minimum forward speed the kart can reach.")]
public float MinSpeed;
[Tooltip("The acceleration.")]
public float Acceleration;
// Rotate speed variable
private Vector3 _turnAngle;
public float MaxRotationAngle;
public float RotationSpeed;
public Transform Hands;
public Transform FrontLeftWheel;
public Transform FrontRightWheel;
// Start is called before the first frame update
void Start()
{
_rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void FixedUpdate()
{
Steer();
Move();
}
// Move the player forward
private void Move()
{
// Calculate current speed
if(Input.GetKey(KeyCode.W)) // Move forward when press W
{
_currentSpeed = Mathf.Lerp(_currentSpeed, MaxSpeed, Time.fixedDeltaTime * Acceleration * 1f);
}
else if(Input.GetKey(KeyCode.S)){ // Move backward when press S
_currentSpeed = Mathf.Lerp(_currentSpeed, MinSpeed, Time.fixedDeltaTime * Acceleration * 2f);
}
else{ // Slow down when no key's pressed
_currentSpeed = Mathf.Lerp(_currentSpeed, 0, Time.fixedDeltaTime * Acceleration * 6f);
}
RotateRigidBody();
Vector3 vel = transform.forward * _currentSpeed;
vel.y = _rb.velocity.y; // Keep the gravity
_rb.velocity = vel; // Apply the speed to the rigidbody
}
private float RegularizeAngle(float angle)
{
// equil to angle = (angle+540)%360 - 180;
angle = (angle > 180) ? angle-360 : angle;
angle = (angle < -180) ? angle+360 : angle;
return angle;
}
private void RotateVisual(float targetAngle, float rotateSpeed)
{
float handAngle = RegularizeAngle(Hands.localRotation.eulerAngles.z);
float wheelAngle = RegularizeAngle(FrontLeftWheel.localRotation.eulerAngles.y);
Hands.Rotate(0, 0, (targetAngle-handAngle) * Time.fixedDeltaTime * rotateSpeed, Space.Self);
FrontLeftWheel.Rotate(0, (-targetAngle-wheelAngle) * Time.fixedDeltaTime * rotateSpeed, 0, Space.Self);
FrontRightWheel.Rotate(0, (-targetAngle-wheelAngle) * Time.fixedDeltaTime * rotateSpeed, 0, Space.Self);
}
private void Steer()
{
if(Input.GetKey(KeyCode.A))
{
RotateVisual(MaxRotationAngle, RotationSpeed * 1f);
}
else if(Input.GetKey(KeyCode.D))
{
RotateVisual(-MaxRotationAngle, RotationSpeed * 1f);
}
else
{
RotateVisual(0, RotationSpeed * 3f);
}
}
private void RotateRigidBody()
{
_turnAngle = FrontLeftWheel.eulerAngles - transform.eulerAngles;
_turnAngle.y = RegularizeAngle(_turnAngle.y);
Quaternion deltaRotation = Quaternion.Euler(Mathf.Sign(_currentSpeed) * _turnAngle * Time.fixedDeltaTime);
_rb.MoveRotation(_rb.rotation * deltaRotation);
}
}
```
```unity=
// CheckPoint.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CheckPoint : MonoBehaviour
{
public AudioClip CheckPointSE;
private void OnTriggerEnter(Collider other)
{
AudioSource.PlayClipAtPoint(CheckPointSE, other.gameObject.transform.position);
// Debug.Log("Passed CheckPoint.");
transform.parent.GetComponent<LevelManager>().LoadCheckPoint();
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
```
```unity=
// LevelManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LevelManager : MonoBehaviour
{
[Tooltip("The start/finish line.")]
public GameObject StartFinishLine;
[Tooltip("The list of checkpoints.")]
public GameObject[] CheckPointList;
private int _gameState = -1;
private int _checkPointNum;
private Collider _startLineCollider;
private float _startTime;
private bool _isPlaying = false;
public Text TimerText;
// Start is called before the first frame update
void Start()
{
_checkPointNum = CheckPointList.Length;
for(int i=0; i<_checkPointNum; i++)
{
CheckPointList[i].SetActive(false);
}
_startLineCollider = StartFinishLine.GetComponent<Collider>();
}
public void LoadCheckPoint()
{
if (_gameState == -1)
{
_startTime = Time.time;
_isPlaying = true;
_startLineCollider.enabled = false;
}
else if (_gameState == _checkPointNum)
{
Debug.Log("Finished!");
_isPlaying = false;
_startLineCollider.enabled = false;
return;
}
else
{
Debug.Log(_gameState);
CheckPointList[_gameState].SetActive(false);
}
_gameState++;
if(_gameState == _checkPointNum)
{
_startLineCollider.enabled = true;
}
else
{
CheckPointList[_gameState].SetActive(true);
}
}
private void UpdateTimer()
{
float currentTime = Time.time - _startTime;
currentTime = Mathf.Max(0, currentTime);
float minutes = (int)(currentTime/60);
float seconds = currentTime % 60;
TimerText.text = string.Format("{0:0}:{1:00.00}", minutes, seconds);
}
// Update is called once per frame
private void Update()
{
if (_isPlaying)
{
UpdateTimer();
}
}
}
```