Legacy Unity Car Controller Code (Javascript)
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