//---------------------------------------------- // Realistic Car Controller // // Copyright © 2014 - 2023 BoneCracker Games // https://www.bonecrackergames.com // Buğra Özdoğanlar // //---------------------------------------------- using UnityEngine; using System.Linq; using System.Collections; using System.Collections.Generic; /// /// Based on Unity's WheelCollider. Modifies forward and sideways curves, settings in order to get stable and realistic physics depends on selected behavior in RCC Settings. /// [RequireComponent(typeof(WheelCollider))] [AddComponentMenu("BoneCracker Games/Realistic Car Controller/Main/RCC Wheel Collider")] public class RCC_WheelCollider : RCC_Core { #region OBSOLETE VARIABLES [System.Obsolete("Use CarController instead of carController")] public RCC_CarControllerV3 carController { get { return CarController; } } [System.Obsolete("Use WheelCollider instead of wheelCollider")] public WheelCollider wheelCollider { get { return WheelCollider; } } [System.Obsolete("Use Rigid instead of rigid")] public Rigidbody rigid { get { return Rigid; } } #endregion // Car controller. public RCC_CarControllerV3 CarController { get { if (_carController == null) _carController = GetComponentInParent(); return _carController; } } private RCC_CarControllerV3 _carController; // WheelCollider. public WheelCollider WheelCollider { get { if (_wheelCollider == null) _wheelCollider = GetComponent(); return _wheelCollider; } } private WheelCollider _wheelCollider; // Rigidbody of the vehicle. private Rigidbody Rigid { get { if (_rigid == null) _rigid = CarController.Rigid; return _rigid; } } private Rigidbody _rigid; public Transform wheelModel; // Wheel model for animating and aligning. public WheelHit wheelHit; // Wheelhit data. public bool isGrounded = false; // Is wheel grounded or not? public int groundIndex = 0; // Current ground index of wheelhit. public bool alignWheel = true; // Align the wheelmodel with wheelcollider position and rotation. public bool drawSkid = true; // Draw skidmarks. // Locating correct position and rotation for the wheel. [HideInInspector] public Vector3 wheelPosition = Vector3.zero; [HideInInspector] public Quaternion wheelRotation = Quaternion.identity; [Space()] public bool canPower = false; // Can this wheel apply power? [Range(-1f, 1f)] public float powerMultiplier = 1f; public bool canSteer = false; // Can this wheel apply steer? [Range(-1f, 1f)] public float steeringMultiplier = 1f; public bool canBrake = false; // Can this wheel apply brake? [Range(0f, 1f)] public float brakingMultiplier = 1f; public bool canHandbrake = false; // Can this wheel apply handbrake? [Range(0f, 1f)] public float handbrakeMultiplier = 1f; [Space()] public float wheelWidth = .275f; // Width of the wheel. public float wheelOffset = 0f; // Offset by X axis. private float wheelRPM2Speed = 0f; // Wheel RPM to Speed in km/h unit. [Space()] [Range(-5f, 5f)] public float camber = 0f; // Camber angle. [Range(-5f, 5f)] public float caster = 0f; // Caster angle. [Range(-5f, 5f)] public float toe = 0f; // Toe angle. [Space()] // Skidmarks private int lastSkidmark = -1; // Slips [HideInInspector] public float wheelSlipAmountForward = 0f; // Forward slip. [HideInInspector] public float wheelSlipAmountSideways = 0f; // Sideways slip. [HideInInspector] public float totalSlip = 0f; // Total amount of forward and sideways slips. // WheelFriction Curves and Stiffness. private WheelFrictionCurve forwardFrictionCurve; // Forward friction curve. private WheelFrictionCurve sidewaysFrictionCurve; // Sideways friction curve. // Original WheelFriction Curves and Stiffness. private WheelFrictionCurve forwardFrictionCurve_Org; // Forward friction curve original. private WheelFrictionCurve sidewaysFrictionCurve_Org; // Sideways friction curve original. // Audio private AudioSource audioSource; // Audiosource for tire skid SFX. private AudioClip audioClip; // Audioclip for tire skid SFX. private float audioVolume = 1f; // Maximum volume for tire skid SFX. // List for all particle systems. [HideInInspector] public List allWheelParticles = new List(); private ParticleSystem.EmissionModule emission; // Tractions used for smooth drifting. [HideInInspector] public float tractionHelpedSidewaysStiffness = 1f; private readonly float minForwardStiffness = .9f; private readonly float maxForwardStiffness = 1f; private readonly float minSidewaysStiffness = .5f; private readonly float maxSidewaysStiffness = 1f; // Getting bump force. [HideInInspector] public float bumpForce, oldForce, RotationValue = 0f; private bool deflated = false; // Deflated or not? [Space()] public float deflateRadiusMultiplier = .8f; // Deflated radius multiplier. Radius of the wheelcollider will be multiplied by this value on deflate. public float deflatedStiffnessMultiplier = .5f; // Deflated stiffness of the wheelcollider. private float defRadius = -1f; // Original radius of the wheelcollider. // Getting audioclips from the RCC Settings. public AudioClip DeflateAudio { get { return RCC_Settings.Instance.wheelDeflateClip; } } public AudioClip InflateAudio { get { return RCC_Settings.Instance.wheelInflateClip; } } private AudioSource flatSource; public AudioClip FlatAudio { get { return RCC_Settings.Instance.wheelFlatClip; } } private ParticleSystem _wheelDeflateParticles; public ParticleSystem WheelDeflateParticles { get { return RCC_Settings.Instance.wheelDeflateParticles.GetComponent(); } } private void Start() { // Increasing wheelcollider mass for avoiding unstable behavior. if (RCC_Settings.Instance.useFixedWheelColliders) WheelCollider.mass = Rigid.mass / 15f; CreatePivotOfTheWheel(); UpdateWheelFrictions(); OverrideWheelSettings(); CreateAudio(); CreateParticles(); } /// /// Creating pivot point of the wheel. /// private void CreatePivotOfTheWheel() { // Creating pivot position of the wheel at correct position and rotation. GameObject newPivot = new GameObject("Pivot_" + wheelModel.transform.name); newPivot.transform.position = RCC_GetBounds.GetBoundsCenter(wheelModel.transform); newPivot.transform.rotation = transform.rotation; newPivot.transform.SetParent(wheelModel.transform.parent, true); // Assigning temporary created wheel to actual wheel. wheelModel.SetParent(newPivot.transform, true); wheelModel = newPivot.transform; } /// /// Creating pacticles. /// private void CreateParticles() { if (RCC_Settings.Instance.dontUseAnyParticleEffects) return; for (int i = 0; i < RCC_GroundMaterials.Instance.frictions.Length; i++) { GameObject ps = Instantiate(RCC_GroundMaterials.Instance.frictions[i].groundParticles, transform.position, transform.rotation); emission = ps.GetComponent().emission; emission.enabled = false; ps.transform.SetParent(transform, false); ps.transform.localPosition = Vector3.zero; ps.transform.localRotation = Quaternion.identity; allWheelParticles.Add(ps.GetComponent()); } } /// /// Creating audiosource. /// private void CreateAudio() { // Creating audiosource for skid SFX. audioSource = NewAudioSource(RCC_Settings.Instance.audioMixer, CarController.gameObject, "Skid Sound AudioSource", 5f, 50f, 0f, audioClip, true, true, false); audioSource.transform.position = transform.position; } /// /// Overriding wheel settings. /// private void OverrideWheelSettings() { // Override wheels automatically if enabled. if (!CarController.overrideAllWheels) { // Overriding canPower, canSteer, canBrake, canHandbrake. if (this == CarController.FrontLeftWheelCollider || this == CarController.FrontRightWheelCollider) { canSteer = true; canBrake = true; brakingMultiplier = 1f; } // Overriding canPower, canSteer, canBrake, canHandbrake. if (this == CarController.RearLeftWheelCollider || this == CarController.RearRightWheelCollider) { canHandbrake = true; canBrake = true; brakingMultiplier = .75f; } } } private void OnEnable() { // Listening an event when main behavior changed. RCC_SceneManager.OnBehaviorChanged += UpdateWheelFrictions; // If wheel model is assigned but not enabled, enable it. if (wheelModel && !wheelModel.gameObject.activeSelf) wheelModel.gameObject.SetActive(true); // Resetting values on enable. wheelSlipAmountForward = 0f; wheelSlipAmountSideways = 0f; totalSlip = 0f; bumpForce = 0f; oldForce = 0f; if (audioSource) { audioSource.volume = 0f; audioSource.Stop(); } WheelCollider.motorTorque = 0f; WheelCollider.brakeTorque = 0f; WheelCollider.steerAngle = 0f; } /// /// Checks the selected behavior in RCC Settings and applies changes. /// private void UpdateWheelFrictions() { // Getting forward and sideways frictions of the wheelcollider. forwardFrictionCurve = WheelCollider.forwardFriction; sidewaysFrictionCurve = WheelCollider.sidewaysFriction; // Getting behavior if selected. RCC_Settings.BehaviorType behavior = RCC_Settings.Instance.selectedBehaviorType; // If there is a selected behavior, override friction curves. if (!CarController.overrideBehavior && behavior != null) { forwardFrictionCurve = SetFrictionCurves(forwardFrictionCurve, behavior.forwardExtremumSlip, behavior.forwardExtremumValue, behavior.forwardAsymptoteSlip, behavior.forwardAsymptoteValue); sidewaysFrictionCurve = SetFrictionCurves(sidewaysFrictionCurve, behavior.sidewaysExtremumSlip, behavior.sidewaysExtremumValue, behavior.sidewaysAsymptoteSlip, behavior.sidewaysAsymptoteValue); } // Assigning new frictons. WheelCollider.forwardFriction = forwardFrictionCurve; WheelCollider.sidewaysFriction = sidewaysFrictionCurve; // Override original frictions. forwardFrictionCurve_Org = WheelCollider.forwardFriction; sidewaysFrictionCurve_Org = WheelCollider.sidewaysFriction; } private void Update() { // Return if RCC is disabled. if (!CarController.enabled) return; // Setting position and rotation of the wheel model. if (alignWheel) WheelAlign(); } private void FixedUpdate() { // Return if RCC is disabled. if (!CarController.enabled) return; float circumFerence = 2.0f * 3.14f * WheelCollider.radius; // Finding circumFerence 2 Pi R. wheelRPM2Speed = (circumFerence * WheelCollider.rpm) * 60f; // Finding MPH and converting to KMH. wheelRPM2Speed = Mathf.Clamp(wheelRPM2Speed / 1000f, 0f, Mathf.Infinity); // Setting power state of the wheels depending on drivetrain mode. Only overrides them if overrideWheels is enabled for the vehicle. if (!CarController.overrideAllWheels) { switch (CarController.wheelTypeChoise) { case RCC_CarControllerV3.WheelType.AWD: canPower = true; break; case RCC_CarControllerV3.WheelType.BIASED: canPower = true; break; case RCC_CarControllerV3.WheelType.FWD: if (this == CarController.FrontLeftWheelCollider || this == CarController.FrontRightWheelCollider) canPower = true; else canPower = false; break; case RCC_CarControllerV3.WheelType.RWD: if (this == CarController.RearLeftWheelCollider || this == CarController.RearRightWheelCollider) canPower = true; else canPower = false; break; } } GroundMaterial(); Frictions(); TotalSlip(); SkidMarks(); Particles(); Audio(); CheckDeflate(); ESP(); } /// /// ESP System. All wheels have individual brakes. In case of loosing control of the vehicle, corresponding wheel will brake for gaining the control again. /// private void ESP() { if (CarController.ESP && CarController.brakeInput < .5f) { if (CarController.handbrakeInput < .5f) { if (CarController.underSteering) { if (this == CarController.FrontLeftWheelCollider) ApplyBrakeTorque((CarController.brakeTorque * CarController.ESPStrength) * Mathf.Clamp(-CarController.rearSlip, 0f, Mathf.Infinity)); if (this == CarController.FrontRightWheelCollider) ApplyBrakeTorque((CarController.brakeTorque * CarController.ESPStrength) * Mathf.Clamp(CarController.rearSlip, 0f, Mathf.Infinity)); } if (CarController.overSteering) { if (this == CarController.RearLeftWheelCollider) ApplyBrakeTorque((CarController.brakeTorque * CarController.ESPStrength) * Mathf.Clamp(-CarController.frontSlip, 0f, Mathf.Infinity)); if (this == CarController.RearRightWheelCollider) ApplyBrakeTorque((CarController.brakeTorque * CarController.ESPStrength) * Mathf.Clamp(CarController.frontSlip, 0f, Mathf.Infinity)); } } } } /// /// Aligning wheel model position and rotation. /// private void WheelAlign() { // Return if no wheel model selected. if (!wheelModel) { Debug.LogWarning(transform.name + " wheel of the " + CarController.transform.name + " is missing wheel model."); return; } // Getting position and rotation of the wheelcollider and assigning wheelPosition and wheelRotation. WheelCollider.GetWorldPose(out wheelPosition, out wheelRotation); //Increase the rotation value. RotationValue += WheelCollider.rpm * (360f / 60f) * Time.deltaTime; // Assigning position and rotation to the wheel model. wheelModel.SetPositionAndRotation(wheelPosition, transform.rotation * Quaternion.Euler(RotationValue, WheelCollider.steerAngle, 0f)); // Adjusting camber angle by Z axis. if (transform.localPosition.x < 0f) wheelModel.transform.RotateAround(wheelModel.transform.position, transform.forward, -camber); else wheelModel.transform.RotateAround(wheelModel.transform.position, transform.forward, camber); // Adjusting caster angle by X axis. if (transform.localPosition.x < 0f) wheelModel.transform.RotateAround(wheelModel.transform.position, transform.right, -caster); else wheelModel.transform.RotateAround(wheelModel.transform.position, transform.right, caster); } /// /// Drawing skidmarks if wheel is skidding. /// private void SkidMarks() { // Return if not drawing skidmarks. if (!drawSkid) return; // If scene has skidmarks manager... if (!RCC_Settings.Instance.dontUseSkidmarks) { // If slips are bigger than target value, draw the skidmarks. if (totalSlip > RCC_GroundMaterials.Instance.frictions[groundIndex].slip) { Vector3 skidPoint = wheelHit.point + 1f * Time.deltaTime * (Rigid.velocity); if (Rigid.velocity.magnitude > 1f && isGrounded && wheelHit.normal != Vector3.zero && wheelHit.point != Vector3.zero && skidPoint != Vector3.zero && Mathf.Abs(skidPoint.x) > 1f && Mathf.Abs(skidPoint.z) > 1f) lastSkidmark = RCC_SkidmarksManager.Instance.AddSkidMark(skidPoint, wheelHit.normal, totalSlip, wheelWidth, lastSkidmark, groundIndex); else lastSkidmark = -1; } else { lastSkidmark = -1; } } } /// /// Sets forward and sideways frictions. /// private void Frictions() { // Handbrake input clamped 0f - 1f. float hbInput = CarController.handbrakeInput; if (canHandbrake && hbInput > .75f) hbInput = .75f; else hbInput = 1f; // Setting wheel stiffness to ground physic material stiffness. forwardFrictionCurve.stiffness = RCC_GroundMaterials.Instance.frictions[groundIndex].forwardStiffness; sidewaysFrictionCurve.stiffness = (RCC_GroundMaterials.Instance.frictions[groundIndex].sidewaysStiffness * hbInput * tractionHelpedSidewaysStiffness); // If deflated, apply deflated stiffness. if (deflated) { forwardFrictionCurve.stiffness *= deflatedStiffnessMultiplier; sidewaysFrictionCurve.stiffness *= deflatedStiffnessMultiplier; } // If drift mode is selected, apply specific frictions. if (!CarController.overrideBehavior && RCC_Settings.Instance.selectedBehaviorType != null && RCC_Settings.Instance.selectedBehaviorType.applyExternalWheelFrictions) Drift(); // Setting new friction curves to wheels. WheelCollider.forwardFriction = forwardFrictionCurve; WheelCollider.sidewaysFriction = sidewaysFrictionCurve; // Also damp too. WheelCollider.wheelDampingRate = RCC_GroundMaterials.Instance.frictions[groundIndex].damp; } /// /// Total amount of wheel slip. /// private void TotalSlip() { // Forward, sideways, and total slips. if (isGrounded && wheelHit.point != Vector3.zero) { wheelSlipAmountForward = wheelHit.forwardSlip; wheelSlipAmountSideways = wheelHit.sidewaysSlip; } else { wheelSlipAmountForward = 0f; wheelSlipAmountSideways = 0f; } totalSlip = Mathf.Lerp(totalSlip, ((Mathf.Abs(wheelSlipAmountSideways) + Mathf.Abs(wheelSlipAmountForward)) / 2f), Time.fixedDeltaTime * 10f); } /// /// Particles. /// private void Particles() { if (RCC_Settings.Instance.dontUseAnyParticleEffects) return; // If wheel slip is bigger than ground physic material slip, enable particles. Otherwise, disable particles. for (int i = 0; i < allWheelParticles.Count; i++) { if (totalSlip > RCC_GroundMaterials.Instance.frictions[groundIndex].slip) { if (i != groundIndex) { ParticleSystem.EmissionModule em; em = allWheelParticles[i].emission; em.enabled = false; } else { ParticleSystem.EmissionModule em; em = allWheelParticles[i].emission; em.enabled = true; } } else { ParticleSystem.EmissionModule em; em = allWheelParticles[i].emission; em.enabled = false; } if (isGrounded && wheelHit.point != Vector3.zero) allWheelParticles[i].transform.position = wheelHit.point + (.05f * transform.up); } } /// /// Drift. /// private void Drift() { Vector3 relativeVelocity = transform.InverseTransformDirection(Rigid.velocity); float sqrVel = (relativeVelocity.x * relativeVelocity.x) / 50f; if (wheelHit.forwardSlip > 0) sqrVel += (Mathf.Abs(wheelHit.forwardSlip)); if (CarController.wheelTypeChoise == RCC_CarControllerV3.WheelType.RWD) { // Forward if (WheelCollider == CarController.FrontLeftWheelCollider.WheelCollider || WheelCollider == CarController.FrontRightWheelCollider.WheelCollider) { forwardFrictionCurve.extremumValue = Mathf.Clamp(forwardFrictionCurve_Org.extremumValue - sqrVel, minForwardStiffness / 1f, maxForwardStiffness); forwardFrictionCurve.asymptoteValue = Mathf.Clamp(forwardFrictionCurve_Org.asymptoteValue - (sqrVel / 1f), minForwardStiffness / 1f, maxForwardStiffness); ; } else { forwardFrictionCurve.extremumValue = Mathf.Clamp(forwardFrictionCurve_Org.extremumValue - sqrVel, minForwardStiffness, maxForwardStiffness); forwardFrictionCurve.asymptoteValue = Mathf.Clamp(forwardFrictionCurve_Org.asymptoteValue - (sqrVel / 1f), minForwardStiffness, maxForwardStiffness); } // Sideways if (WheelCollider == CarController.FrontLeftWheelCollider.WheelCollider || WheelCollider == CarController.FrontRightWheelCollider.WheelCollider) { sidewaysFrictionCurve.extremumValue = Mathf.Clamp(sidewaysFrictionCurve_Org.extremumValue - sqrVel, minSidewaysStiffness, maxSidewaysStiffness); sidewaysFrictionCurve.asymptoteValue = Mathf.Clamp(sidewaysFrictionCurve_Org.asymptoteValue - (sqrVel / 1f), minSidewaysStiffness, maxSidewaysStiffness); } else { sidewaysFrictionCurve.extremumValue = Mathf.Clamp(sidewaysFrictionCurve_Org.extremumValue - sqrVel, minSidewaysStiffness, maxSidewaysStiffness); sidewaysFrictionCurve.asymptoteValue = Mathf.Clamp(sidewaysFrictionCurve_Org.asymptoteValue - (sqrVel / 1f), minSidewaysStiffness, maxSidewaysStiffness); } } else { if (WheelCollider == CarController.FrontLeftWheelCollider.WheelCollider || WheelCollider == CarController.FrontRightWheelCollider.WheelCollider) { // Forward forwardFrictionCurve.extremumValue = Mathf.Clamp(forwardFrictionCurve_Org.extremumValue - sqrVel, minForwardStiffness / 1f, maxForwardStiffness); forwardFrictionCurve.asymptoteValue = Mathf.Clamp(forwardFrictionCurve_Org.asymptoteValue - (sqrVel / 1f), minForwardStiffness / 1f, maxForwardStiffness); } else { forwardFrictionCurve.extremumValue = Mathf.Clamp(forwardFrictionCurve_Org.extremumValue - sqrVel, minForwardStiffness, maxForwardStiffness); forwardFrictionCurve.asymptoteValue = Mathf.Clamp(forwardFrictionCurve_Org.asymptoteValue - (sqrVel / 1f), minForwardStiffness, maxForwardStiffness); } // Sideways sidewaysFrictionCurve.extremumValue = Mathf.Clamp(sidewaysFrictionCurve_Org.extremumValue - sqrVel, minSidewaysStiffness, maxSidewaysStiffness); sidewaysFrictionCurve.asymptoteValue = Mathf.Clamp(sidewaysFrictionCurve_Org.asymptoteValue - (sqrVel / 1f), minSidewaysStiffness, maxSidewaysStiffness); } } /// /// Audio. /// private void Audio() { // Set audioclip to ground physic material sound. audioClip = RCC_GroundMaterials.Instance.frictions[groundIndex].groundSound; audioVolume = RCC_GroundMaterials.Instance.frictions[groundIndex].volume; // If total slip is high enough... if (totalSlip > RCC_GroundMaterials.Instance.frictions[groundIndex].slip) { // Assigning corresponding audio clip. if (audioSource.clip != audioClip) audioSource.clip = audioClip; // Playing it. if (!audioSource.isPlaying) audioSource.Play(); // If vehicle is moving, set volume and pitch. Otherwise set them to 0. if (Rigid.velocity.magnitude > 1f) { audioSource.volume = Mathf.Lerp(0f, audioVolume, totalSlip); audioSource.pitch = Mathf.Lerp(1f, .8f, audioSource.volume); } else { audioSource.volume = 0f; } } else { audioSource.volume = 0f; // If volume is minimal and audio is still playing, stop. if (audioSource.volume <= .05f && audioSource.isPlaying) audioSource.Stop(); } // Calculating bump force. bumpForce = wheelHit.force - oldForce; // If bump force is high enough, play bump SFX. if ((bumpForce) >= 5000f) { // Creating and playing audiosource for bump SFX. AudioSource bumpSound = NewAudioSource(RCC_Settings.Instance.audioMixer, CarController.gameObject, "Bump Sound AudioSource", 5f, 50f, (bumpForce - 5000f) / 3000f, RCC_Settings.Instance.bumpClip, false, true, true); bumpSound.pitch = Random.Range(.9f, 1.1f); } oldForce = wheelHit.force; } /// /// Returns true if one of the wheel is slipping. /// /// true, if skidding was ised, false otherwise. public bool IsSkidding() { for (int i = 0; i < CarController.AllWheelColliders.Length; i++) { if (CarController.AllWheelColliders[i].totalSlip > RCC_GroundMaterials.Instance.frictions[groundIndex].slip) return true; } return false; } /// /// Applies the motor torque. /// /// Torque. public void ApplyMotorTorque(float torque) { // If TCS is enabled, checks forward slip. If wheel is losing traction, don't apply torque. if (CarController.TCS) { if (Mathf.Abs(WheelCollider.rpm) >= 1) { if (Mathf.Abs(wheelSlipAmountForward) > RCC_GroundMaterials.Instance.frictions[groundIndex].slip) { CarController.TCSAct = true; torque -= Mathf.Clamp(torque * (Mathf.Abs(wheelSlipAmountForward)) * CarController.TCSStrength, -Mathf.Infinity, Mathf.Infinity); if (WheelCollider.rpm > 1) { torque -= Mathf.Clamp(torque * (Mathf.Abs(wheelSlipAmountForward)) * CarController.TCSStrength, 0f, Mathf.Infinity); torque = Mathf.Clamp(torque, 0f, Mathf.Infinity); } else { torque += Mathf.Clamp(-torque * (Mathf.Abs(wheelSlipAmountForward)) * CarController.TCSStrength, 0f, Mathf.Infinity); torque = Mathf.Clamp(torque, -Mathf.Infinity, 0f); } } else { CarController.TCSAct = false; } } else { CarController.TCSAct = false; } } if (CheckOvertorque()) torque = 0; if (Mathf.Abs(torque) > 1f) WheelCollider.motorTorque = torque; else WheelCollider.motorTorque = 0f; } /// /// Applies the steering. /// /// Steer input. /// Angle. public void ApplySteering(float steerInput, float angle) { // Ackerman steering formula. if (steerInput > 0f) { if (transform.localPosition.x < 0) WheelCollider.steerAngle = (Mathf.Deg2Rad * angle * 2.55f) * (Mathf.Rad2Deg * Mathf.Atan(2.55f / (6 + (1.5f / 2))) * steerInput); else WheelCollider.steerAngle = (Mathf.Deg2Rad * angle * 2.55f) * (Mathf.Rad2Deg * Mathf.Atan(2.55f / (6 - (1.5f / 2))) * steerInput); } else if (steerInput < 0f) { if (transform.localPosition.x < 0) WheelCollider.steerAngle = (Mathf.Deg2Rad * angle * 2.55f) * (Mathf.Rad2Deg * Mathf.Atan(2.55f / (6 - (1.5f / 2))) * steerInput); else WheelCollider.steerAngle = (Mathf.Deg2Rad * angle * 2.55f) * (Mathf.Rad2Deg * Mathf.Atan(2.55f / (6 + (1.5f / 2))) * steerInput); } else { WheelCollider.steerAngle = 0f; } if (transform.localPosition.x < 0) WheelCollider.steerAngle += toe; else WheelCollider.steerAngle -= toe; } /// /// Applies the brake torque. /// /// Torque. public void ApplyBrakeTorque(float torque) { // If ABS is enabled, checks forward slip. If wheel is losing traction, don't apply torque. if (CarController.ABS && CarController.handbrakeInput <= .1f) { if ((Mathf.Abs(wheelHit.forwardSlip) * Mathf.Clamp01(torque)) >= CarController.ABSThreshold) { CarController.ABSAct = true; torque = 0; } else { CarController.ABSAct = false; } } if (Mathf.Abs(torque) > 1f) WheelCollider.brakeTorque = torque; else WheelCollider.brakeTorque = 0f; } /// /// Converts to splat map coordinate. /// /// The to splat map coordinate. /// Player position. private Vector3 ConvertToSplatMapCoordinate(Terrain terrain, Vector3 playerPos) { Vector3 vecRet = new Vector3(); Vector3 terPosition = terrain.transform.position; vecRet.x = ((playerPos.x - terPosition.x) / terrain.terrainData.size.x) * terrain.terrainData.alphamapWidth; vecRet.z = ((playerPos.z - terPosition.z) / terrain.terrainData.size.z) * terrain.terrainData.alphamapHeight; return vecRet; } /// /// Gets the index of the ground material. /// /// The ground material index. private void GroundMaterial() { isGrounded = WheelCollider.GetGroundHit(out wheelHit); if (!isGrounded || wheelHit.point == Vector3.zero || wheelHit.collider == null) { groundIndex = 0; return; } for (int i = 0; i < RCC_GroundMaterials.Instance.frictions.Length; i++) { if (wheelHit.collider.sharedMaterial == RCC_GroundMaterials.Instance.frictions[i].groundMaterial) { groundIndex = i; return; } } // If ground pyhsic material is not one of the ground material in Configurable Ground Materials, check if we are on terrain collider... if (!RCC_SceneManager.Instance.terrainsInitialized) { groundIndex = 0; return; } for (int i = 0; i < RCC_GroundMaterials.Instance.terrainFrictions.Length; i++) { if (wheelHit.collider.sharedMaterial == RCC_GroundMaterials.Instance.terrainFrictions[i].groundMaterial) { RCC_SceneManager.Terrains currentTerrain = null; for (int l = 0; l < RCC_SceneManager.Instance.terrains.Length; l++) { if (RCC_SceneManager.Instance.terrains[l].terrainCollider == RCC_GroundMaterials.Instance.terrainFrictions[i].groundMaterial) { currentTerrain = RCC_SceneManager.Instance.terrains[l]; break; } } if (currentTerrain != null) { Vector3 playerPos = transform.position; Vector3 TerrainCord = ConvertToSplatMapCoordinate(currentTerrain.terrain, playerPos); float comp = 0f; for (int k = 0; k < currentTerrain.mNumTextures; k++) { if (comp < currentTerrain.mSplatmapData[(int)TerrainCord.z, (int)TerrainCord.x, k]) groundIndex = k; } groundIndex = RCC_GroundMaterials.Instance.terrainFrictions[i].splatmapIndexes[groundIndex].index; return; } } } groundIndex = 0; } /// /// Checking deflated wheel. /// private void CheckDeflate() { if (deflated) { if (!flatSource) flatSource = NewAudioSource(gameObject, FlatAudio.name, 1f, 15f, .5f, FlatAudio, true, false, false); flatSource.volume = Mathf.Clamp01(Mathf.Abs(WheelCollider.rpm * .001f)); flatSource.volume *= isGrounded ? 1f : 0f; if (!flatSource.isPlaying) flatSource.Play(); } else { if (flatSource && flatSource.isPlaying) flatSource.Stop(); } if (_wheelDeflateParticles != null) { ParticleSystem.EmissionModule em = _wheelDeflateParticles.emission; if (deflated) { if (WheelCollider.rpm > 100f && isGrounded) em.enabled = true; else em.enabled = false; } else { em.enabled = false; } } if (!isGrounded || wheelHit.point == Vector3.zero || wheelHit.collider == null) return; for (int i = 0; i < RCC_GroundMaterials.Instance.frictions.Length; i++) { if (wheelHit.collider.sharedMaterial == RCC_GroundMaterials.Instance.frictions[i].groundMaterial) { if (RCC_GroundMaterials.Instance.frictions[i].deflate) Deflate(); } } } /// /// Checks if overtorque applying. /// /// true, if torque was overed, false otherwise. private bool CheckOvertorque() { if (CarController.speed > CarController.maxspeed || !CarController.engineRunning) return true; if (CarController.speed > CarController.gears[CarController.currentGear].maxSpeed && CarController.engineRPM >= (CarController.maxEngineRPM * .985f)) return true; return false; } /// /// Sets a new friction to WheelCollider. /// /// The friction curves. /// Curve. /// Extremum slip. /// Extremum value. /// Asymptote slip. /// Asymptote value. public WheelFrictionCurve SetFrictionCurves(WheelFrictionCurve curve, float extremumSlip, float extremumValue, float asymptoteSlip, float asymptoteValue) { WheelFrictionCurve newCurve = curve; newCurve.extremumSlip = extremumSlip; newCurve.extremumValue = extremumValue; newCurve.asymptoteSlip = asymptoteSlip; newCurve.asymptoteValue = asymptoteValue; return newCurve; } /// /// Deflates the wheel. /// public void Deflate() { if (deflated) return; deflated = true; if (defRadius == -1) defRadius = WheelCollider.radius; WheelCollider.radius = defRadius * deflateRadiusMultiplier; if (DeflateAudio) NewAudioSource(gameObject, DeflateAudio.name, 5f, 50f, 1f, DeflateAudio, false, true, true); if (_wheelDeflateParticles == null && WheelDeflateParticles) { GameObject ps = Instantiate(WheelDeflateParticles.gameObject, transform.position, transform.rotation); _wheelDeflateParticles = ps.GetComponent(); _wheelDeflateParticles.transform.SetParent(transform, false); _wheelDeflateParticles.transform.localPosition = new Vector3(0f, -.2f, 0f); _wheelDeflateParticles.transform.localRotation = Quaternion.identity; } CarController.Rigid.AddForceAtPosition(transform.right * Random.Range(-1f, 1f) * 30f, transform.position, ForceMode.Acceleration); } /// /// Inflates the wheel. /// public void Inflate() { if (!deflated) return; deflated = false; if (defRadius != -1) WheelCollider.radius = defRadius; if (InflateAudio) NewAudioSource(gameObject, InflateAudio.name, 5f, 50f, 1f, InflateAudio, false, true, true); } private void OnDisable() { RCC_SceneManager.OnBehaviorChanged -= UpdateWheelFrictions; if (wheelModel) wheelModel.gameObject.SetActive(false); // Resetting values on disable. wheelSlipAmountForward = 0f; wheelSlipAmountSideways = 0f; totalSlip = 0f; bumpForce = 0f; oldForce = 0f; if (audioSource) { audioSource.volume = 0f; audioSource.Stop(); } WheelCollider.motorTorque = 0f; WheelCollider.brakeTorque = 0f; WheelCollider.steerAngle = 0f; } /// /// Raises the draw gizmos event. /// private void OnDrawGizmos() { #if UNITY_EDITOR if (Application.isPlaying) { WheelCollider.GetGroundHit(out WheelHit hit); // Drawing gizmos for wheel forces and slips. float extension = (-WheelCollider.transform.InverseTransformPoint(hit.point).y - (WheelCollider.radius * transform.lossyScale.y)) / WheelCollider.suspensionDistance; Debug.DrawLine(hit.point, hit.point + transform.up * (hit.force / Rigid.mass), extension <= 0.0 ? Color.magenta : Color.white); Debug.DrawLine(hit.point, hit.point - transform.forward * hit.forwardSlip * 2f, Color.green); Debug.DrawLine(hit.point, hit.point - transform.right * hit.sidewaysSlip * 2f, Color.red); } #endif } }