I made this like 3 months ago, I guess some people want it.
http://yogware.bluegillstudios.com/blogfiles/CarPakke2.zip
By the way there may be some bug in the build / web player, I have no idea what is up with that.
And here is a version of the Wheel.js script with comments.
Show/Hide
var mass = 1.00;
var wheelRadius = 0.00;
var suspensionRange = 0.00;
var suspensionForce = 0.00;
var suspensionDamp = 0.00;
var compressionFrictionFactor = 0.00;
var sidewaysFriction = 0.00;
var sidewaysDamp = 0.00;
var sidewaysSlipVelocity = 0.00;
var sidewaysSlipForce = 0.00;
var sidewaysSlipFriction = 0.00;
var sidewaysStiffnessFactor = 0.00;
var forwardFriction = 0.00;
var forwardSlipVelocity = 0.00;
var forwardSlipForce = 0.00;
var forwardSlipFriction = 0.00;
var forwardStiffnessFactor = 0.00;
var frictionSmoothing = 1.00;
private var hit : RaycastHit;
private var parent : Rigidbody;
parent = transform.root.rigidbody;
private var graphic : Transform;
graphic = transform.FindChild("Graphic");
private var wheelCircumference = 0.00;
wheelCircumference = wheelRadius * Mathf.PI * 2;
private var usedSideFriction = 0.00;
private var usedForwardFriction = 0.00;
private var sideSlip = 0.00;
private var forwardSlip = 0.00;
var car : Car;
var driven = false;
var speed = 0.00;
var brake = false;
var skidbrake = false;
function FixedUpdate ()
{
down = transform.TransformDirection(Vector3.down);
// the object this script is attached to does not move. This ray is like imagining that the shock is compressed as much as possible, and then extends out as far as it can before it hits the ground. If it doesn't, then the wheel is fully extended.
if(Physics.Raycast (transform.position, down, hit, suspensionRange + wheelRadius) && hit.collider.transform.root != transform.root)
{
// how fast is the wheel moving relative to the ground, without taking the wheel's rotation into account
velocityAtTouch = parent.GetPointVelocity(hit.point);
// calculate the force of the shock as it pushes on the car and the earth due to compression. Since the earth does not move, this is an upward force on the car only
compression = hit.distance / (suspensionRange + wheelRadius);
compression = -compression + 1;
force = -down * compression * suspensionForce;
// Here we set t equal to the speed at which the shock is contracting / expanding
t = transform.InverseTransformDirection(velocityAtTouch);
t.z = t.x = 0;
// this force simulates the force exerted by the friction in the suspension.
shockDrag = transform.TransformDirection(t) * -suspensionDamp;
// the difference between the speed of the ground and the wheel taking rotation + the car's velocity into account. (in local space, that means, relative to the wheel. So if you were standing on the wheel surface, how fast would you percieve the ground moving? Either squashing you every time the wheel goes around (not skidding, absolute value close to zero) or scrapping you to bits (skidding, large absolute value) )
forwardDifference = transform.InverseTransformDirection(velocityAtTouch).z - speed;
//Ok, this next part is not related to real physics an any way whatsoever. Ummm... Basically the friction that the wheel has with the ground changes (using a lerp function over time) depending on the current friction force. I guess this simulates how once a car starts skidding, the friction goes down so it skids more.
// __________________z-friction__________________
// move the current working friction value toward the minimum about porportional to the "skidding-ness" squared
newForwardFriction = Mathf.Lerp(forwardFriction, forwardSlipFriction, forwardSlip * forwardSlip);
// increase the current working friction value depending on how hard the shock is pressing the wheel against the ground
newForwardFriction = Mathf.Lerp(newForwardFriction, newForwardFriction * compression, compressionFrictionFactor);
// smooth the friction value
if(frictionSmoothing > 0)
usedForwardFriction = Mathf.Lerp(usedForwardFriction, newForwardFriction, Time.fixedDeltaTime / frictionSmoothing);
else
usedForwardFriction = newForwardFriction;
// calculate one component of the friction force: the difference between the wheel surface velocity and ground surface velocity times friction (not based on real physics AFAIK)
forwardForce = transform.TransformDirection(Vector3(0, 0, -forwardDifference)) * usedForwardFriction;
// calculate how much we be slippin (if the force is high, the tire will give and slip on the road)
forwardSlip = Mathf.Lerp(forwardForce.magnitude / forwardSlipForce, forwardDifference / forwardSlipVelocity, forwardStiffnessFactor);
// ____________________________________
// this is much the same as the block above, but for the x-axis
// __________________x-friction__________________
sidewaysDifference = transform.InverseTransformDirection(velocityAtTouch).x;
newSideFriction = Mathf.Lerp(sidewaysFriction, sidewaysSlipFriction, sideSlip * sideSlip);
newSideFriction = Mathf.Lerp(newSideFriction, newSideFriction * compression, compressionFrictionFactor);
if(frictionSmoothing > 0)
usedSideFriction = Mathf.Lerp(usedSideFriction, newSideFriction, Time.fixedDeltaTime / frictionSmoothing);
else
usedSideFriction = newSideFriction;
sideForce = transform.TransformDirection(Vector3(-sidewaysDifference, 0, 0)) * usedSideFriction;
sideSlip = Mathf.Lerp(sideForce.magnitude / sidewaysSlipForce, sidewaysDifference / sidewaysSlipVelocity, sidewaysStiffnessFactor);
// ____________________________________
// this thing is totally made up. It's as if god's hand nudges the car back on course whenever it is moving sideways, by a factor of sidewaysDamp
t = transform.InverseTransformDirection(velocityAtTouch);
t.z = t.y = 0;
sideDrag = transform.TransformDirection(t) * -sidewaysDamp;
// By the some of all you combined, I become CAPTAIN FORCE
parent.AddForceAtPosition(force + shockDrag + forwardForce + sideForce + sideDrag, hit.point);
// for every action there is an opposite reaction: the wheel adds a force on the ground, but the ground does not move, so that force is added to the car instead in the opposite direction (this lets the engine propel the thing forward). But!!! we also have to add a force on the engine, because otherwise we'd be adding force from nothing. The opposite reaction is this force on the motor. The motor is not a rigidbody, it is a made up thing in the Car.js script
if(driven)
car.AddForceOnMotor (forwardDifference * usedForwardFriction * Time.fixedDeltaTime);
else
// if this wheel isn't attached to the motor, just add the force to the wheel instead
speed += forwardDifference;
graphic.position = transform.position + (down * (hit.distance - wheelRadius));
}
else
{
graphic.position = transform.position + (down * suspensionRange);
}
graphic.transform.Rotate(360 * (speed / wheelCircumference) * Time.fixedDeltaTime, 0, 0);
}
function OnDrawGizmos () {
Gizmos.color = Color.yellow;
Gizmos.DrawRay (transform.position, transform.TransformDirection (Vector3.up) * -wheelRadius);
}


