240 lines
8.3 KiB
C#
240 lines
8.3 KiB
C#
using UnityEngine;
|
||
|
||
public enum DriveType { FWD, RWD, AWD }
|
||
|
||
[RequireComponent(typeof(Rigidbody))]
|
||
public class CarController : MonoBehaviour
|
||
{
|
||
public float SpeedKmH => rb != null ? rb.linearVelocity.magnitude * 3.6f : 0f;
|
||
|
||
[Header("Аркадные настройки движения")]
|
||
public DriveType driveType = DriveType.AWD;
|
||
[SerializeField] private float motorForce = 2200f;
|
||
[SerializeField] private float maxSpeedKmH = 160f;
|
||
[SerializeField] private float brakeForce = 4500f;
|
||
|
||
[Header("Рулевое управление")]
|
||
[SerializeField] private float maxSteerAngle = 32f;
|
||
[SerializeField] private float steerSpeed = 8f;
|
||
[Tooltip("Во сколько раз уменьшается выворот руля на максимальной скорости")]
|
||
[SerializeField] private float highSpeedSteerFactor = 0.35f;
|
||
|
||
[Header("Устойчивость и Дрифт")]
|
||
[SerializeField] private Vector3 centerOfMassOffset = new Vector3(0f, -0.45f, 0f);
|
||
[SerializeField] private float antiRollForce = 8000f;
|
||
[SerializeField] private float normalGrip = 2.2f;
|
||
[SerializeField] private float handbrakeRearGrip = 0.65f;
|
||
|
||
[Header("Wheel Colliders")]
|
||
[SerializeField] private WheelCollider frontLeftCollider;
|
||
[SerializeField] private WheelCollider frontRightCollider;
|
||
[SerializeField] private WheelCollider rearLeftCollider;
|
||
[SerializeField] private WheelCollider rearRightCollider;
|
||
|
||
[Header("Wheel Meshes")]
|
||
[SerializeField] private Transform frontLeftMesh;
|
||
[SerializeField] private Transform frontRightMesh;
|
||
[SerializeField] private Transform rearLeftMesh;
|
||
[SerializeField] private Transform rearRightMesh;
|
||
|
||
private Rigidbody rb;
|
||
private float inputVertical;
|
||
private float inputHorizontal;
|
||
private bool inputHandbrake;
|
||
private float currentSteerAngle;
|
||
|
||
private void Awake()
|
||
{
|
||
rb = GetComponent<Rigidbody>();
|
||
rb.centerOfMass = centerOfMassOffset;
|
||
rb.angularDamping = 1.5f;
|
||
|
||
SetupArcadeWheel(frontLeftCollider);
|
||
SetupArcadeWheel(frontRightCollider);
|
||
SetupArcadeWheel(rearLeftCollider);
|
||
SetupArcadeWheel(rearRightCollider);
|
||
}
|
||
|
||
private void SetupArcadeWheel(WheelCollider wc)
|
||
{
|
||
if (wc == null) return;
|
||
|
||
JointSpring spring = wc.suspensionSpring;
|
||
spring.spring = 35000f;
|
||
spring.damper = 4500f;
|
||
spring.targetPosition = 0.5f;
|
||
wc.suspensionSpring = spring;
|
||
wc.suspensionDistance = 0.18f;
|
||
|
||
WheelFrictionCurve fwd = wc.forwardFriction;
|
||
fwd.stiffness = 2.0f;
|
||
wc.forwardFriction = fwd;
|
||
|
||
WheelFrictionCurve side = wc.sidewaysFriction;
|
||
side.stiffness = normalGrip;
|
||
wc.sidewaysFriction = side;
|
||
}
|
||
|
||
public void ProcessInput(float vertical, float horizontal, bool handbrake)
|
||
{
|
||
inputVertical = vertical;
|
||
inputHorizontal = horizontal;
|
||
inputHandbrake = handbrake;
|
||
}
|
||
|
||
private void FixedUpdate()
|
||
{
|
||
HandleSteering();
|
||
HandleMotorAndBrakes();
|
||
ApplyAntiRoll(frontLeftCollider, frontRightCollider);
|
||
ApplyAntiRoll(rearLeftCollider, rearRightCollider);
|
||
UpdateWheelPoses();
|
||
}
|
||
|
||
private void HandleSteering()
|
||
{
|
||
float speedFactor = Mathf.Lerp(1f, highSpeedSteerFactor, SpeedKmH / maxSpeedKmH);
|
||
float targetSteer = inputHorizontal * maxSteerAngle * speedFactor;
|
||
currentSteerAngle = Mathf.Lerp(currentSteerAngle, targetSteer, Time.fixedDeltaTime * steerSpeed);
|
||
|
||
if (frontLeftCollider != null) frontLeftCollider.steerAngle = currentSteerAngle;
|
||
if (frontRightCollider != null) frontRightCollider.steerAngle = currentSteerAngle;
|
||
}
|
||
|
||
private void HandleMotorAndBrakes()
|
||
{
|
||
float localVelZ = transform.InverseTransformDirection(rb.linearVelocity).z;
|
||
float motor = 0f;
|
||
float brake = 0f;
|
||
|
||
if (inputVertical > 0.01f)
|
||
{
|
||
if (localVelZ < -1.5f)
|
||
brake = brakeForce;
|
||
else if (SpeedKmH < maxSpeedKmH)
|
||
motor = inputVertical * motorForce;
|
||
}
|
||
else if (inputVertical < -0.01f)
|
||
{
|
||
if (localVelZ > 1.5f)
|
||
brake = brakeForce;
|
||
else if (SpeedKmH < 45f)
|
||
motor = inputVertical * motorForce * 0.7f;
|
||
}
|
||
else
|
||
{
|
||
// Плавное торможение двигателем, когда газ отпущен
|
||
brake = 150f;
|
||
}
|
||
|
||
if (inputHandbrake)
|
||
{
|
||
motor = 0f;
|
||
brake = brakeForce * 1.5f;
|
||
}
|
||
|
||
ApplyDriveTorque(motor);
|
||
ApplyBrakesAndDrift(brake, inputHandbrake);
|
||
}
|
||
|
||
private void ApplyDriveTorque(float torque)
|
||
{
|
||
if (driveType == DriveType.FWD)
|
||
{
|
||
SetMotor(frontLeftCollider, torque);
|
||
SetMotor(frontRightCollider, torque);
|
||
SetMotor(rearLeftCollider, 0f);
|
||
SetMotor(rearRightCollider, 0f);
|
||
}
|
||
else if (driveType == DriveType.RWD)
|
||
{
|
||
SetMotor(frontLeftCollider, 0f);
|
||
SetMotor(frontRightCollider, 0f);
|
||
SetMotor(rearLeftCollider, torque);
|
||
SetMotor(rearRightCollider, torque);
|
||
}
|
||
else
|
||
{
|
||
float half = torque * 0.55f;
|
||
SetMotor(frontLeftCollider, half);
|
||
SetMotor(frontRightCollider, half);
|
||
SetMotor(rearLeftCollider, half);
|
||
SetMotor(rearRightCollider, half);
|
||
}
|
||
}
|
||
|
||
private void SetMotor(WheelCollider wc, float val)
|
||
{
|
||
if (wc != null && wc.enabled) wc.motorTorque = val;
|
||
}
|
||
|
||
private void ApplyBrakesAndDrift(float brake, bool handbrake)
|
||
{
|
||
float targetRearStiffness = handbrake ? handbrakeRearGrip : normalGrip;
|
||
|
||
SetRearGrip(rearLeftCollider, targetRearStiffness);
|
||
SetRearGrip(rearRightCollider, targetRearStiffness);
|
||
|
||
if (handbrake)
|
||
{
|
||
if (frontLeftCollider != null) frontLeftCollider.brakeTorque = 0f;
|
||
if (frontRightCollider != null) frontRightCollider.brakeTorque = 0f;
|
||
if (rearLeftCollider != null) rearLeftCollider.brakeTorque = brake;
|
||
if (rearRightCollider != null) rearRightCollider.brakeTorque = brake;
|
||
}
|
||
else
|
||
{
|
||
if (frontLeftCollider != null) frontLeftCollider.brakeTorque = brake;
|
||
if (frontRightCollider != null) frontRightCollider.brakeTorque = brake;
|
||
if (rearLeftCollider != null) rearLeftCollider.brakeTorque = brake * 0.6f;
|
||
if (rearRightCollider != null) rearRightCollider.brakeTorque = brake * 0.6f;
|
||
}
|
||
}
|
||
|
||
private void SetRearGrip(WheelCollider wc, float targetStiff)
|
||
{
|
||
if (wc == null) return;
|
||
WheelFrictionCurve side = wc.sidewaysFriction;
|
||
side.stiffness = Mathf.Lerp(side.stiffness, targetStiff, Time.fixedDeltaTime * 10f);
|
||
wc.sidewaysFriction = side;
|
||
}
|
||
|
||
private void ApplyAntiRoll(WheelCollider left, WheelCollider right)
|
||
{
|
||
if (left == null || right == null || !left.enabled || !right.enabled) return;
|
||
|
||
float travelL = 1f;
|
||
float travelR = 1f;
|
||
|
||
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 force = (travelL - travelR) * antiRollForce;
|
||
|
||
if (groundedL) rb.AddForceAtPosition(left.transform.up * -force, left.transform.position);
|
||
if (groundedR) rb.AddForceAtPosition(right.transform.up * force, right.transform.position);
|
||
}
|
||
|
||
private void UpdateWheelPoses()
|
||
{
|
||
UpdateWheelPose(frontLeftCollider, frontLeftMesh);
|
||
UpdateWheelPose(frontRightCollider, frontRightMesh);
|
||
UpdateWheelPose(rearLeftCollider, rearLeftMesh);
|
||
UpdateWheelPose(rearRightCollider, rearRightMesh);
|
||
}
|
||
|
||
private void UpdateWheelPose(WheelCollider collider, Transform mesh)
|
||
{
|
||
if (mesh == null || collider == null || !collider.enabled || !mesh.IsChildOf(transform))
|
||
return;
|
||
|
||
collider.GetWorldPose(out Vector3 pos, out Quaternion rot);
|
||
mesh.position = pos;
|
||
mesh.rotation = rot;
|
||
}
|
||
} |