---
# System prepended metadata

title: CSL LAB03 Mario Kart

---

# 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();
        }
    }
}
```