//using UnityEngine; //using CnControls; //public enum GroundCheck //{ // RayCast, // SphereCaste //} //public class VehicleController : MonoBehaviour //{ // // Existing serialized and private fields... // // (unchanged fields) // [SerializeField] float _currentSpeed = 0f; // [SerializeField] float _turnAI = 0f; // [SerializeField] bool _isAI = false; // [SerializeField] GroundCheck _groundCheck = GroundCheck.RayCast; // [SerializeField] LayerMask _drivableSurface = -1; // [SerializeField] float _maxSpeed = 100f; // [SerializeField] float _normalAccelaration = 10f; // [SerializeField] float _steering = 10f; // [SerializeField] float _gravity = 9.8f; // [SerializeField] float _downforce = 5f; // [SerializeField] float _brakeAngle = 30f; // [SerializeField] float _stoppingDistance = 5f; // [SerializeField] bool _airControl = false; // [SerializeField] AnimationCurve _frictionCurve = new AnimationCurve(); // [SerializeField] AnimationCurve _turnCurve = new AnimationCurve(); // [SerializeField, Range(0, 10)] float _bodyTilt = 0.5f; // [SerializeField] Transform _bodyMesh = null; // [SerializeField, Range(0.1f, 1f)] float _skidWidth = 0.25f; // [SerializeField, Range(0.1f, 10f)] float _skidTime = 5f; // [SerializeField] Transform _wheelFL = null; // [SerializeField] Transform _wheelFR = null; // [SerializeField] Transform _wheelRL = null; // [SerializeField] Transform _wheelRR = null; // [SerializeField] Transform _wheelFLAxel = null; // [SerializeField] Transform _wheelFRAxel = null; // [SerializeField] GameObject speedLines = null; // [SerializeField] Rigidbody _sphereRB = null; // [SerializeField] PhysicMaterial _frictionMaterial = null; // [SerializeField] Transform _followTarget = null; // // Internal variables // private bool _canRun = false; // private bool _grounded = false; // private bool _turboPlayed = true; // public bool _useNos = false; // private float _speed = 0f; // private float _brake = 0f; // private float _accelaration = 0f; // private float _sign = 0f; // private float _horizontalInput = 0f; // private float _verticalInput = 0f; // private float _radius = 0f; // private float _desiredTurning = 0f; // private float _reachedTargetDistance = 0f; // private float _distToTarget = 0f; // private float _dot = 0f; // private float _angleToMove = 0f; // private float _maxDistance = 0f; // private float _counterSteering = 0f; // private float _turnMultiplyer = 0f; // private Vector3 _carVelocity = Vector3.zero; // private Vector3 _myDir = Vector3.zero; // private Vector3 _aimedDir = Vector3.zero; // private Vector3 _aimedPoint = Vector3.zero; // private Vector3 _dirToMovePos = Vector3.zero; // private RaycastHit _hit; // private Rigidbody _carBody = null; // private VehicleTracker _vehicleTracker = null; // public bool IsAI => _isAI; // public bool CanRun => _canRun; // public bool IsGrounded => _grounded; // public float SkidWidth => _skidWidth; // public float SkidTime => _skidTime; // public float Speed => _carVelocity.z; // public float MaxSpeed => _maxSpeed; // public bool IsTouchDown => _horizontalInput != 0 || _verticalInput != 0 || _useNos; // public NOSController nosController; // internal float Steering // { // get => _steering; // set => _steering = value; // } // public Vector3 CarVelocity => _carVelocity; // public Transform FollowTarget => _followTarget; // [Range(0.3f, 1f)] public float steeringFactor = 1f; // private void Awake() // { // _carBody = GetComponent(); // _vehicleTracker = GetComponent(); // _radius = _sphereRB.GetComponent().radius; // nosController = GetComponent(); // _maxDistance = _radius + 0.5f; // StartVehicle(); // } // public void StartVehicle() // { // _canRun = true; // if (!_isAI) // _accelaration = _normalAccelaration; // } // public void RaceFinish() // { // _canRun = false; // _carVelocity = Vector3.zero; // _currentSpeed = 0f; // _turnAI = 0f; // } // private void Update() // { // if (!_canRun) return; // Visuals(); // Inputs(); // Controls(); // } // private void Inputs() // { // _verticalInput = CnInputManager.GetAxis("Vertical"); // _horizontalInput = CnInputManager.GetAxis("Horizontal"); // } // private void Visuals() // { // _counterSteering = (Mathf.Abs(_carVelocity.x) > 20f) ? -1f : 1f; // if (_wheelFL) _wheelFL.localRotation = Quaternion.Slerp(_wheelFL.localRotation, Quaternion.Euler(0, 45f * _counterSteering * _turnAI, 0), 0.1f); // if (_wheelFR) _wheelFR.localRotation = Quaternion.Slerp(_wheelFR.localRotation, Quaternion.Euler(0, 45f * _counterSteering * _turnAI, 0), 0.1f); // _wheelFLAxel?.Rotate(Vector3.right * (Speed * 0.75f)); // _wheelFRAxel?.Rotate(Vector3.right * (Speed * 0.75f)); // _wheelRL?.Rotate(Vector3.right * (Speed * 0.75f)); // _wheelRR?.Rotate(Vector3.right * (Speed * 0.75f)); // if (_bodyMesh) // { // if (_carVelocity.z > 1) // _bodyMesh.localRotation = Quaternion.Slerp(_bodyMesh.localRotation, // Quaternion.Euler(Mathf.Lerp(0, -5, _carVelocity.z / _maxSpeed), 0, Mathf.Clamp(_desiredTurning * _turnAI, -_bodyTilt, _bodyTilt)), 0.05f); // else // _bodyMesh.localRotation = Quaternion.Slerp(_bodyMesh.localRotation, Quaternion.identity, 0.05f); // } // if (speedLines) // speedLines.SetActive(_carVelocity.z > 10); // } // private void Controls() // { // _aimedPoint = _vehicleTracker.TargetPos; // _aimedPoint.y = transform.position.y; // _aimedDir = (_aimedPoint - transform.position).normalized; // _myDir = transform.forward; // _myDir.Normalize(); // _desiredTurning = Mathf.Abs(Vector3.Angle(_myDir, Vector3.ProjectOnPlane(_aimedDir, transform.up))); // _distToTarget = Vector3.Distance(transform.position, _vehicleTracker.TargetPos); // _dirToMovePos = (_vehicleTracker.TargetPos - transform.position).normalized; // _dot = Vector3.Dot(transform.forward, _dirToMovePos); // _angleToMove = Vector3.Angle(transform.forward, _dirToMovePos); // // ✨ No more full brake; allow drift // _brake = 0f; // if (_distToTarget > _reachedTargetDistance) // { // _speed = _dot > 0f ? _verticalInput : (_distToTarget > 5f ? _verticalInput : -1f); // _turnAI = (Vector3.SignedAngle(transform.forward, _dirToMovePos, Vector3.up) > 0f ? 1f : -1f) // * _turnCurve.Evaluate(_desiredTurning / 160f); // } // else // { // _turnAI = 0f; // } // if (!_isAI) // _turnAI += _horizontalInput; // _currentSpeed = Mathf.Round(Speed); // } // private void FixedUpdate() // { // if (!_canRun) return; // Grounded(); // Movement(); // } // private void Grounded() // { // if (_groundCheck == GroundCheck.RayCast) // { // _grounded = Physics.Raycast(_sphereRB.position, Vector3.down, out _hit, _maxDistance, _drivableSurface) // || Physics.Raycast(_sphereRB.position + Vector3.forward * 3f, Vector3.down, out _hit, _maxDistance, _drivableSurface) // || Physics.Raycast(_sphereRB.position - Vector3.forward * 3f, Vector3.down, out _hit, _maxDistance, _drivableSurface); // } // else if (_groundCheck == GroundCheck.SphereCaste) // { // _grounded = Physics.SphereCast(_sphereRB.position + _radius * Vector3.up, _radius + 0.25f, -transform.up, out _hit, _maxDistance, _drivableSurface); // } // else _grounded = false; // } // private void Movement() // { // _carVelocity = _carBody.transform.InverseTransformDirection(_carBody.velocity); // // 🎯 Dynamic friction drift logic // if (_angleToMove > _brakeAngle && _carVelocity.z > 15f) // { // _frictionMaterial.dynamicFriction = 0.01f; // low grip drift // } // else // { // _frictionMaterial.dynamicFriction = _frictionCurve.Evaluate(Mathf.Abs(_carVelocity.x / 100)); // } // _turnMultiplyer = _turnCurve.Evaluate(_carVelocity.magnitude / _maxSpeed); // if (IsGrounded) // { // _sign = Mathf.Sign(_carVelocity.z); // if (Mathf.Abs(_speed) > 0.1f) // _carBody.AddTorque(Vector3.up * (_turnAI * _sign * _steering * 100f * _turnMultiplyer * steeringFactor)); // // ✨ No braking-based constraint // if (Mathf.Abs(_speed) > 0.1f) // _sphereRB.velocity = Vector3.Lerp(_sphereRB.velocity, _carBody.transform.forward * (_speed * _maxSpeed), _accelaration / 10f * Time.fixedDeltaTime); // _sphereRB.AddForce(-transform.up * (_downforce * _sphereRB.mass)); // _carBody.MoveRotation(Quaternion.Slerp(_carBody.rotation, Quaternion.FromToRotation(_carBody.transform.up, _hit.normal) * _carBody.transform.rotation, 0.12f)); // } // else // { // if (_airControl) // _carBody.AddTorque(Vector3.up * (_turnAI * _steering * 100f * _turnMultiplyer * steeringFactor)); // _sphereRB.velocity = Vector3.Lerp(_sphereRB.velocity, (_carBody.transform.forward * (_speed * _maxSpeed)) + Vector3.down * (_gravity * 9.8f), (_accelaration / 25f) * Time.deltaTime); // } // } // internal void Sleep() // { // this.enabled = false; // _sphereRB.gameObject.SetActive(false); // _sphereRB.velocity = Vector3.zero; // _sphereRB.Sleep(); // _carBody.velocity = Vector3.zero; // _carBody.Sleep(); // } // public void UseNOS() // { // UseNOS(true); // nosController._useNOS = true; // } // public void UseNOS(bool use, float acceleration = 10f) // { // _useNos = use; // _accelaration = use ? _accelaration + acceleration : _normalAccelaration; // Debug.Log("accelaration: " + _accelaration); // } // internal void WakeUp() // { // this.enabled = true; // _sphereRB.gameObject.SetActive(true); // } //} using UnityEngine; using CnControls; public enum SurfaceDetection { RayCast, SphereCast } public class VehicleController : MonoBehaviour { [SerializeField] float currentSpeed = 0f; [SerializeField] float steeringAI = 0f; [SerializeField] bool isAIControlled = false; [SerializeField] SurfaceDetection groundDetection = SurfaceDetection.RayCast; [SerializeField] LayerMask driveableSurface = -1; [SerializeField] float maxSpeed = 100f; [SerializeField] float baseAcceleration = 10f; [SerializeField] float steeringSensitivity = 10f; [SerializeField] float gravityForce = 9.8f; [SerializeField] float downforce = 5f; [SerializeField] float brakeThreshold = 30f; [SerializeField] float targetStoppingDistance = 5f; [SerializeField] bool allowAirControl = false; [SerializeField] AnimationCurve _frictionCurve = new AnimationCurve(); [SerializeField] AnimationCurve _turnCurve = new AnimationCurve(); [SerializeField, Range(0, 10)] float bodyTiltAmount = 0.5f; [SerializeField] Transform carBodyVisual = null; [SerializeField, Range(0.1f, 1f)] float skidMarkWidth = 0.25f; [SerializeField, Range(0.1f, 10f)] float skidMarkDuration = 5f; [SerializeField] Transform wheelFrontLeft = null; [SerializeField] Transform wheelFrontRight = null; [SerializeField] Transform wheelRearLeft = null; [SerializeField] Transform wheelRearRight = null; [SerializeField] Transform frontLeftAxle = null; [SerializeField] Transform frontRightAxle = null; [SerializeField] GameObject speedVisualEffects = null; [SerializeField] Rigidbody sphereRigidbody = null; [SerializeField] PhysicMaterial wheelFrictionMaterial = null; [SerializeField] Transform cameraFollowTarget = null; private bool isRunning = false; private bool isOnGround = false; private bool hasTurboBoost = true; public bool nosActive = false; private float accelerationInput = 0f; private float brakeInput = 0f; private float accelerationForce = 0f; private float steeringSign = 0f; private float inputHorizontal = 0f; private float inputVertical = 0f; private float wheelRadius = 0f; private float desiredTurnAngle = 0f; private float distanceToTarget = 0f; private float angleDifference = 0f; private float maxGroundDistance = 0f; private float counterSteerValue = 0f; private float steeringMultiplier = 0f; private Vector3 velocityLocal = Vector3.zero; private Vector3 forwardDirection = Vector3.zero; private Vector3 directionToTarget = Vector3.zero; private Vector3 movementDirection = Vector3.zero; private RaycastHit surfaceHit; private Rigidbody vehicleRigidbody = null; private VehicleTracker targetTracker = null; public bool AIControlled => isAIControlled; public bool Running => isRunning; public bool Grounded => isOnGround; public float SkidWidth => skidMarkWidth; public float SkidDuration => skidMarkDuration; public float CurrentSpeed => velocityLocal.z; public float MaximumSpeed => maxSpeed; public bool InputsActive => inputHorizontal != 0 || inputVertical != 0 || nosActive; public NOSController nosController; internal float SteeringSensitivity { get => steeringSensitivity; set => steeringSensitivity = value; } public Vector3 LocalVelocity => velocityLocal; public Transform FollowTarget => cameraFollowTarget; [Range(0.3f, 1f)] public float steeringFactor = 1f; private void Awake() { vehicleRigidbody = GetComponent(); targetTracker = GetComponent(); wheelRadius = sphereRigidbody.GetComponent().radius; nosController = GetComponent(); maxGroundDistance = wheelRadius + 0.5f; InitializeVehicle(); } public void InitializeVehicle() { isRunning = true; if (!isAIControlled) accelerationForce = baseAcceleration; } public void StopVehicle() { isRunning = false; velocityLocal = Vector3.zero; currentSpeed = 0f; steeringAI = 0f; } private void Update() { if (!isRunning) return; UpdateVisuals(); ProcessInputs(); ApplyControls(); } private void ProcessInputs() { inputVertical = CnInputManager.GetAxis("Vertical"); inputHorizontal = CnInputManager.GetAxis("Horizontal"); } private void UpdateVisuals() { counterSteerValue = (Mathf.Abs(velocityLocal.x) > 20f) ? -1f : 1f; if (wheelFrontLeft) wheelFrontLeft.localRotation = Quaternion.Slerp(wheelFrontLeft.localRotation, Quaternion.Euler(0, 45f * counterSteerValue * steeringAI, 0), 0.1f); if (wheelFrontRight) wheelFrontRight.localRotation = Quaternion.Slerp(wheelFrontRight.localRotation, Quaternion.Euler(0, 45f * counterSteerValue * steeringAI, 0), 0.1f); frontLeftAxle?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); frontRightAxle?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); wheelRearLeft?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); wheelRearRight?.Rotate(Vector3.right * (CurrentSpeed * 0.75f)); if (carBodyVisual) { if (velocityLocal.z > 1) carBodyVisual.localRotation = Quaternion.Slerp(carBodyVisual.localRotation, Quaternion.Euler(Mathf.Lerp(0, -5, velocityLocal.z / maxSpeed), 0, Mathf.Clamp(desiredTurnAngle * steeringAI, -bodyTiltAmount, bodyTiltAmount)), 0.05f); else carBodyVisual.localRotation = Quaternion.Slerp(carBodyVisual.localRotation, Quaternion.identity, 0.05f); } if (speedVisualEffects) speedVisualEffects.SetActive(velocityLocal.z > 10); } private void ApplyControls() { Vector3 targetPoint = targetTracker.TargetPos; targetPoint.y = transform.position.y; directionToTarget = (targetPoint - transform.position).normalized; forwardDirection = transform.forward.normalized; desiredTurnAngle = Mathf.Abs(Vector3.Angle(forwardDirection, Vector3.ProjectOnPlane(directionToTarget, transform.up))); distanceToTarget = Vector3.Distance(transform.position, targetTracker.TargetPos); movementDirection = (targetTracker.TargetPos - transform.position).normalized; float alignment = Vector3.Dot(transform.forward, movementDirection); angleDifference = Vector3.Angle(transform.forward, movementDirection); brakeInput = 0f; if (distanceToTarget > targetStoppingDistance) { accelerationInput = alignment > 0f ? inputVertical : (distanceToTarget > 5f ? inputVertical : -1f); steeringAI = (Vector3.SignedAngle(transform.forward, movementDirection, Vector3.up) > 0f ? 1f : -1f) * _turnCurve.Evaluate(desiredTurnAngle / 160f); } else { steeringAI = 0f; } if (!isAIControlled) steeringAI += inputHorizontal; currentSpeed = Mathf.Round(CurrentSpeed); } private void FixedUpdate() { if (!isRunning) return; CheckGround(); HandleMovement(); } private void CheckGround() { if (groundDetection == SurfaceDetection.RayCast) { isOnGround = Physics.Raycast(sphereRigidbody.position, Vector3.down, out surfaceHit, maxGroundDistance, driveableSurface) || Physics.Raycast(sphereRigidbody.position + Vector3.forward * 3f, Vector3.down, out surfaceHit, maxGroundDistance, driveableSurface) || Physics.Raycast(sphereRigidbody.position - Vector3.forward * 3f, Vector3.down, out surfaceHit, maxGroundDistance, driveableSurface); } else if (groundDetection == SurfaceDetection.SphereCast) { isOnGround = Physics.SphereCast(sphereRigidbody.position + wheelRadius * Vector3.up, wheelRadius + 0.25f, -transform.up, out surfaceHit, maxGroundDistance, driveableSurface); } else isOnGround = false; } private void HandleMovement() { velocityLocal = vehicleRigidbody.transform.InverseTransformDirection(vehicleRigidbody.velocity); if (angleDifference > brakeThreshold && velocityLocal.z > 15f) { wheelFrictionMaterial.dynamicFriction = 0.01f; } else { wheelFrictionMaterial.dynamicFriction = _frictionCurve.Evaluate(Mathf.Abs(velocityLocal.x / 100)); } steeringMultiplier = _turnCurve.Evaluate(velocityLocal.magnitude / maxSpeed); if (Grounded) { steeringSign = Mathf.Sign(velocityLocal.z); if (Mathf.Abs(accelerationInput) > 0.1f) vehicleRigidbody.AddTorque(Vector3.up * (steeringAI * steeringSign * steeringSensitivity * 100f * steeringMultiplier * steeringFactor)); if (Mathf.Abs(accelerationInput) > 0.1f) sphereRigidbody.velocity = Vector3.Lerp(sphereRigidbody.velocity, vehicleRigidbody.transform.forward * (accelerationInput * maxSpeed), accelerationForce / 10f * Time.fixedDeltaTime); sphereRigidbody.AddForce(-transform.up * (downforce * sphereRigidbody.mass)); vehicleRigidbody.MoveRotation(Quaternion.Slerp(vehicleRigidbody.rotation, Quaternion.FromToRotation(vehicleRigidbody.transform.up, surfaceHit.normal) * vehicleRigidbody.transform.rotation, 0.12f)); } else { if (allowAirControl) vehicleRigidbody.AddTorque(Vector3.up * (steeringAI * steeringSensitivity * 100f * steeringMultiplier * steeringFactor)); sphereRigidbody.velocity = Vector3.Lerp(sphereRigidbody.velocity, (vehicleRigidbody.transform.forward * (accelerationInput * maxSpeed)) + Vector3.down * (gravityForce * 9.8f), (accelerationForce / 25f) * Time.deltaTime); } } internal void DisableVehicle() { enabled = false; sphereRigidbody.gameObject.SetActive(false); sphereRigidbody.velocity = Vector3.zero; sphereRigidbody.Sleep(); vehicleRigidbody.velocity = Vector3.zero; vehicleRigidbody.Sleep(); } public void ActivateNOS() { ActivateNOS(true); nosController._isBoosting = true; } public void ActivateNOS(bool active, float bonusAcceleration = 10f) { nosActive = active; accelerationForce = active ? accelerationForce + bonusAcceleration : baseAcceleration; Debug.Log("Acceleration updated: " + accelerationForce); } internal void EnableVehicle() { enabled = true; sphereRigidbody.gameObject.SetActive(true); } }