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:
-
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.
-
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.
-
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.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public enum SuspensionComponent
{
Damper,
Spring,
TargetPos
};
public class SimpleWheel : MonoBehaviour
{
public bool IsPowerWheel;
public bool IsTurnWheel;
public bool IsLeftWheel;
public bool IsRearWheel;
public float MaxRpm;
public float MaxSpeed;
public float CurrentRpm;
public float CurrentTorque;
public float CurrentSteer;
public float CurrentBrake;
WheelFrictionCurve wheelForwardFriction = new WheelFrictionCurve();
WheelFrictionCurve wheelSidewaysFriction = new WheelFrictionCurve();
JointSpring spring = new JointSpring();
#region internal refernces
GameObject WheelCollider;
WheelCollider wheelCollider;
Transform wheel;
Quaternion q;
Vector3 p;
#endregion
void Start()
{
wheel = transform;
WheelCollider = new GameObject("Wheel");
WheelCollider.transform.parent = transform.root;
WheelCollider.transform.localPosition = wheel.localPosition;
//WheelCollider.layer = LayerMask.NameToLayer(Layers.WHEEL);
wheelCollider = WheelCollider.AddComponent<WheelCollider>();
wheelCollider.radius = wheel.GetComponent<MeshRenderer>().bounds.extents.y;
IsRearWheel = wheel.localPosition.z < 0;
IsLeftWheel = wheel.localPosition.x < 0;
if (IsRearWheel)
IsPowerWheel = true;
else IsTurnWheel = true;
SetupWheelCollider();
}
void SetupWheelCollider()
{
wheelForwardFriction.extremumSlip = 0.6f;
wheelForwardFriction.extremumValue = 1;
wheelForwardFriction.asymptoteSlip = 0.8f;
wheelForwardFriction.asymptoteValue = 0.5f;
wheelForwardFriction.stiffness = 2f;
wheelCollider.forwardFriction = wheelForwardFriction;
wheelSidewaysFriction.extremumSlip = 0.5f;
wheelSidewaysFriction.extremumValue = 1;
wheelSidewaysFriction.asymptoteSlip = 0.8f;
wheelSidewaysFriction.asymptoteValue = 0.75f;
wheelSidewaysFriction.stiffness = 4;
wheelCollider.sidewaysFriction = wheelSidewaysFriction;
spring.spring = 6000;
spring.damper = 1000;
spring.targetPosition = 0.5f;
wheelCollider.suspensionSpring = spring;
wheelCollider.suspensionDistance = 0.5f;
}
public void SetMaxRpm(float maxRpm)
{
MaxRpm = maxRpm;
}
public void SetTorque(float torque)
{
if (IsPowerWheel)
{
if (Mathf.Abs(torque) > 0)
{
if (Mathf.Abs(wheelCollider.rpm) < MaxRpm)
wheelCollider.motorTorque = torque;
else wheelCollider.motorTorque = 0;
}
else wheelCollider.motorTorque = torque;
}
else if (Mathf.Abs(wheelCollider.rpm) > MaxRpm)
{
wheelCollider.brakeTorque = Mathf.Abs(wheelCollider.rpm);
}
}
public void SetSteering(float steering)
{
if(IsTurnWheel)
wheelCollider.steerAngle = steering * (IsRearWheel ? -1 : 1);
}
public void SetBrake(float brake)
{
if (IsPowerWheel)
{
if (brake > 0)
wheelCollider.brakeTorque = Mathf.Clamp(Mathf.Abs(wheelCollider.rpm) * 8f, 100, 10000);
else wheelCollider.brakeTorque = 0;
}
else
{
if (!wheelCollider.isGrounded || brake > 0)
wheelCollider.brakeTorque = Mathf.Abs(wheelCollider.rpm);
else wheelCollider.brakeTorque = 0;
}
}
void Update()
{
CurrentTorque = wheelCollider.motorTorque;
CurrentSteer = wheelCollider.steerAngle;
CurrentBrake = wheelCollider.brakeTorque;
CurrentRpm = wheelCollider.rpm;
Debug.DrawRay(wheel.position, transform.parent.forward * CurrentTorque / 100, Color.blue);
Debug.DrawRay(wheel.position, Vector3.up * CurrentRpm / 100, Color.green);
Debug.DrawRay(wheel.position, -transform.parent.forward * CurrentBrake / 100, Color.red);
wheelCollider.GetWorldPose(out p, out q);
wheel.position = p;
wheel.rotation = q;
}
void OnGUI()
{
int x = 0;
string title = "";
if (IsLeftWheel)
{
if (IsRearWheel)
{
x = 200;
title = " Rear Left";
}
else
{
x = 400;
title = " Front Left";
}
}
else
{
if (IsRearWheel)
{
x = 300;
title = " Rear Right";
}
else
{
x = 500;
title = " Front Right";
}
}
GUI.BeginGroup(new Rect(x, Screen.height-200, 100, 200));
GUI.Box(new Rect(0, 0, 100, 200), "");
GUI.Label(new Rect(0, 0, 100, 20), title);
GUI.Label(new Rect(0, 20, 100, 20), string.Format(" RPM: {0:00.0}", CurrentRpm));
GUI.Label(new Rect(0, 40, 100, 20), string.Format(" Torque: {0:00.0}", CurrentTorque));
GUI.Label(new Rect(0, 60, 100, 20), string.Format(" Brake: {0:00.0}", CurrentBrake));
GUI.Label(new Rect(0, 120, 100, 20), string.Format(" Damper: {0:00.0}",spring.damper));
GUI.Label(new Rect(0, 140, 100, 20), string.Format(" Spring: {0:00.0}", spring.spring));
GUI.Label(new Rect(0, 160, 100, 20), string.Format(" TargetPos: {0:0.0}", spring.targetPosition));
GUI.EndGroup();
}
internal void SetSuspension(float value, SuspensionComponent type)
{
switch (type)
{
case SuspensionComponent.Damper:
spring.damper = value;
break;
case SuspensionComponent.Spring:
spring.spring = value;
break;
case SuspensionComponent.TargetPos:
spring.targetPosition = value;
break;
}
wheelCollider.suspensionSpring = spring;
}
internal JointSpring GetSpring()
{
return spring;
}
}
public class SimpleVehicle : MonoBehaviour {
List<SimpleWheel> wheels = new List<SimpleWheel>();
public float Weight = 1000;
public float MaxSpeed = 30;
public float MaxRpm = 500;
public float MaxTorque = 2000;
public float MaxTurn = 30;
public float MaxBrake = 1000;
public bool IsFrontWheelDrive = false;
public bool IsRearWheelDrive = true;
public bool IsFrontWheelSteer = true;
public bool IsRearWheelSteer = false;
public float acceleration = 0;
public float turn = 0;
public float currentTurn = 0;
public float currentTorque = 0;
public float currentBrake = 0;
public float currentSpeed = 0;
public float currentSpeedKMs = 0;
public float currentSuspensionDamper = 0;
public float currentSuspensionSpring = 0;
public float currentSuspensionHeight = 0;
Rigidbody _rigidbody;
void Start () {
foreach (var mesh in GetComponentsInChildren<MeshRenderer>())
{
if (mesh.name.ToLower().Contains("steering"))
continue;
if (mesh.name.ToLower().Contains("wheel"))
wheels.Add(mesh.gameObject.AddComponent<SimpleWheel>());
}
_rigidbody = gameObject.AddComponent<Rigidbody>();
_rigidbody.mass = Weight;
_rigidbody.centerOfMass = Vector3.down;
}
void Update()
{
acceleration = Input.GetKey(KeyCode.W) ? 1 : Input.GetKey(KeyCode.S) ? -1 : 0;
turn = Input.GetKey(KeyCode.A) ? -1 : Input.GetKey(KeyCode.D) ? 1 : 0;
currentTurn = MoveTo(currentTurn, turn * MaxTurn, 1);
currentTorque = acceleration * MaxTorque;
currentBrake = acceleration == 0 ? MaxBrake : 0;
Debug.DrawRay(_rigidbody.worldCenterOfMass, transform.forward * _rigidbody.velocity.magnitude, Color.magenta);
}
void OnGUI()
{
GUI.BeginGroup(new Rect(0, Screen.height-200, 200, 200));
GUI.Box(new Rect(0, 0, 200, 200), "");
GUI.Label(new Rect(0, 0, 100, 20), string.Format("Speed: {0:00.0}",currentSpeedKMs));
MaxSpeed = int.Parse(GUI.TextField(new Rect(100, 0, 100, 20), MaxSpeed.ToString()));
GUI.Label(new Rect(0, 20, 100, 20), string.Format("Torque: {0:00.0}", currentTorque));
MaxTorque = int.Parse(GUI.TextField(new Rect(100, 20, 100, 20), MaxTorque.ToString()));
ChangeDrive(true,GUI.Toggle(new Rect(0, 40, 200, 20), IsFrontWheelDrive, "Front Wheel Drive"));
ChangeDrive(false,GUI.Toggle(new Rect(0, 60, 200, 20), IsRearWheelDrive, "Rear Wheel Drive"));
ChangeSteer(true, GUI.Toggle(new Rect(0, 80, 200, 20), IsFrontWheelSteer, "Front Wheel Steer"));
ChangeSteer(false, GUI.Toggle(new Rect(0, 100, 200, 20), IsRearWheelSteer, "Rear Wheel Steer"));
ChangeSuspension(GUI.HorizontalSlider(new Rect(20, 125, 160, 20), currentSuspensionDamper, 500, 9999), SuspensionComponent.Damper);
ChangeSuspension(GUI.HorizontalSlider(new Rect(20, 145, 160, 20), currentSuspensionSpring, 500, 9999), SuspensionComponent.Spring);
ChangeSuspension(GUI.HorizontalSlider(new Rect(20, 165, 160, 20), currentSuspensionHeight, 0, 1), SuspensionComponent.TargetPos);
GUI.EndGroup();
}
void ChangeSuspension(float value, SuspensionComponent type)
{
switch (type)
{
case SuspensionComponent.Damper:
if (value != currentSuspensionDamper)
{
for (int x = 0; x < wheels.Count; x++)
wheels[x].SetSuspension(value, type);
currentSuspensionDamper = value;// Mathf.Clamp(value, 500, 10000);
}
break;
case SuspensionComponent.Spring:
if (value != currentSuspensionSpring)
{
for (int x = 0; x < wheels.Count; x++)
wheels[x].SetSuspension(value, type);
currentSuspensionSpring = value;
}
break;
case SuspensionComponent.TargetPos:
if (value != currentSuspensionHeight)
{
for (int x = 0; x < wheels.Count; x++)
wheels[x].SetSuspension(1 - value, type);
currentSuspensionHeight = value;
}
break;
}
}
void ChangeDrive(bool front, bool on)
{
if (front)
{
if (on != IsFrontWheelDrive)
{
for (int x = 0; x < wheels.Count; x++)
{
if (!wheels[x].IsRearWheel)
wheels[x].IsPowerWheel = on;
}
IsFrontWheelDrive = on;
}
}
else
{
if (on != IsRearWheelDrive)
{
for (int x = 0; x < wheels.Count; x++)
{
if (wheels[x].IsRearWheel)
wheels[x].IsPowerWheel = on;
}
IsRearWheelDrive = on;
}
}
}
void ChangeSteer(bool front, bool on)
{
if (front)
{
if (on != IsFrontWheelSteer)
{
for (int x = 0; x < wheels.Count; x++)
{
if (!wheels[x].IsRearWheel)
wheels[x].IsTurnWheel = on;
}
IsFrontWheelSteer = on;
}
}
else
{
if (on != IsRearWheelSteer)
{
for (int x = 0; x < wheels.Count; x++)
{
if (wheels[x].IsRearWheel)
wheels[x].IsTurnWheel = on;
}
IsRearWheelSteer = on;
}
}
}
float MoveTo(float current, float target, float rate)
{
if (current < target)
current += rate;
if (current > target)
current -= rate;
return current;
}
// Update is called once per frame
void FixedUpdate () {
for (int x = 0; x < wheels.Count; x++)
{
wheels[x].SetMaxRpm(MaxRpm);
wheels[x].SetSteering(currentTurn);
wheels[x].SetTorque(currentSpeedKMs < MaxSpeed ? currentTorque : 0);
wheels[x].SetBrake(currentBrake);
//wheels[x].SetSuspension(
}
currentSpeed = _rigidbody.velocity.magnitude;
currentSpeedKMs = currentSpeed * 3.6f;
}
}
