Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Automagic wheelcollider configurator

Discussion in 'Physics' started by JamesLeeNZ, Sep 1, 2015.

  1. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    This is not designed to be a final solution, but it should help you get a basic vehicle going without too much effort provided a few conditions are met:

    1. You vehicle must have the correct rotations. This is for both meshes and pivots. If the car isn't facing the right direction or any of your mesh rotations are not 0,0,0 when you drag it into the scene, I make no promises of the results.

    2. You only need to drag this script onto your vehicle and a box collider onto the main car body mesh (probably doesn't need to be a box collider, but for now, lets go with the basics). The script will add and configure the rigidbody.

    3. Your wheel meshes need to have the name wheel in them. The script looks through all transforms and finds anything with wheel in the name (it excludes anything with steering).

    It has an ugly ass GUI to let you configure stuff while running. It has a few debug drawlines that indicate various things like torque/brake/rpm

    I may extend it further later to include slip indicators, but for now, it is what it is. A free get me started script.

    I might make a demo unitypackage later if people have trouble with it.

    Code (csharp):
    1.  
    2.  
    3. using UnityEngine;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. public enum SuspensionComponent
    8. {
    9.     Damper,
    10.     Spring,
    11.     TargetPos
    12. };
    13.  
    14. public class SimpleWheel : MonoBehaviour
    15. {
    16.  
    17.     public bool IsPowerWheel;
    18.     public bool IsTurnWheel;
    19.  
    20.     public bool IsLeftWheel;
    21.     public bool IsRearWheel;
    22.  
    23.     public float MaxRpm;
    24.     public float MaxSpeed;
    25.  
    26.     public float CurrentRpm;
    27.     public float CurrentTorque;
    28.     public float CurrentSteer;
    29.     public float CurrentBrake;
    30.  
    31.     WheelFrictionCurve wheelForwardFriction = new WheelFrictionCurve();
    32.     WheelFrictionCurve wheelSidewaysFriction = new WheelFrictionCurve();
    33.     JointSpring spring = new JointSpring();
    34.  
    35.     #region internal refernces
    36.  
    37.     GameObject WheelCollider;
    38.     WheelCollider wheelCollider;
    39.     Transform wheel;
    40.  
    41.     Quaternion q;
    42.     Vector3 p;
    43.  
    44.     #endregion
    45.  
    46.     void Start()
    47.     {
    48.         wheel = transform;
    49.  
    50.         WheelCollider = new GameObject("Wheel");
    51.         WheelCollider.transform.parent = transform.root;
    52.         WheelCollider.transform.localPosition = wheel.localPosition;
    53.         //WheelCollider.layer = LayerMask.NameToLayer(Layers.WHEEL);
    54.  
    55.         wheelCollider = WheelCollider.AddComponent<WheelCollider>();
    56.         wheelCollider.radius = wheel.GetComponent<MeshRenderer>().bounds.extents.y;
    57.  
    58.         IsRearWheel = wheel.localPosition.z < 0;
    59.         IsLeftWheel = wheel.localPosition.x < 0;
    60.  
    61.         if (IsRearWheel)
    62.             IsPowerWheel = true;
    63.         else IsTurnWheel = true;
    64.      
    65.  
    66.         SetupWheelCollider();
    67.     }
    68.  
    69.  
    70.     void SetupWheelCollider()
    71.     {
    72.         wheelForwardFriction.extremumSlip = 0.6f;
    73.         wheelForwardFriction.extremumValue = 1;
    74.         wheelForwardFriction.asymptoteSlip = 0.8f;
    75.         wheelForwardFriction.asymptoteValue = 0.5f;
    76.         wheelForwardFriction.stiffness = 2f;
    77.         wheelCollider.forwardFriction = wheelForwardFriction;
    78.  
    79.         wheelSidewaysFriction.extremumSlip = 0.5f;
    80.         wheelSidewaysFriction.extremumValue = 1;
    81.         wheelSidewaysFriction.asymptoteSlip = 0.8f;
    82.         wheelSidewaysFriction.asymptoteValue = 0.75f;
    83.         wheelSidewaysFriction.stiffness = 4;
    84.         wheelCollider.sidewaysFriction = wheelSidewaysFriction;
    85.  
    86.         spring.spring = 6000;
    87.         spring.damper = 1000;
    88.         spring.targetPosition = 0.5f;
    89.  
    90.         wheelCollider.suspensionSpring = spring;
    91.         wheelCollider.suspensionDistance = 0.5f;
    92.     }
    93.  
    94.     public void SetMaxRpm(float maxRpm)
    95.     {
    96.         MaxRpm = maxRpm;
    97.     }
    98.  
    99.     public void SetTorque(float torque)
    100.     {
    101.         if (IsPowerWheel)
    102.         {
    103.             if (Mathf.Abs(torque) > 0)
    104.             {
    105.                 if (Mathf.Abs(wheelCollider.rpm) < MaxRpm)
    106.                     wheelCollider.motorTorque = torque;
    107.                 else wheelCollider.motorTorque = 0;
    108.             }
    109.             else wheelCollider.motorTorque = torque;
    110.         }
    111.         else if (Mathf.Abs(wheelCollider.rpm) > MaxRpm)
    112.         {
    113.             wheelCollider.brakeTorque = Mathf.Abs(wheelCollider.rpm);
    114.         }
    115.     }
    116.  
    117.     public void SetSteering(float steering)
    118.     {
    119.         if(IsTurnWheel)
    120.             wheelCollider.steerAngle = steering * (IsRearWheel ? -1 : 1);
    121.     }
    122.  
    123.     public void SetBrake(float brake)
    124.     {
    125.         if (IsPowerWheel)
    126.         {
    127.             if (brake > 0)
    128.                 wheelCollider.brakeTorque = Mathf.Clamp(Mathf.Abs(wheelCollider.rpm) * 8f, 100, 10000);
    129.             else wheelCollider.brakeTorque = 0;
    130.         }
    131.         else
    132.         {
    133.             if (!wheelCollider.isGrounded || brake > 0)
    134.                 wheelCollider.brakeTorque = Mathf.Abs(wheelCollider.rpm);
    135.             else wheelCollider.brakeTorque = 0;
    136.  
    137.         }
    138.     }
    139.  
    140.     void Update()
    141.     {
    142.         CurrentTorque = wheelCollider.motorTorque;
    143.         CurrentSteer = wheelCollider.steerAngle;
    144.         CurrentBrake = wheelCollider.brakeTorque;
    145.         CurrentRpm = wheelCollider.rpm;
    146.  
    147.         Debug.DrawRay(wheel.position, transform.parent.forward * CurrentTorque / 100, Color.blue);
    148.         Debug.DrawRay(wheel.position, Vector3.up * CurrentRpm / 100, Color.green);
    149.         Debug.DrawRay(wheel.position, -transform.parent.forward * CurrentBrake / 100, Color.red);
    150.  
    151.         wheelCollider.GetWorldPose(out p, out q);
    152.         wheel.position = p;
    153.         wheel.rotation = q;
    154.     }
    155.  
    156.     void OnGUI()
    157.     {
    158.         int x = 0;
    159.         string title = "";
    160.         if (IsLeftWheel)
    161.         {
    162.             if (IsRearWheel)
    163.             {
    164.                 x = 200;
    165.                 title = " Rear Left";
    166.             }
    167.             else
    168.             {
    169.                 x = 400;
    170.                 title = " Front Left";
    171.             }
    172.         }
    173.         else
    174.         {
    175.             if (IsRearWheel)
    176.             {
    177.                 x = 300;
    178.                 title = " Rear Right";
    179.             }
    180.             else
    181.             {
    182.                 x = 500;
    183.                 title = " Front Right";
    184.             }
    185.         }
    186.  
    187.         GUI.BeginGroup(new Rect(x, Screen.height-200, 100, 200));
    188.         GUI.Box(new Rect(0, 0, 100, 200), "");
    189.  
    190.         GUI.Label(new Rect(0, 0, 100, 20), title);
    191.         GUI.Label(new Rect(0, 20, 100, 20), string.Format(" RPM: {0:00.0}", CurrentRpm));
    192.  
    193.         GUI.Label(new Rect(0, 40, 100, 20), string.Format(" Torque: {0:00.0}", CurrentTorque));
    194.         GUI.Label(new Rect(0, 60, 100, 20), string.Format(" Brake: {0:00.0}", CurrentBrake));
    195.  
    196.         GUI.Label(new Rect(0, 120, 100, 20), string.Format(" Damper: {0:00.0}",spring.damper));
    197.         GUI.Label(new Rect(0, 140, 100, 20), string.Format(" Spring: {0:00.0}", spring.spring));
    198.         GUI.Label(new Rect(0, 160, 100, 20), string.Format(" TargetPos: {0:0.0}", spring.targetPosition));
    199.  
    200.         GUI.EndGroup();
    201.     }
    202.  
    203.     internal void SetSuspension(float value, SuspensionComponent type)
    204.     {
    205.         switch (type)
    206.         {
    207.             case SuspensionComponent.Damper:
    208.                 spring.damper = value;
    209.                 break;
    210.  
    211.             case SuspensionComponent.Spring:
    212.                 spring.spring = value;
    213.                 break;
    214.  
    215.             case SuspensionComponent.TargetPos:
    216.                 spring.targetPosition = value;
    217.                 break;
    218.  
    219.         }
    220.  
    221.         wheelCollider.suspensionSpring = spring;
    222.     }
    223.  
    224.     internal JointSpring GetSpring()
    225.     {
    226.         return spring;
    227.     }
    228. }
    229.  
    230. public class SimpleVehicle : MonoBehaviour {
    231.  
    232.     List<SimpleWheel> wheels = new List<SimpleWheel>();
    233.  
    234.     public float Weight = 1000;
    235.  
    236.     public float MaxSpeed = 30;
    237.     public float MaxRpm = 500;
    238.     public float MaxTorque = 2000;
    239.     public float MaxTurn = 30;
    240.     public float MaxBrake = 1000;
    241.  
    242.     public bool IsFrontWheelDrive = false;
    243.     public bool IsRearWheelDrive = true;
    244.     public bool IsFrontWheelSteer = true;
    245.     public bool IsRearWheelSteer = false;
    246.  
    247.     public float acceleration = 0;
    248.     public float turn = 0;
    249.  
    250.     public float currentTurn = 0;
    251.     public float currentTorque = 0;
    252.     public float currentBrake = 0;
    253.     public float currentSpeed = 0;
    254.     public float currentSpeedKMs = 0;
    255.  
    256.     public float currentSuspensionDamper = 0;
    257.     public float currentSuspensionSpring = 0;
    258.     public float currentSuspensionHeight = 0;
    259.  
    260.     Rigidbody _rigidbody;
    261.  
    262.     void Start () {
    263.         foreach (var mesh in GetComponentsInChildren<MeshRenderer>())
    264.         {
    265.             if (mesh.name.ToLower().Contains("steering"))
    266.                 continue;
    267.  
    268.             if (mesh.name.ToLower().Contains("wheel"))
    269.                 wheels.Add(mesh.gameObject.AddComponent<SimpleWheel>());
    270.         }
    271.  
    272.         _rigidbody = gameObject.AddComponent<Rigidbody>();
    273.         _rigidbody.mass = Weight;
    274.         _rigidbody.centerOfMass = Vector3.down;
    275.  
    276.     }
    277.  
    278.     void Update()
    279.     {
    280.         acceleration = Input.GetKey(KeyCode.W) ? 1 : Input.GetKey(KeyCode.S) ? -1 : 0;
    281.         turn = Input.GetKey(KeyCode.A) ? -1 : Input.GetKey(KeyCode.D) ? 1 : 0;
    282.  
    283.         currentTurn = MoveTo(currentTurn, turn * MaxTurn, 1);
    284.         currentTorque = acceleration * MaxTorque;
    285.         currentBrake = acceleration == 0 ? MaxBrake : 0;
    286.  
    287.         Debug.DrawRay(_rigidbody.worldCenterOfMass, transform.forward * _rigidbody.velocity.magnitude, Color.magenta);
    288.      
    289.     }
    290.  
    291.     void OnGUI()
    292.     {
    293.         GUI.BeginGroup(new Rect(0, Screen.height-200, 200, 200));
    294.         GUI.Box(new Rect(0, 0, 200, 200), "");
    295.  
    296.         GUI.Label(new Rect(0, 0, 100, 20), string.Format("Speed: {0:00.0}",currentSpeedKMs));
    297.         MaxSpeed = int.Parse(GUI.TextField(new Rect(100, 0, 100, 20), MaxSpeed.ToString()));
    298.  
    299.         GUI.Label(new Rect(0, 20, 100, 20), string.Format("Torque: {0:00.0}", currentTorque));
    300.         MaxTorque = int.Parse(GUI.TextField(new Rect(100, 20, 100, 20), MaxTorque.ToString()));
    301.  
    302.         ChangeDrive(true,GUI.Toggle(new Rect(0, 40, 200, 20), IsFrontWheelDrive, "Front Wheel Drive"));
    303.         ChangeDrive(false,GUI.Toggle(new Rect(0, 60, 200, 20), IsRearWheelDrive, "Rear Wheel Drive"));
    304.  
    305.         ChangeSteer(true, GUI.Toggle(new Rect(0, 80, 200, 20), IsFrontWheelSteer, "Front Wheel Steer"));
    306.         ChangeSteer(false, GUI.Toggle(new Rect(0, 100, 200, 20), IsRearWheelSteer, "Rear Wheel Steer"));
    307.  
    308.         ChangeSuspension(GUI.HorizontalSlider(new Rect(20, 125, 160, 20), currentSuspensionDamper, 500, 9999), SuspensionComponent.Damper);
    309.         ChangeSuspension(GUI.HorizontalSlider(new Rect(20, 145, 160, 20), currentSuspensionSpring, 500, 9999), SuspensionComponent.Spring);
    310.         ChangeSuspension(GUI.HorizontalSlider(new Rect(20, 165, 160, 20), currentSuspensionHeight, 0, 1), SuspensionComponent.TargetPos);
    311.  
    312.         GUI.EndGroup();
    313.     }
    314.  
    315.     void ChangeSuspension(float value, SuspensionComponent type)
    316.     {
    317.         switch (type)
    318.         {
    319.             case SuspensionComponent.Damper:
    320.                 if (value != currentSuspensionDamper)
    321.                 {
    322.                     for (int x = 0; x < wheels.Count; x++)
    323.                         wheels[x].SetSuspension(value, type);
    324.  
    325.                     currentSuspensionDamper = value;// Mathf.Clamp(value, 500, 10000);
    326.                 }
    327.                 break;
    328.  
    329.             case SuspensionComponent.Spring:
    330.                 if (value != currentSuspensionSpring)
    331.                 {
    332.                     for (int x = 0; x < wheels.Count; x++)
    333.                         wheels[x].SetSuspension(value, type);
    334.  
    335.                     currentSuspensionSpring = value;
    336.                 }
    337.                 break;
    338.  
    339.             case SuspensionComponent.TargetPos:
    340.                 if (value != currentSuspensionHeight)
    341.                 {
    342.                     for (int x = 0; x < wheels.Count; x++)
    343.                         wheels[x].SetSuspension(1 - value, type);
    344.  
    345.                     currentSuspensionHeight = value;
    346.                 }
    347.                 break;
    348.         }
    349.  
    350.  
    351.  
    352.     }
    353.  
    354.     void ChangeDrive(bool front, bool on)
    355.     {
    356.         if (front)
    357.         {
    358.             if (on != IsFrontWheelDrive)
    359.             {
    360.                 for (int x = 0; x < wheels.Count; x++)
    361.                 {
    362.                     if (!wheels[x].IsRearWheel)
    363.                         wheels[x].IsPowerWheel = on;
    364.                 }
    365.                 IsFrontWheelDrive = on;
    366.             }
    367.         }
    368.         else
    369.         {
    370.             if (on != IsRearWheelDrive)
    371.             {
    372.                 for (int x = 0; x < wheels.Count; x++)
    373.                 {
    374.                     if (wheels[x].IsRearWheel)
    375.                         wheels[x].IsPowerWheel = on;
    376.                 }
    377.                 IsRearWheelDrive = on;
    378.             }
    379.         }
    380.     }
    381.  
    382.  
    383.     void ChangeSteer(bool front, bool on)
    384.     {
    385.         if (front)
    386.         {
    387.             if (on != IsFrontWheelSteer)
    388.             {
    389.                 for (int x = 0; x < wheels.Count; x++)
    390.                 {
    391.                     if (!wheels[x].IsRearWheel)
    392.                         wheels[x].IsTurnWheel = on;
    393.                 }
    394.                 IsFrontWheelSteer = on;
    395.             }
    396.         }
    397.         else
    398.         {
    399.             if (on != IsRearWheelSteer)
    400.             {
    401.                 for (int x = 0; x < wheels.Count; x++)
    402.                 {
    403.                     if (wheels[x].IsRearWheel)
    404.                         wheels[x].IsTurnWheel = on;
    405.                 }
    406.                 IsRearWheelSteer = on;
    407.             }
    408.         }
    409.     }
    410.  
    411.     float MoveTo(float current, float target, float rate)
    412.     {
    413.         if (current < target)
    414.             current += rate;
    415.         if (current > target)
    416.             current -= rate;
    417.  
    418.         return current;
    419.     }
    420.  
    421.     // Update is called once per frame
    422.     void FixedUpdate () {
    423.  
    424.         for (int x = 0; x < wheels.Count; x++)
    425.         {
    426.             wheels[x].SetMaxRpm(MaxRpm);
    427.  
    428.             wheels[x].SetSteering(currentTurn);
    429.             wheels[x].SetTorque(currentSpeedKMs < MaxSpeed ? currentTorque : 0);
    430.             wheels[x].SetBrake(currentBrake);
    431.  
    432.             //wheels[x].SetSuspension(
    433.         }
    434.  
    435.         currentSpeed = _rigidbody.velocity.magnitude;
    436.         currentSpeedKMs = currentSpeed * 3.6f;
    437.     }
    438. }
    439.  
    440.  
    441.  
     
    Last edited: Sep 2, 2015
  2. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,497
    That's a nice kick starter script! Thanks!
    I think I've found something that might not be correct:

    wheelSidewaysFriction.extremumSlip = 0.8f;
    wheelSidewaysFriction.extremumValue = 1;
    wheelSidewaysFriction.asymptoteSlip = 0.5f;
    wheelSidewaysFriction.asymptoteValue = 0.75f;
    wheelSidewaysFriction.stiffness = 4;
    wheelCollider.sidewaysFriction = wheelSidewaysFriction;​

    As far as I know, extremum slip is located before asymptote slip. So I'm not sure if those slip numbers make sense. In theory, extremumSlip should be < asymptoteSlip. This part hasn't changed from Unity 4.



    (http://docs.unity3d.com/Manual/class-WheelCollider.html)
     
  3. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    You're probably right. I have tooled around withthe slip settings much. Was gonna be a future enhancement. Never really understood the slip stuff.
     
  4. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Looking at it again, I always assumed that graph meant extremum > asymptote, but I read it on the Force axis which is obviously backwards to how it should be.

    Is the concept that any value over Asym is considered slipping?
     
  5. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,497
    In theory, slip is the velocity of the tire over the ground. The more slip, the more force until reaching the extremum force. If slip keeps increasing beyond that point, the force begin decreasing until the minimum asymptote. There's no clear difference among "slipping" and "not slipping". The tire is considered to be always slipping, and producing force according to the actual slip amount.

    That's the theory. In practice, I've been unable to extract any logic on the slip vs. force values in the WheelCollider, specially in sideways slipping (where slip seems to be limited to the range [-1, +1]).
     
  6. Bidsy

    Bidsy

    Joined:
    Sep 15, 2015
    Posts:
    14
    Hi James, I thought I'd reply here
    I thought there might of been a problem with the vehicle I was using so I imported "Sedan Car" and I got these 3 errors

    WheelCollider requires an attached Rigidbody to function
    MissingComponentException: There is no 'MeshRender' attached to the "sedanpref"
    NullReferenceException; Object reference not set to an instance of an object.

    Also this might be a stupid question but when I copy and paste your script from above into monodevelop it copies in every line number. What is the best way to prevent this from happening.
     
  7. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Easiest way to get the code, is hit the reply button to the first post, it will put everything as a quote into the reply box (without all the line numbers etc).

    As for you specific problems I would say the script is failing early for some reason. Given the content of the script and your error, im not 100% why its failing. I assume the script is un-changed? It shouldnt be throwing an error stating it cant find a meshrenderer considering it only searches for meshrenderers.

    I would say youre going to have to send me the car model so I can take a look at why its not working. If youre interested, pm me a link, or whatever and Ill give you my email to send it.