# Car Tire Forces instability

Hello everyone,
I am not a very experienced developer and I am trying to make a physics based car game. I have read this as my inspiration and guide, but have been stuck for a too long of a time on an issue:

GDSim v0.4a - Autocross and Custom Setups | GTPlanet (archive.org)

Previously whenever I would start my game the angular velocity of all wheels would just accelerate infinitely into negative or positive without any input. I have since rewritten everything trying to search for mistake(s), but right now experiencing issues not much different. I have managed to make the car stable without any input, but once there is gas input, the car first accelerates forward, inverses direction and starts rotating in all sorts of different ways uncontrollably. From my understanding the slip ratio calculation must be at fault, as that is the input feeding into the simplified Pacejka formula. The angular velocity is just a variable of the wheel script, which is defined through
`angularvelocity += Time.fixedDeltaTime * netTorque / momentOfInertia`
every `Fixed Update`. I am not calculating lateral slip at all yet, trying to understand with what is wrong with the longitudinal force first.

I would really appreciate somebody experienced in this who knows better than me to explain this. I am mostly interested in,

### is this issue just a mistake I made or is there an entire procedure/calculation I just didn’t even know about?

My code for the wheel script:

``````private void Start()
{
rb = transform.root.GetComponent<Rigidbody>();
minLength = restLength - travel;
maxLength = restLength + travel;
}

private void FixedUpdate()
{
SimulateSuspension();
if (grounded)
{
ApplyForces();
CalculateNetTorque();
SimulateTire();
slip.y = AlternateSlipRatio();
}
else
{
slip = Vector2.zero;
}
localVelo = transform.InverseTransformDirection(rb.velocity);
forward = transform.TransformDirection(transform.localRotation * Vector3.forward);
}

private void SimulateTire()
{
tireForce.x = Pacejka(slip.x, xShape);
tireForce.z = Pacejka(slip.y, yShape);
//slip.x = SlipAngle();
}

private void CalculateNetTorque()
{
netTorque += motorTorque;
if (Mathf.Abs(angularVelocity) < 5 && brakeTorque > Mathf.Abs(netTorque))
{
angularVelocity = 0;
}
else
{
netTorque -= brakeTorque * Mathf.Sign(angularVelocity);
netTorque -= rollingResistanceForce * Mathf.Sign(angularVelocity);
angularVelocity += Time.fixedDeltaTime * netTorque / wheelMoment;
}
}

float AlternateSlipRatio()
{
const float fullSlipVelo = 4.0f;
const float minVelocityThreshold = 0.1f; // Threshold for low-speed adjustments
const float minSpeedForSlip = 1e-5f; // Minimum speed to consider for slip calculation

// Calculate the component of the local velocity in the direction the wheel is facing

// Absolute value of road velocity

// Calculate the linear velocity at the circumference of the wheel
float wheelTireVelo = angularVelocity * wheelRadius;

// Handle near-zero road velocity separately to avoid instability
{
// Use a simplified slip calculation when the vehicle is nearly stationary
return Mathf.Clamp(wheelTireVelo / fullSlipVelo, -1.0f, 1.0f);
}

// Calculate the damping factor based on the road velocity
float damping = Mathf.Clamp01(absRoadVelo / fullSlipVelo);

// Calculate the slip ratio

// Clamp the slip ratio to avoid excessive values
return Mathf.Clamp(slipRatio, -1.0f, 1.0f);
}

private float SlipRatio()
{
float ySlip = 0;
if (Mathf.Abs(localVelo.z) > 0.00001f)
{
ySlip = angularVelocity * wheelRadius - localVelo.z / Mathf.Abs(localVelo.z);
Debug.Log("Local velocity on z axis is bigger than the theshold");
}
Debug.Log(\$"The localVelo.z = {localVelo.z}, localVelo.z > threshold = {localVelo.z > 1e-5f}");
Debug.Log(ySlip);
return ySlip;
}

float SlipAngle()
{
const float fullAngleVelo = 2.0f;

Vector3 wheelMotionDirection = localVelo;
wheelMotionDirection.y = 0;

if (wheelMotionDirection.sqrMagnitude < Mathf.Epsilon)
return 0;

float sinSlipAngle = wheelMotionDirection.normalized.x;
Mathf.Clamp(sinSlipAngle, -1, 1); // To avoid precision errors.

float damping = Mathf.Clamp01(localVelo.magnitude / fullAngleVelo);

return -Mathf.Asin(sinSlipAngle) * damping * damping;
}

private float Pacejka(float slip, float shape)
{
return suspensionForce * peak * Mathf.Sin(shape * Mathf.Atan(tireStiffness * slip - curve * (tireStiffness * slip - Mathf.Atan(tireStiffness * slip))));
}

private void SimulateSuspension()
{
if (Physics.Raycast(transform.position, -transform.up, out hitInfo, wheelRadius + maxLength))
{
grounded = true;
lastLength = length;
length = Mathf.Clamp(length, minLength, maxLength);

compression = 1 - length / maxLength;

springVelo = (lastLength - length) / Time.fixedDeltaTime;

hitNormal = hitInfo.normal;

springForce = springStiffness * (restLength - length);

if (springVelo > 0) // If the spring is compressing, use the bump shock absorber stiffness
{
damperForce = bump * springVelo;
}
if (springVelo < 0) // If the spring is decompressing, use the rebound shock absorber stiffness
{
damperForce = rebound * springVelo;
}
suspensionForce = (springForce + damperForce);
}
else
{
grounded = false;
}
}

private void ApplyForces()
{
}

private void OnDrawGizmosSelected()
{
if (Physics.Raycast(transform.position, -transform.up, out RaycastHit hit, maxLength + wheelRadius))
{
Debug.DrawRay(transform.position, -transform.up, Color.red);
}
else
{
Debug.DrawRay(transform.position, -transform.up, Color.green);
}
}
``````

Why are you using this custom wheel? Just use the `Wheel Collider` component.

I am using a custom collider, because I have already tried making a game with wheel colliders. With this project I wanted to get a higher level of realism then just arcade. The wheel collider seems to lack depth with parameters like camber and as far as I can see, I cannot modify it or build on top of it (very easily). I can’t use a different tire model if I want to, or introduce new parameters like tire pressure and camber. With the wheel collider I could never find good values for the tire friction, every time the car would either turn like it’s on rails or understeer every single time not reacting to any input at all. I would like some dynamic between understeer and oversteer. Plus as I have already tried the wheel collider component that is built in, I wanted to try making my own.