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