using UnityEngine; #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem; #endif namespace RaceFields.Vehicles { [RequireComponent(typeof(Rigidbody))] public sealed class ArcadeBuggyController : MonoBehaviour { [Header("Wheel Colliders")] [SerializeField] private WheelCollider frontLeftCollider; [SerializeField] private WheelCollider frontRightCollider; [SerializeField] private WheelCollider rearLeftCollider; [SerializeField] private WheelCollider rearRightCollider; [Header("Wheel Visuals")] [SerializeField] private Transform frontLeftVisual; [SerializeField] private Transform frontRightVisual; [SerializeField] private Transform rearLeftVisual; [SerializeField] private Transform rearRightVisual; [Header("Driving")] [SerializeField] private float motorTorque = 950f; [SerializeField] private float brakeTorque = 1700f; [SerializeField] private float maxSteerAngle = 32f; [SerializeField] private float reverseTorqueScale = 0.45f; [SerializeField] private float downforce = 35f; [Header("Stability")] [SerializeField] private Vector3 centerOfMassOffset = new Vector3(0f, -0.45f, 0.15f); [SerializeField] private float maxSpeedKmh = 120f; private Rigidbody body; private float throttle; private float steering; private bool brake; private Quaternion frontLeftVisualRotationOffset = Quaternion.identity; private Quaternion frontRightVisualRotationOffset = Quaternion.identity; private Quaternion rearLeftVisualRotationOffset = Quaternion.identity; private Quaternion rearRightVisualRotationOffset = Quaternion.identity; private void Awake() { body = GetComponent(); body.centerOfMass += centerOfMassOffset; AutoFindReferences(); CacheVisualRotationOffsets(); } private void Update() { ReadInput(); SyncWheelVisual(frontLeftCollider, frontLeftVisual, frontLeftVisualRotationOffset); SyncWheelVisual(frontRightCollider, frontRightVisual, frontRightVisualRotationOffset); SyncWheelVisual(rearLeftCollider, rearLeftVisual, rearLeftVisualRotationOffset); SyncWheelVisual(rearRightCollider, rearRightVisual, rearRightVisualRotationOffset); } private void FixedUpdate() { ApplySteering(); ApplyMotorAndBrakes(); body.AddForce(-transform.up * downforce * body.linearVelocity.magnitude, ForceMode.Force); } private void ReadInput() { #if ENABLE_INPUT_SYSTEM if (Keyboard.current != null) { float forward = Keyboard.current.wKey.isPressed || Keyboard.current.upArrowKey.isPressed ? 1f : 0f; float reverse = Keyboard.current.sKey.isPressed || Keyboard.current.downArrowKey.isPressed ? 1f : 0f; float left = Keyboard.current.aKey.isPressed || Keyboard.current.leftArrowKey.isPressed ? 1f : 0f; float right = Keyboard.current.dKey.isPressed || Keyboard.current.rightArrowKey.isPressed ? 1f : 0f; throttle = forward - reverse; steering = right - left; brake = Keyboard.current.spaceKey.isPressed; return; } #endif throttle = Input.GetAxisRaw("Vertical"); steering = Input.GetAxisRaw("Horizontal"); brake = Input.GetKey(KeyCode.Space); } private void ApplySteering() { float angle = steering * maxSteerAngle; if (frontLeftCollider != null) frontLeftCollider.steerAngle = angle; if (frontRightCollider != null) frontRightCollider.steerAngle = angle; } private void ApplyMotorAndBrakes() { float speedKmh = body.linearVelocity.magnitude * 3.6f; float torque = speedKmh < maxSpeedKmh ? throttle * motorTorque : 0f; if (throttle < 0f) torque *= reverseTorqueScale; SetDriveTorque(rearLeftCollider, torque); SetDriveTorque(rearRightCollider, torque); float currentBrakeTorque = brake ? brakeTorque : 0f; SetBrakeTorque(frontLeftCollider, currentBrakeTorque); SetBrakeTorque(frontRightCollider, currentBrakeTorque); SetBrakeTorque(rearLeftCollider, currentBrakeTorque); SetBrakeTorque(rearRightCollider, currentBrakeTorque); } private static void SetDriveTorque(WheelCollider wheel, float torque) { if (wheel != null) wheel.motorTorque = torque; } private static void SetBrakeTorque(WheelCollider wheel, float torque) { if (wheel != null) wheel.brakeTorque = torque; } private static void SyncWheelVisual(WheelCollider collider, Transform visual, Quaternion visualRotationOffset) { if (collider == null || visual == null) return; collider.GetWorldPose(out Vector3 position, out Quaternion rotation); visual.SetPositionAndRotation(position, rotation * visualRotationOffset); } private void AutoFindReferences() { frontLeftCollider ??= FindChildComponent("WheelCollider_FL"); frontRightCollider ??= FindChildComponent("WheelCollider_FR"); rearLeftCollider ??= FindChildComponent("WheelCollider_RL"); rearRightCollider ??= FindChildComponent("WheelCollider_RR"); frontLeftVisual ??= FindFirstChildTransform("F_L", "WheelVisual_FL"); frontRightVisual ??= FindFirstChildTransform("F_R", "WheelVisual_FR"); rearLeftVisual ??= FindFirstChildTransform("B_L", "WheelVisual_RL"); rearRightVisual ??= FindFirstChildTransform("B_R", "WheelVisual_RR"); } private void CacheVisualRotationOffsets() { frontLeftVisualRotationOffset = GetVisualRotationOffset(frontLeftCollider, frontLeftVisual); frontRightVisualRotationOffset = GetVisualRotationOffset(frontRightCollider, frontRightVisual); rearLeftVisualRotationOffset = GetVisualRotationOffset(rearLeftCollider, rearLeftVisual); rearRightVisualRotationOffset = GetVisualRotationOffset(rearRightCollider, rearRightVisual); } private static Quaternion GetVisualRotationOffset(WheelCollider collider, Transform visual) { if (collider == null || visual == null) return Quaternion.identity; return Quaternion.Inverse(collider.transform.rotation) * visual.rotation; } private T FindChildComponent(string childName) where T : Component { Transform child = FindChildTransform(childName); return child != null ? child.GetComponent() : null; } private Transform FindChildTransform(string childName) { Transform[] children = GetComponentsInChildren(true); for (int i = 0; i < children.Length; i++) { if (children[i].name == childName) return children[i]; } return null; } private Transform FindFirstChildTransform(params string[] childNames) { for (int i = 0; i < childNames.Length; i++) { Transform child = FindChildTransform(childNames[i]); if (child != null) return child; } return null; } } }