HOW TO MOVE A CAR IN UNITY USING C#
Build a solid, responsive arcade car controller in Unity using Rigidbody physics, velocity steering, and acceleration torque.
Quick Answer
To move a car smoothly with realistic physics in Unity, apply rb.AddRelativeForce() or modify rb.linearVelocity inside FixedUpdate() based on vertical input (W/S), and rotate the car transform around its Y-axis using Quaternion.Euler() or rb.MoveRotation() scaled by horizontal input (A/D) and forward velocity.
Requirements & Setup
Configure the Car GameObject & Physics Components
Create a 3D car model or a simple 3D Cube (scaled X: 2, Y: 0.8, Z: 4). Attach a Rigidbody component and a BoxCollider to the root object.
- Mass: Set to
1200-1500 kg(realistic passenger vehicle weight). - Drag (Linear Damping): Set to
0.5to provide natural rolling air resistance. - Angular Drag: Set to
2.0to prevent endless spinning when turning. - Interpolate: Set to
Interpolate(crucial for smooth camera tracking without jitter). - Collision Detection: Set to
Continuousto prevent clipping through guardrails.
Create the Arcade Car Controller C# Script
Create a new C# script named ArcadeCarController.cs and attach it to your car GameObject:
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class ArcadeCarController : MonoBehaviour
{
[Header("Engine & Acceleration")]
[SerializeField] private float accelerationSpeed = 25f;
[SerializeField] private float maxForwardSpeed = 45f;
[SerializeField] private float reverseSpeed = 15f;
[SerializeField] private float brakeForce = 35f;
[Header("Steering Dynamics")]
[SerializeField] private float turnSpeed = 90f;
[SerializeField] private float traction = 0.9f;
private Rigidbody rb;
private float moveInput;
private float steerInput;
public float CurrentSpeedKmh => rb.linearVelocity.magnitude * 3.6f;
private void Awake()
{
rb = GetComponent<Rigidbody>();
// Lower Center of Mass to prevent flipping over during sharp corners
rb.centerOfMass = new Vector3(0f, -0.4f, 0f);
}
private void Update()
{
// Read Player Input (WASD or Arrow Keys)
moveInput = Input.GetAxisRaw("Vertical");
steerInput = Input.GetAxisRaw("Horizontal");
}
private void FixedUpdate()
{
HandleAcceleration();
HandleSteering();
ApplyLateralFriction();
}
private void HandleAcceleration()
{
if (Mathf.Abs(moveInput) > 0.05f)
{
float targetSpeed = moveInput > 0 ? maxForwardSpeed : reverseSpeed;
Vector3 forwardForce = transform.forward * (moveInput * accelerationSpeed);
if (rb.linearVelocity.magnitude < targetSpeed)
{
rb.AddForce(forwardForce, ForceMode.Acceleration);
}
}
else
{
// Deceleration / Natural Braking
rb.linearVelocity = Vector3.Lerp(rb.linearVelocity, Vector3.zero, Time.fixedDeltaTime * 0.8f);
}
}
private void HandleSteering()
{
// Steer only when moving to simulate tire grip
float speedRatio = Mathf.Clamp01(rb.linearVelocity.magnitude / 3f);
float direction = Vector3.Dot(transform.forward, rb.linearVelocity) >= 0 ? 1f : -1f;
float turnAmount = steerInput * turnSpeed * speedRatio * direction * Time.fixedDeltaTime;
Quaternion turnRotation = Quaternion.Euler(0f, turnAmount, 0f);
rb.MoveRotation(rb.rotation * turnRotation);
}
private void ApplyLateralFriction()
{
// Cancel sideways drift based on traction ratio
Vector3 forwardVelocity = transform.forward * Vector3.Dot(rb.linearVelocity, transform.forward);
Vector3 lateralVelocity = transform.right * Vector3.Dot(rb.linearVelocity, transform.right);
rb.linearVelocity = forwardVelocity + (lateralVelocity * (1f - traction));
}
}
Understanding the Code Architecture
- Center of Mass (
centerOfMass = new Vector3(0, -0.4f, 0)): Lowering the physics pivot ensures the car stays glued to the road rather than flipping over whenever turning at high velocity. - Input in Update vs Physics in FixedUpdate: We gather raw input inside
Update()for responsiveness and executerb.AddForce()insideFixedUpdate()to maintain frame-rate independent physics simulation. - Lateral Drift Cancellation: We separate the velocity into forward and lateral vectors. Multiplying the lateral component by
(1 - traction)eliminates unwanted ice-skating drift while allowing subtle slide.
Common Problems & How to Fix Them
Check if your wheel colliders or chassis collider are intersecting the ground mesh at spawn. Ensure your road surface has a standard Physics Material with Dynamic Friction =
0.6.
Set
Rigidbody.Interpolation = Interpolate on the Car, and update your Camera position inside LateUpdate(). For more troubleshooting, see our Transform Jitter Fix.
Related Developer Tools & Tutorials
Convert km/h, m/s, acceleration curves and braking distances.
Control engine audio pitch dynamically with car speed.
Frequently Asked Questions
Transform.Translate teleports the mesh through walls, ignores mass, breaks collision impulses, and creates micro-jitter on mobile devices.
traction variable from 0.9 down to 0.25 and increase angular turning speed slightly to let the rear wheels slide smoothly.