Custom car physics issue with steering forces

I created a custom car physics wich has suspension, torque forces, steering rotation and steering forces.

First Scenario:
The steering force method iIS NOT USED (line that calls HandleSteerPhysics commented), and the car drives with 0 grip, steering around by himself and when i drive him.

https://vimeo.com/952033528

Second Scenario:
The steering force method IS USED, and the car becomes crazy bouncing in the world space.

https://vimeo.com/952032558

  • At this point i am definitly not sure where the problem is.
  • Is it my car structure? the way i created the game objects hierarchy
  • Is it the actual force?
  • I’ll let references down here to show how is my code and how is my gameobjects configured

Tires and springs:
All springs: Strength 2000, Damping 150, rest distance 0.2, max distance 0.3
Front Tires: Grip 1, Max steer angle 35
Back Tires: Grip 1, Max steer angle 0

Images:


Code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarController : Mechanic
{
    [Header("[Car Parts]")]
    [SerializeField] List<WheelAssembly> wheelAssemblies;

    [Header("[Car Movement]")]
    [SerializeField] float acceleration;
    [SerializeField] float maxTorque;
    [SerializeField] float wheelBase;
    [SerializeField] float trackWidth;
    [SerializeField] LayerMask layerMask;
    [SerializeField] bool isGrounded;

    private Rigidbody rb;
    void Start() {
        rb = GetComponent<Rigidbody>();
    }

    void Update() {
        HandleSteering();
    }

    void FixedUpdate() {
        HandlePhysics();
    }

    float SuspensionMaxDist(SpringTip springTip, Tire tire) {
        return springTip.maxDist + tire.mesh.bounds.size.y;
    }

    float SuspensionRestDist(SpringTip springTip, Tire tire) {
        return springTip.restDist + tire.mesh.bounds.size.y;
    }

    void HandlePhysics() {
        foreach (WheelAssembly WA in wheelAssemblies) {
            SpringTip springTip = WA.springTip;
            Tire tire = WA.tire;
            float rayDistance = SuspensionMaxDist(springTip, tire);

            Debug.DrawRay(springTip.transform.position, Vector3.down * rayDistance, Color.green);

            if (Physics.Raycast(springTip.transform.position, -springTip.transform.up, out RaycastHit hit, rayDistance, layerMask)) {
                isGrounded = true;
                HandleSuspensionPhysics(springTip, tire, hit);
                HandleTorquePhysics(tire, hit);
                HandleSteerPhysics(tire, hit);
            } else {
                isGrounded = false;
            }
        }
    }

    void HandleSuspensionPhysics(SpringTip springTip, Tire tire, RaycastHit hit) {
        Vector3 springVel = rb.GetPointVelocity(springTip.transform.position);

        float projectedVelocity = Vector3.Dot(springTip.transform.up, springVel);
        float springOffset = SuspensionRestDist(springTip, tire) - hit.distance;
        float force = (springOffset * springTip.strength) - (projectedVelocity * springTip.damping);

        rb.AddForceAtPosition(springTip.transform.up * force, springTip.transform.position);
    }

    // Basic newton force law
    void HandleTorquePhysics(Tire tire, RaycastHit _) {
        if (Mathf.Abs(rb.velocity.magnitude) < maxTorque) {
            float accelerationInput = Input.GetAxis("Vertical");
            float accelerationFactor = accelerationInput * acceleration;
            float torque = rb.mass / wheelAssemblies.Count * accelerationFactor;

            rb.AddForceAtPosition(tire.transform.forward * torque, tire.transform.position);
        } else {
            rb.velocity = rb.velocity.normalized * maxTorque;
        }
    }

    void HandleSteerPhysics(Tire tire, RaycastHit _) {
        Vector3 steerDirection = tire.transform.right;
        Vector3 tireVelocity = rb.GetPointVelocity(tire.transform.position);

        float steeringVelocity = Vector3.Dot(steerDirection, tireVelocity);
        float desiredChangeVelocity = -steeringVelocity * tire.grip / Time.fixedDeltaTime;

        Vector3 force = steerDirection * (rb.mass / wheelAssemblies.Count) * desiredChangeVelocity;

        rb.AddForceAtPosition(force, tire.transform.position);
    }

    // Kinematics Ackerman Geometry
    void HandleSteering() {
        foreach (WheelAssembly WA in wheelAssemblies) {
            Tire tire = WA.tire;
            if (tire.position == Tire.Position.FL || tire.position == Tire.Position.FR) {
                float deltaSin = Input.GetAxis("Horizontal");
                float deltaAck = deltaSin * tire.maxSteerAngle * Mathf.Deg2Rad;
                float tanDeltaAck = Mathf.Tan(deltaAck);

                float deltaRight = Mathf.Atan( wheelBase * tanDeltaAck / (wheelBase - 0.5f * trackWidth * tanDeltaAck) ) * Mathf.Rad2Deg;
                float deltaLeft = Mathf.Atan( wheelBase * tanDeltaAck / (wheelBase + 0.5f * trackWidth * tanDeltaAck) ) * Mathf.Rad2Deg;

                if (tire.position == Tire.Position.FL) {
                    tire.transform.localRotation = Quaternion.Lerp(tire.transform.localRotation, Quaternion.Euler(tire.transform.localEulerAngles.x, deltaLeft, tire.transform.localEulerAngles.z), Time.deltaTime * 5);
                } else if (tire.position == Tire.Position.FR) {
                    tire.transform.localRotation = Quaternion.Lerp(tire.transform.localRotation, Quaternion.Euler(tire.transform.localEulerAngles.x, deltaRight, tire.transform.localEulerAngles.z), Time.deltaTime * 5);
                }
            }
        }
    }
}
using System;
using UnityEngine;

[Serializable]
public struct WheelAssembly {
    public SpringTip springTip;
    public Tire tire;
}

[Serializable]
public struct SpringTip {
    [SerializeField] public Transform transform;
    [SerializeField] public float strength;
    [SerializeField] public float damping;
    [SerializeField] public float restDist;
    [SerializeField] public float maxDist;
}

[Serializable]
public struct Tire {
    [SerializeField] public Transform transform;
    [SerializeField] public MeshRenderer mesh;
    [SerializeField] public Position position;
    [SerializeField] public float maxSteerAngle;
    [SerializeField] [Range(0, 1)] public float grip;
    public enum Position {
        FL,
        FR,
        BL,
        BR
    }
}

I followed by the way the tutorial from Toyful Games, wich is a awesome explanation about car physics