1178 lines
38 KiB
C#
1178 lines
38 KiB
C#
//----------------------------------------------
|
|
// 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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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<RCC_CarControllerV3>();
|
|
return _carController;
|
|
}
|
|
}
|
|
private RCC_CarControllerV3 _carController;
|
|
|
|
// WheelCollider.
|
|
public WheelCollider WheelCollider {
|
|
get {
|
|
if (_wheelCollider == null)
|
|
_wheelCollider = GetComponent<WheelCollider>();
|
|
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<ParticleSystem> allWheelParticles = new List<ParticleSystem>();
|
|
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<ParticleSystem>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creating pivot point of the wheel.
|
|
/// </summary>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creating pacticles.
|
|
/// </summary>
|
|
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<ParticleSystem>().emission;
|
|
emission.enabled = false;
|
|
ps.transform.SetParent(transform, false);
|
|
ps.transform.localPosition = Vector3.zero;
|
|
ps.transform.localRotation = Quaternion.identity;
|
|
allWheelParticles.Add(ps.GetComponent<ParticleSystem>());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creating audiosource.
|
|
/// </summary>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overriding wheel settings.
|
|
/// </summary>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks the selected behavior in RCC Settings and applies changes.
|
|
/// </summary>
|
|
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();
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// ESP System. All wheels have individual brakes. In case of loosing control of the vehicle, corresponding wheel will brake for gaining the control again.
|
|
/// </summary>
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aligning wheel model position and rotation.
|
|
/// </summary>
|
|
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);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Drawing skidmarks if wheel is skidding.
|
|
/// </summary>
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets forward and sideways frictions.
|
|
/// </summary>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Total amount of wheel slip.
|
|
/// </summary>
|
|
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);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Particles.
|
|
/// </summary>
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Drift.
|
|
/// </summary>
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Audio.
|
|
/// </summary>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if one of the wheel is slipping.
|
|
/// </summary>
|
|
/// <returns><c>true</c>, if skidding was ised, <c>false</c> otherwise.</returns>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies the motor torque.
|
|
/// </summary>
|
|
/// <param name="torque">Torque.</param>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies the steering.
|
|
/// </summary>
|
|
/// <param name="steerInput">Steer input.</param>
|
|
/// <param name="angle">Angle.</param>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies the brake torque.
|
|
/// </summary>
|
|
/// <param name="torque">Torque.</param>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts to splat map coordinate.
|
|
/// </summary>
|
|
/// <returns>The to splat map coordinate.</returns>
|
|
/// <param name="playerPos">Player position.</param>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the index of the ground material.
|
|
/// </summary>
|
|
/// <returns>The ground material index.</returns>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checking deflated wheel.
|
|
/// </summary>
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if overtorque applying.
|
|
/// </summary>
|
|
/// <returns><c>true</c>, if torque was overed, <c>false</c> otherwise.</returns>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a new friction to WheelCollider.
|
|
/// </summary>
|
|
/// <returns>The friction curves.</returns>
|
|
/// <param name="curve">Curve.</param>
|
|
/// <param name="extremumSlip">Extremum slip.</param>
|
|
/// <param name="extremumValue">Extremum value.</param>
|
|
/// <param name="asymptoteSlip">Asymptote slip.</param>
|
|
/// <param name="asymptoteValue">Asymptote value.</param>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deflates the wheel.
|
|
/// </summary>
|
|
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<ParticleSystem>();
|
|
_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);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inflates the wheel.
|
|
/// </summary>
|
|
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;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the draw gizmos event.
|
|
/// </summary>
|
|
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
|
|
|
|
}
|
|
|
|
} |