170 lines
6.8 KiB
C#
170 lines
6.8 KiB
C#
using UnityEngine;
|
||
|
||
[RequireComponent(typeof(Rigidbody))]
|
||
public class AdvancedCarController : MonoBehaviour
|
||
{
|
||
public float SpeedKmH => rb.linearVelocity.magnitude * 3.6f;
|
||
|
||
[Header("Данные авто")]
|
||
[SerializeField] private CarPhysicsData data;
|
||
|
||
[Header("Колеса (WheelColliders)")]
|
||
[SerializeField] private WheelCollider[] frontWheels;
|
||
[SerializeField] private WheelCollider[] rearWheels;
|
||
|
||
[Header("Состояние деталей (Для диплома)")]
|
||
[Range(0.1f, 1f)] public float engineCondition = 1f; // 1 - новое, 0.1 - убитое (не тянет)
|
||
[Range(0.1f, 1f)] public float tiresCondition = 1f; // Влияет на держак резины
|
||
|
||
// Состояние авто
|
||
public float CurrentSpeedKmH => rb.linearVelocity.magnitude * 3.6f;
|
||
public float EngineRPM { get; private set; }
|
||
public int CurrentGear { get; private set; }
|
||
|
||
private Rigidbody rb;
|
||
private float currentSteerAngle;
|
||
private WheelFrictionCurve originalRearSidewaysFriction;
|
||
|
||
// Ввод
|
||
private float moveInput, steerInput;
|
||
private bool isBraking, isHandbrake;
|
||
|
||
private void Awake()
|
||
{
|
||
rb = GetComponent<Rigidbody>();
|
||
rb.centerOfMass = data.centerOfMassOffset; // Понижаем центр масс для стабильности
|
||
|
||
// Запоминаем оригинальное трение для ручника
|
||
if (rearWheels.Length > 0)
|
||
originalRearSidewaysFriction = rearWheels[0].sidewaysFriction;
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
// Чтение инпута (можно заменить на новый Input System)
|
||
moveInput = Input.GetAxis("Vertical");
|
||
steerInput = Input.GetAxis("Horizontal");
|
||
isBraking = Input.GetKey(KeyCode.Space) || (moveInput < 0 && CurrentSpeedKmH > 5f);
|
||
isHandbrake = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.Space);
|
||
}
|
||
|
||
private void FixedUpdate()
|
||
{
|
||
ApplySteering();
|
||
ProcessEngineAndGears();
|
||
ApplyAntiRollBar();
|
||
ProcessHandbrake();
|
||
}
|
||
|
||
public void ProcessInput(float vertical, float horizontal, bool handbrake)
|
||
{
|
||
moveInput = vertical;
|
||
steerInput = horizontal;
|
||
isHandbrake = handbrake;
|
||
}
|
||
|
||
private void ApplySteering()
|
||
{
|
||
// 1. Угол зависит от скорости (на 100 км/ч руль не вывернется на 35 градусов)
|
||
float speedMultiplier = data.steerAngleBySpeed.Evaluate(CurrentSpeedKmH);
|
||
float targetSteer = steerInput * data.maxSteerAngle * speedMultiplier;
|
||
|
||
// 2. Сглаживание поворота (руль не дергается мгновенно)
|
||
currentSteerAngle = Mathf.Lerp(currentSteerAngle, targetSteer, Time.fixedDeltaTime * data.steerSmoothing);
|
||
|
||
foreach (var wheel in frontWheels)
|
||
{
|
||
wheel.steerAngle = currentSteerAngle;
|
||
}
|
||
}
|
||
|
||
private void ProcessEngineAndGears()
|
||
{
|
||
// Считаем средние обороты ведущих колес (допустим, задний привод)
|
||
float wheelRPM = 0;
|
||
foreach (var w in rearWheels) wheelRPM += Mathf.Abs(w.rpm);
|
||
wheelRPM /= rearWheels.Length;
|
||
|
||
// Вычисляем обороты двигателя через передаточные числа
|
||
float gearRatio = data.gearRatios[CurrentGear];
|
||
EngineRPM = Mathf.Clamp(wheelRPM * gearRatio * data.finalDrive, data.minRPM, data.maxRPM);
|
||
|
||
// Автоматическая коробка передач
|
||
if (EngineRPM > data.shiftUpRPM && CurrentGear < data.gearRatios.Length - 1)
|
||
CurrentGear++;
|
||
else if (EngineRPM < data.shiftDownRPM && CurrentGear > 0)
|
||
CurrentGear--;
|
||
|
||
// Получаем крутящий момент по графику и умножаем на состояние двигателя!
|
||
float motorTorque = data.torqueCurve.Evaluate(EngineRPM) * gearRatio * data.finalDrive;
|
||
float finalTorque = moveInput * motorTorque * engineCondition;
|
||
|
||
// Применяем газ или тормоз
|
||
foreach (var wheel in rearWheels)
|
||
{
|
||
if (isBraking)
|
||
{
|
||
wheel.motorTorque = 0;
|
||
wheel.brakeTorque = data.brakeForce;
|
||
}
|
||
else
|
||
{
|
||
wheel.motorTorque = finalTorque / rearWheels.Length;
|
||
wheel.brakeTorque = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ProcessHandbrake()
|
||
{
|
||
foreach (var wheel in rearWheels)
|
||
{
|
||
if (isHandbrake)
|
||
{
|
||
wheel.brakeTorque = data.brakeForce;
|
||
|
||
// ДРИФТ: Уменьшаем боковое сцепление, умножая на износ шин
|
||
WheelFrictionCurve fc = wheel.sidewaysFriction;
|
||
fc.stiffness = originalRearSidewaysFriction.stiffness * data.handbrakeFrictionMultiplier * tiresCondition;
|
||
wheel.sidewaysFriction = fc;
|
||
}
|
||
else if (!isBraking)
|
||
{
|
||
wheel.brakeTorque = 0;
|
||
|
||
// Возвращаем обычное сцепление с поправкой на убитость шин
|
||
WheelFrictionCurve fc = wheel.sidewaysFriction;
|
||
fc.stiffness = originalRearSidewaysFriction.stiffness * tiresCondition;
|
||
wheel.sidewaysFriction = fc;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ApplyAntiRollBar()
|
||
{
|
||
// Стабилизатор поперечной устойчивости для передней и задней оси
|
||
ApplyAxleAntiRoll(frontWheels[0], frontWheels[1]);
|
||
ApplyAxleAntiRoll(rearWheels[0], rearWheels[1]);
|
||
}
|
||
|
||
private void ApplyAxleAntiRoll(WheelCollider left, WheelCollider right)
|
||
{
|
||
float travelL = 1.0f;
|
||
float travelR = 1.0f;
|
||
|
||
bool groundedL = left.GetGroundHit(out WheelHit hitL);
|
||
if (groundedL) travelL = (-left.transform.InverseTransformPoint(hitL.point).y - left.radius) / left.suspensionDistance;
|
||
|
||
bool groundedR = right.GetGroundHit(out WheelHit hitR);
|
||
if (groundedR) travelR = (-right.transform.InverseTransformPoint(hitR.point).y - right.radius) / right.suspensionDistance;
|
||
|
||
// Разница сжатия подвески
|
||
float antiRollVal = (travelL - travelR) * data.antiRollForce;
|
||
|
||
// Применяем противодействующую силу (прижимаем поднявшееся колесо)
|
||
if (groundedL) rb.AddForceAtPosition(left.transform.up * -antiRollVal, left.transform.position);
|
||
if (groundedR) rb.AddForceAtPosition(right.transform.up * antiRollVal, right.transform.position);
|
||
}
|
||
|
||
|
||
} |