using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public enum GearState
{
Neutral,
Running,
CheckChange,
Changing
}
public class Car : MonoBehaviour
{
[Header("Values")]
public float motorPower;
public float weight;
public Transform centerOfMass;
public bool RWD;
public bool FWD;
[Header("Nerd Shit (v metrax)")]
public float wheelBase;
public float rearTrack;
public float turnRadius;
[Header("Wheels")]
public List<Suspension> wheels;
[Header("Drift specs")]
public float driftMultiplier = 1f;
public float driftThreshold = 0.5f;
[Header("Transmission")]
public float RPM;
public float redLine;
public float idleRPM;
public AnimationCurve powerCurve;
[Space]
public float differentialRatio;
public GearState gearState;
public float increaseGearRPM;
public float decreaseGearRPM;
[Space]
public int currentGear;
public float[] gearRatios;
[Header("Debug")]
public TextMeshProUGUI infoText;
[Space]
[SerializeField] private Transform rpmNeedle;
[SerializeField] private float minNeedleAngle;
[SerializeField] private float maxNeedleAngle;
// only editable through other scripts
[HideInInspector] public float horInput, verInput;
[HideInInspector] public bool grounded, drifting;
[HideInInspector] public float num, num2, driftFactor2, speed;
private Rigidbody rb;
private float dir, startForce, startRadius;
private float ackermannAngleLeft, ackermannAngleRight;
private float currentTorque, clutch, wheelRPM;
private int wheelIndex;
private Vector3 XZVector(Vector3 v) { return new Vector3(v.x, 0f, v.z); }
void Start()
{
rb = GetComponent<Rigidbody>();
rb.centerOfMass = centerOfMass.position;
rb.mass = weight;
startForce = motorPower;
startRadius = turnRadius;
}
void Update()
{
HudInit();
if (gearState != GearState.Changing)
{
if (gearState == GearState.Neutral)
{
clutch = 0;
if (verInput > 0) gearState = GearState.Running;
}
else clutch = Input.GetKey(KeyCode.LeftShift) ? 0 : Mathf.Lerp(clutch, 1, Time.deltaTime);
}
if(verInput > 0) gearState = GearState.Running;
//var slipAngle = Vector3.Angle(transform.forward, rb.velocity - transform.forward);
if (GameObject.Find("MobileControls") == null)
{
horInput = Input.GetAxisRaw("Horizontal");
verInput = Input.GetAxis("Vertical");
}
Steering();
CheckGrounded();
WheelInit();
currentGear = Mathf.Clamp(currentGear, 0, gearRatios.Length - 1);
}
private void FixedUpdate()
{
Movement();
}
private void Movement()
{
drifting = false;
if (RWD && FWD) motorPower = startForce / 2;
else motorPower = startForce;
Vector3 vector = XZVector(rb.velocity);
Vector3 vector2 = transform.InverseTransformDirection(XZVector(rb.velocity));
dir = Mathf.Sign(transform.InverseTransformDirection(vector).z);
speed = vector.magnitude * 3.6f * dir;
float num = Mathf.Abs(rb.angularVelocity.y);
foreach (Suspension suspension in wheels)
{
wheelIndex = wheels.IndexOf(suspension);
currentTorque = CalculateTorque();
if (suspension.grounded)
{
Vector3 pointVel = XZVector(rb.GetPointVelocity(suspension.hit.point));
transform.InverseTransformDirection(pointVel);
Vector3 proj = Vector3.Project(pointVel, suspension.transform.right);
float num2 = 1f;
float f = Mathf.Atan2(vector2.x, vector2.z);
float driftThreshold = this.driftThreshold;
if (num > 1f)
{
driftThreshold -= 0.2f;
}
if (Mathf.Abs(f) > driftThreshold)
{
float num4 = Mathf.Clamp(Mathf.Abs(f) * 2.4f - driftThreshold, 0f, 1f);
num2 = Mathf.Clamp(1f - num4, 0.05f, 1f);
float magnitude = rb.velocity.magnitude;
drifting = true;
if (magnitude < 8f)
{
num2 += (8f - magnitude) / 8f;
}
if (num < 0.6f)
{
float num5 = (0.6f - num) / 0.6f;
num2 += num5 * 0.15f;
}
if (Mathf.Abs(verInput) < 0.3f)
{
num2 += 0.1f;
}
num2 = Mathf.Clamp(num2, 0f, 1f);
}
float driftApplier = 1f;
if (drifting) driftApplier = driftMultiplier;
if (RWD)
{
if (suspension.wheel == Suspension.Wheel.rearLeft || suspension.wheel == Suspension.Wheel.rearRight)
rb.AddForceAtPosition(suspension.transform.forward * verInput * currentTorque * driftApplier, suspension.hit.point);
if (drifting && !FWD) turnRadius = 5f; else turnRadius = startRadius;
}
if (FWD)
{
if (suspension.wheel == Suspension.Wheel.frontLeft || suspension.wheel == Suspension.Wheel.frontRight)
rb.AddForceAtPosition(suspension.transform.forward * verInput * currentTorque * driftApplier, suspension.hit.point);
}
Vector3 a2 = proj * rb.mass * num2;
rb.AddForceAtPosition(-a2, suspension.hit.point);
rb.AddForceAtPosition(suspension.transform.forward * a2.magnitude * 0.25f, suspension.hit.point);
float traction = Mathf.Clamp(1f - num2, 0f, 1f);
if (Mathf.Sign(dir) != Mathf.Sign(verInput) && speed > 2f)
{
traction = Mathf.Clamp(traction + 0.5f, 0f, 1f);
}
suspension.traction = traction;
Vector3 dragForce = -3.5f * vector; //-3.5f is drag
rb.AddForce(dragForce);
Vector3 rollForce = -105f * vector; //-105 is rollfriction
rb.AddForce(rollForce);
}
}
StandStill();
}
float CalculateTorque()
{
float torque = 0;
if (RPM < idleRPM + 200 && verInput == 0 && currentGear == 0)
{
gearState = GearState.Neutral;
}
if (gearState == GearState.Running && clutch > 0)
{
if (RPM > increaseGearRPM)
{
StartCoroutine(ChangeGear(1));
}
else if (RPM < decreaseGearRPM)
{
StartCoroutine(ChangeGear(-1));
}
}
if (clutch < 0.1f)
{
RPM = Mathf.Lerp(RPM, Mathf.Max(idleRPM, redLine * verInput) + Random.Range(-50, 50), Time.deltaTime);
}
else
{
wheelRPM = Mathf.Abs(wheels[wheelIndex].wheelRPM * gearRatios[currentGear] * differentialRatio);
RPM = Mathf.Lerp(RPM, Mathf.Max(idleRPM - 100, wheelRPM), Time.deltaTime * 3f);
torque = (powerCurve.Evaluate(RPM / redLine) * motorPower / RPM) * gearRatios[currentGear] * differentialRatio * 5252f * clutch;
}
return torque;
}
private void CheckGrounded()
{
grounded = false;
foreach (Suspension sus in wheels)
{
if (sus.grounded) grounded = true; return;
}
}
void Steering()
{
foreach (Suspension sus in wheels)
{
if (horInput > 0)
{
ackermannAngleLeft = Mathf.Rad2Deg * Mathf.Atan(wheelBase / (turnRadius + (rearTrack / 2))) * horInput;
ackermannAngleRight = Mathf.Rad2Deg * Mathf.Atan(wheelBase / (turnRadius - (rearTrack / 2))) * horInput;
}
else if (horInput < 0)
{
ackermannAngleLeft = Mathf.Rad2Deg * Mathf.Atan(wheelBase / (turnRadius - (rearTrack / 2))) * horInput;
ackermannAngleRight = Mathf.Rad2Deg * Mathf.Atan(wheelBase / (turnRadius + (rearTrack / 2))) * horInput;
}
else
{
ackermannAngleLeft = 0;
ackermannAngleRight = 0;
}
if (sus.wheel == Suspension.Wheel.frontLeft)
sus.steerAngle = ackermannAngleLeft;
if (sus.wheel == Suspension.Wheel.frontRight)
sus.steerAngle = ackermannAngleRight;
}
}
void WheelInit()
{
foreach (Suspension suspension in wheels)
{
float num = suspension.springLength;
float hitHeight = suspension.hit.distance;
float offset;
if (suspension.grounded) offset = 0; else offset = -1;
float y = Mathf.Lerp(suspension.visualWheel.transform.localPosition.y, -hitHeight + num, Time.deltaTime * 20f);
suspension.visualWheel.transform.localPosition = new Vector3(suspension.transform.localPosition.x * transform.localScale.x, -suspension.hit.distance + suspension.wheelRadius + offset, suspension.transform.localPosition.z * transform.localScale.z);
suspension.visualWheel.GetChild(0).Rotate(-Vector3.forward, XZVector(rb.velocity).magnitude * dir);
suspension.visualWheel.localScale = Vector3.one * (suspension.springLength * 2f);
suspension.visualWheel.transform.localScale = new Vector3(suspension.wheelRadius * 2, suspension.wheelRadius * 2, suspension.wheelRadius * 2);
suspension.transform.localRotation = Quaternion.Euler(suspension.transform.localRotation.x, suspension.wheelAngle, suspension.transform.localRotation.z);
suspension.visualWheel.transform.localRotation = Quaternion.Euler(0, suspension.wheelAngle, 0);
}
}
void StandStill()
{
if (Mathf.Abs(speed) >= 1f || !grounded || verInput != 0f)
{
rb.drag = 0f;
return;
}
bool flag = true;
for (int i = 0; i < wheels.Count; i++)
{
if (Vector3.Angle(wheels[i].hit.normal, Vector3.up) > 1f)
{
flag = false;
break;
}
}
if (flag)
{
rb.drag = (1f - Mathf.Abs(speed)) * 30f;
return;
}
rb.drag = 0f;
}
IEnumerator ChangeGear(int gearChange)
{
print("change gear");
gearState = GearState.CheckChange;
if (currentGear + gearChange >= 0)
{
if (gearChange > 0)
{
yield return new WaitForSeconds(0.7f);
if (RPM < increaseGearRPM || currentGear >= gearRatios.Length - 1)
{
gearState = GearState.Running;
yield break;
}
}
if (gearChange < 0)
{
yield return new WaitForSeconds(0.1f);
if (RPM > decreaseGearRPM || currentGear <= 0)
{
gearState = GearState.Running;
yield break;
}
}
gearState = GearState.Changing;
yield return new WaitForSeconds(0.5f);
currentGear += gearChange;
}
if (gearState != GearState.Neutral)
gearState = GearState.Running;
}
void HudInit()
{
rpmNeedle.rotation = Quaternion.Euler(0, 0, Mathf.Lerp(minNeedleAngle, maxNeedleAngle, RPM/(redLine * 1.1f)));
infoText.text = (int)speed +"km/h \n" + (int)RPM + "RPM \n" + ((gearState == GearState.Neutral) ? "Neutral Gear" : ((currentGear + 1) + "th Gear"));
}
}