Created
July 20, 2020 09:12
Star
You must be signed in to star a gist
Legacy Unity Car Controller Code (Javascript)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private var wheelRadius : float = 0.4; | |
var dragMultiplier : Vector3 = new Vector3(2, 5, 1); | |
var throttle : float = 0; | |
private var steer : float = 0; | |
private var handbrake : boolean = false; | |
var centerOfMass : Transform; | |
var frontWheels : Transform[]; | |
var rearWheels : Transform[]; | |
private var wheels : Wheel[]; | |
wheels = new Wheel[frontWheels.Length + rearWheels.Length]; | |
private var wfc : WheelFrictionCurve; | |
var topSpeed : float = 180; | |
var numberOfGears : int = 6; | |
var maximumTurn : int = 20; | |
var minimumTurn : int = 10; | |
var resetTime : float = 5.0; | |
private var resetTimer : float = 0.0; | |
private var engineForceValues : float[]; | |
private var gearSpeeds : float[]; | |
private var currentGear : int; | |
private var currentEnginePower : float = 0.0; | |
private var handbrakeXDragFactor : float = 0.5; | |
private var initialDragMultiplierX : float = 10.0; | |
private var handbrakeTime : float = 0.0; | |
private var handbrakeTimer : float = 1.0; | |
private var sound : SoundController = null; | |
sound = transform.GetComponent(SoundController); | |
private var canSteer : boolean; | |
private var canDrive : boolean; | |
class Wheel | |
{ | |
var collider : WheelCollider; | |
var tireGraphic : Transform; | |
var driveWheel : boolean = false; | |
var steerWheel : boolean = false; | |
var lastSkidmark : int = -1; | |
var lastEmitPosition : Vector3 = Vector3.zero; | |
var lastEmitTime : float = Time.time; | |
var wheelVelo : Vector3 = Vector3.zero; | |
var groundSpeed : Vector3 = Vector3.zero; | |
} | |
function Start() | |
{ | |
SetupWheelColliders(); | |
SetupCenterOfMass(); | |
topSpeed = Convert_Miles_Per_Hour_To_Meters_Per_Second(topSpeed); | |
SetupGears(); | |
initialDragMultiplierX = dragMultiplier.x; | |
} | |
function Update() | |
{ | |
var relativeVelocity : Vector3 = transform.InverseTransformDirection(rigidbody.velocity); | |
GetInput(); | |
Check_If_Car_Is_Flipped(); | |
UpdateWheelGraphics(relativeVelocity); | |
UpdateGear(relativeVelocity); | |
} | |
function FixedUpdate() | |
{ | |
var relativeVelocity : Vector3 = transform.InverseTransformDirection(rigidbody.velocity); | |
CalculateState(); | |
UpdateFriction(relativeVelocity); | |
UpdateDrag(relativeVelocity); | |
CalculateEnginePower(relativeVelocity); | |
ApplyThrottle(canDrive, relativeVelocity); | |
ApplySteering(canSteer, relativeVelocity); | |
} | |
/**************************************************/ | |
/* Functions called from Start() */ | |
/**************************************************/ | |
function SetupWheelColliders() | |
{ | |
SetupWheelFrictionCurve(); | |
var wheelCount : int = 0; | |
for (var t : Transform in frontWheels) | |
{ | |
wheels[wheelCount] = SetupWheel(t, true); | |
wheelCount++; | |
} | |
for (var t : Transform in rearWheels) | |
{ | |
wheels[wheelCount] = SetupWheel(t, false); | |
wheelCount++; | |
} | |
} | |
function SetupWheelFrictionCurve() | |
{ | |
wfc = new WheelFrictionCurve(); | |
wfc.extremumSlip = 1; | |
wfc.extremumValue = 50; | |
wfc.asymptoteSlip = 2; | |
wfc.asymptoteValue = 25; | |
wfc.stiffness = 1; | |
} | |
function SetupWheel(wheelTransform : Transform, isFrontWheel : boolean) | |
{ | |
var go : GameObject = new GameObject(wheelTransform.name + " Collider"); | |
go.transform.position = wheelTransform.position; | |
go.transform.parent = transform; | |
go.transform.rotation = wheelTransform.rotation; | |
var wc : WheelCollider = go.AddComponent(typeof(WheelCollider)) as WheelCollider; | |
var wheel = new Wheel(); | |
wheel.collider = wc; | |
wc.sidewaysFriction = wfc; | |
wheel.tireGraphic = wheelTransform; | |
wheelRadius = (wheel.tireGraphic.renderer.bounds.size.y) / 2; | |
wheel.collider.radius = wheelRadius; | |
if (isFrontWheel) | |
{ | |
wheel.steerWheel = true; | |
wheel.driveWheel = true; | |
go = new GameObject(wheelTransform.name + " Steer Column"); | |
go.transform.position = wheelTransform.position; | |
go.transform.rotation = wheelTransform.rotation; | |
go.transform.parent = transform; | |
wheelTransform = go.transform; | |
} | |
else | |
{ | |
wheel.driveWheel = true; | |
} | |
return wheel; | |
} | |
function SetupCenterOfMass() | |
{ | |
if(centerOfMass != null) | |
rigidbody.centerOfMass = centerOfMass.localPosition; | |
} | |
function SetupGears() | |
{ | |
engineForceValues = new float[numberOfGears]; | |
gearSpeeds = new float[numberOfGears]; | |
var tempTopSpeed : float = topSpeed; | |
for(var i = 0; i < numberOfGears; i++) | |
{ | |
if(i > 0) | |
gearSpeeds[i] = tempTopSpeed / 4 + gearSpeeds[i-1]; | |
else | |
gearSpeeds[i] = tempTopSpeed / 4; | |
tempTopSpeed -= tempTopSpeed / 4; | |
} | |
var engineFactor : float = topSpeed / gearSpeeds[gearSpeeds.Length - 1]; | |
for(i = 0; i < numberOfGears; i++) | |
{ | |
var maxLinearDrag : float = gearSpeeds[i] * gearSpeeds[i];// * dragMultiplier.z; | |
engineForceValues[i] = maxLinearDrag * engineFactor; | |
} | |
} | |
/**************************************************/ | |
/* Functions called from Update() */ | |
/**************************************************/ | |
function GetInput() | |
{ | |
throttle = Input.GetAxis("Vertical"); | |
steer = Input.GetAxis("Horizontal"); | |
if(Input.GetButton("Brake")) | |
{ | |
if(!handbrake) | |
{ | |
handbrake = true; | |
handbrakeTime = Time.time; | |
dragMultiplier.x = initialDragMultiplierX * handbrakeXDragFactor; | |
sound.Skid(true, 0.8); | |
} | |
} | |
else if(handbrake) | |
{ | |
sound.Skid(false, 0); | |
handbrake = false; | |
StartCoroutine(StopHandbraking(Mathf.Min(5, Time.time - handbrakeTime))); | |
} | |
} | |
function StopHandbraking(seconds : float) | |
{ | |
var diff : float = initialDragMultiplierX - dragMultiplier.x; | |
handbrakeTimer = 1; | |
// Get the x value of the dragMultiplier back to its initial value in the specified time. | |
while(dragMultiplier.x < initialDragMultiplierX && !handbrake) | |
{ | |
dragMultiplier.x += diff * (Time.deltaTime / seconds); | |
handbrakeTimer -= Time.deltaTime / seconds; | |
yield; | |
} | |
dragMultiplier.x = initialDragMultiplierX; | |
handbrakeTimer = 0; | |
} | |
function Check_If_Car_Is_Flipped() | |
{ | |
if(transform.localEulerAngles.z > 80 && transform.localEulerAngles.z < 280) | |
resetTimer += Time.deltaTime; | |
else | |
resetTimer = 0; | |
if(resetTimer > resetTime) | |
FlipCar(); | |
Debug.Log(resetTimer); | |
} | |
function FlipCar() | |
{ | |
transform.rotation = Quaternion.LookRotation(transform.forward); | |
transform.rotation.eulerAngles.x = 0; | |
transform.position += Vector3.up * 0.5; | |
rigidbody.velocity = Vector3.zero; | |
rigidbody.angularVelocity = Vector3.zero; | |
resetTimer = 0; | |
currentEnginePower = 0; | |
} | |
var wheelCount : float; | |
function UpdateWheelGraphics(relativeVelocity : Vector3) | |
{ | |
wheelCount = -1; | |
var groundWheel = 0; | |
for(var w : Wheel in wheels) | |
{ | |
wheelCount++; | |
var wheel : WheelCollider = w.collider; | |
var wh : WheelHit = new WheelHit(); | |
if(wheel.GetGroundHit(wh)) | |
{ | |
// First we get the velocity at the point where the wheel meets the ground, if the wheel is touching the ground | |
groundWheel++; | |
w.wheelVelo = rigidbody.GetPointVelocity(wh.point); | |
w.groundSpeed = w.tireGraphic.InverseTransformDirection(w.wheelVelo); | |
w.tireGraphic.Rotate(Vector3.right * (w.groundSpeed.z / wheelRadius) * Time.deltaTime * Mathf.Rad2Deg); | |
} | |
} | |
if( groundWheel == 0 ) | |
resetTimer += Time.deltaTime; | |
else | |
resetTimer = 0; | |
} | |
function UpdateGear(relativeVelocity : Vector3) | |
{ | |
currentGear = 0; | |
for(var i = 0; i < numberOfGears - 1; i++) | |
{ | |
if(relativeVelocity.z > gearSpeeds[i]) | |
currentGear = i + 1; | |
} | |
} | |
/**************************************************/ | |
/* Functions called from FixedUpdate() */ | |
/**************************************************/ | |
function UpdateDrag(relativeVelocity : Vector3) | |
{ | |
var relativeDrag : Vector3 = new Vector3( -relativeVelocity.x * Mathf.Abs(relativeVelocity.x), | |
-relativeVelocity.y * Mathf.Abs(relativeVelocity.y), | |
-relativeVelocity.z * Mathf.Abs(relativeVelocity.z) ); | |
dragMultiplier.x *= 50; | |
var drag = Vector3.Scale(dragMultiplier, relativeDrag); | |
dragMultiplier.x /= 50; | |
if(initialDragMultiplierX > dragMultiplier.x) // Handbrake code | |
{ | |
drag.x /= (relativeVelocity.magnitude / (topSpeed / ( 1 + 2 * handbrakeXDragFactor) ) ); | |
drag.z *= (1 + Mathf.Abs(Vector3.Dot(rigidbody.velocity.normalized, transform.forward))); | |
drag += rigidbody.velocity * Mathf.Clamp01(rigidbody.velocity.magnitude / topSpeed); | |
} | |
else // No handbrake | |
{ | |
drag = Vector3.Scale(dragMultiplier, relativeDrag); | |
drag.x *= topSpeed / relativeVelocity.magnitude; | |
} | |
if(Mathf.Abs(relativeVelocity.x) < 5 && !handbrake) | |
drag.x = -relativeVelocity.x * dragMultiplier.x; | |
rigidbody.AddForce(transform.TransformDirection(drag) * rigidbody.mass * Time.deltaTime); | |
} | |
function UpdateFriction(relativeVelocity : Vector3) | |
{ | |
var sqrVel : float = relativeVelocity.x * relativeVelocity.x; | |
// Add extra sideways friction based on the car's turning velocity to avoid slipping | |
if( !handbrake ) | |
{ | |
wfc.extremumValue = Mathf.Clamp(300 - sqrVel, 0, 300); | |
wfc.asymptoteValue = Mathf.Clamp(150 - (sqrVel / 2), 0, 150); | |
} | |
for(var w : Wheel in wheels) | |
{ | |
if( !handbrake ) | |
w.collider.sidewaysFriction = wfc; | |
w.collider.forwardFriction = wfc; | |
} | |
} | |
function CalculateEnginePower(relativeVelocity : Vector3) | |
{ | |
if(throttle == 0) | |
{ | |
currentEnginePower -= Time.deltaTime * 200; | |
} | |
else if( HaveTheSameSign(relativeVelocity.z, throttle) ) | |
{ | |
var normPower = (currentEnginePower / engineForceValues[engineForceValues.Length - 1]) * 2; | |
currentEnginePower += Time.deltaTime * 200 * EvaluateNormPower(normPower); | |
} | |
else | |
{ | |
currentEnginePower -= Time.deltaTime * 300; | |
} | |
if(currentGear == 0) | |
currentEnginePower = Mathf.Clamp(currentEnginePower, 0, engineForceValues[0]); | |
else | |
currentEnginePower = Mathf.Clamp(currentEnginePower, engineForceValues[currentGear - 1], engineForceValues[currentGear]); | |
} | |
function CalculateState() | |
{ | |
canDrive = false; | |
canSteer = false; | |
for(var w : Wheel in wheels) | |
{ | |
if(w.collider.isGrounded) | |
{ | |
if(w.steerWheel) | |
canSteer = true; | |
if(w.driveWheel) | |
canDrive = true; | |
} | |
} | |
} | |
function ApplyThrottle(canDrive : boolean, relativeVelocity : Vector3) | |
{ | |
if(canDrive) | |
{ | |
var throttleForce : float = 0; | |
var brakeForce : float = 0; | |
if (HaveTheSameSign(relativeVelocity.z, throttle) && (throttle != 0)) | |
{ | |
if (!handbrake) | |
throttleForce = Mathf.Sign(throttle) * currentEnginePower * rigidbody.mass; | |
} | |
else if(throttle != 0) | |
brakeForce = Mathf.Sign(throttle) * engineForceValues[0] * rigidbody.mass; | |
rigidbody.AddForce(transform.forward * Time.deltaTime * ((throttleForce + brakeForce))); | |
} | |
} | |
function ApplySteering(canSteer : boolean, relativeVelocity : Vector3) | |
{ | |
if(canSteer) | |
{ | |
var turnRadius : float = 3.0 / Mathf.Sin((90 - (steer * 30)) * Mathf.Deg2Rad); | |
var minMaxTurn : float = EvaluateSpeedToTurn(rigidbody.velocity.magnitude); | |
var turnSpeed : float = Mathf.Clamp(relativeVelocity.z / turnRadius, -minMaxTurn / 10, minMaxTurn / 10); | |
transform.RotateAround( transform.position + transform.right * turnRadius * steer, | |
transform.up, | |
turnSpeed * Mathf.Rad2Deg * Time.deltaTime * steer); | |
var debugStartPoint = transform.position + transform.right * turnRadius * steer; | |
var debugEndPoint = debugStartPoint + Vector3.up * 5; | |
Debug.DrawLine(debugStartPoint, debugEndPoint, Color.red); | |
if(initialDragMultiplierX > dragMultiplier.x) // Handbrake | |
{ | |
var rotationDirection : float = Mathf.Sign(steer) * 5; // rotationDirection is -1 or 1 by default, depending on steering | |
if(steer == 0) | |
{ | |
if(rigidbody.angularVelocity.y < 1) // If we are not steering and we are handbraking and not rotating fast, we apply a random rotationDirection | |
rotationDirection = Random.Range(-1.0, 1.0); | |
else | |
rotationDirection = rigidbody.angularVelocity.y; // If we are rotating fast we are applying that rotation to the car | |
} | |
// -- Finally we apply this rotation around a point between the cars front wheels. | |
transform.RotateAround( transform.TransformPoint( ( frontWheels[0].localPosition + frontWheels[1].localPosition) * 0.5), | |
transform.up, | |
rigidbody.velocity.magnitude * Mathf.Clamp01(1 - rigidbody.velocity.magnitude / topSpeed) * rotationDirection * Time.deltaTime * 2); | |
} | |
} | |
} | |
/**************************************************/ | |
/* Utility Functions */ | |
/**************************************************/ | |
function Convert_Miles_Per_Hour_To_Meters_Per_Second(value : float) : float | |
{ | |
return value * 0.44704; | |
} | |
function Convert_Meters_Per_Second_To_Miles_Per_Hour(value : float) : float | |
{ | |
return value * 2.23693629; | |
} | |
function HaveTheSameSign(first : float, second : float) : boolean | |
{ | |
if (Mathf.Sign(first) == Mathf.Sign(second)) | |
return true; | |
else | |
return false; | |
} | |
function EvaluateSpeedToTurn(speed : float) | |
{ | |
if(speed > topSpeed / 2) | |
return minimumTurn; | |
var speedIndex : float = 1 - (speed / (topSpeed / 2)); | |
return minimumTurn + speedIndex * (maximumTurn - minimumTurn); | |
} | |
function EvaluateNormPower(normPower : float) | |
{ | |
if(normPower < 1) | |
return 10 - normPower * 9; | |
else | |
return 1.9 - normPower * 0.9; | |
} | |
function GetGearState() | |
{ | |
var relativeVelocity : Vector3 = transform.InverseTransformDirection(rigidbody.velocity); | |
var lowLimit : float = (currentGear == 0 ? 0 : gearSpeeds[currentGear-1]); | |
return (relativeVelocity.z - lowLimit) / (gearSpeeds[currentGear - lowLimit]) * (1 - currentGear * 0.1) + currentGear * 0.1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment