In short
Weld a VehicleSeat to a chassis part. Join each wheel to the chassis with a HingeConstraint set to Motor, with its axis along the axle. For steering, put each front wheel on a small knuckle part joined to the chassis by a second hinge set to Servo, with an upright axis. A server script reads the seat's ThrottleFloat and SteerFloat and sets the motors' AngularVelocity (speed divided by wheel radius) and the servos' TargetAngle.
- Give the driver network ownership of the car with SetNetworkOwner, or it lags behind their keys.
- Spin the wheels with constraints, not by moving the car with CFrame: physics keeps it on the ground and lets it hit things.
- One car per player: destroy the old one before spawning the next, and when the player leaves.
Get the script
Car script with a spawner pad
One server Script builds a drivable car with motor and servo hinges, gives the driver network ownership and spawns one car per player from a pad.
-- Script in ServerScriptService named CarSpawner.
-- Builds a drivable car from parts and constraints when a player uses the
-- "Spawn car" prompt on Workspace.CarPad. One car per player.
local Players = game:GetService("Players")
local TOP_SPEED = 60 -- studs per second
local REVERSE_SPEED = 25
local DRIVE_TORQUE = 6000 -- how hard the wheels push
local BRAKE_TORQUE = 600 -- slows the car down when you let go
local STEER_ANGLE = 30 -- degrees the front wheels turn
local WHEEL_RADIUS = 1.4
local pad = workspace:WaitForChild("CarPad") :: BasePart
local cars: { [Player]: Model } = {}
local function newPart(name: string, size: Vector3, cframe: CFrame, parent: Instance): Part
local p = Instance.new("Part")
p.Name = name
p.Size = size
p.CFrame = cframe
p.TopSurface = Enum.SurfaceType.Smooth
p.BottomSurface = Enum.SurfaceType.Smooth
p.Parent = parent
return p
end
local function weld(a: BasePart, b: BasePart)
local w = Instance.new("WeldConstraint")
w.Part0 = a
w.Part1 = b
w.Parent = a
end
-- An attachment whose X axis (the hinge axis) points along worldCFrame's X axis
local function attach(part: BasePart, name: string, worldCFrame: CFrame): Attachment
local a = Instance.new("Attachment")
a.Name = name
a.CFrame = part.CFrame:ToObjectSpace(worldCFrame)
a.Parent = part
return a
end
type Wheel = { drive: HingeConstraint, steer: HingeConstraint? }
local function buildCar(at: CFrame): (Model, VehicleSeat, BasePart, { Wheel })
local car = Instance.new("Model")
car.Name = "Car"
local R, U, F = at.RightVector, at.UpVector, at.LookVector
-- The chassis: everything else hangs off this part
local base = newPart("Base", Vector3.new(6, 1, 10), at * CFrame.new(0, WHEEL_RADIUS + 0.5, 0), car)
base.Color = Color3.fromRGB(200, 40, 50)
car.PrimaryPart = base
local body = newPart("Body", Vector3.new(5.6, 2, 5), at * CFrame.new(0, WHEEL_RADIUS + 2, 1), car)
body.Color = Color3.fromRGB(230, 60, 70)
body.Massless = true
weld(base, body)
local seat = Instance.new("VehicleSeat")
seat.Name = "DriveSeat"
seat.Size = Vector3.new(2, 1, 2)
seat.CFrame = at * CFrame.new(0, WHEEL_RADIUS + 1.5, -2)
seat.HeadsUpDisplay = false
seat.Massless = true
seat.Parent = car
weld(base, seat)
local wheels: { Wheel } = {}
for _, corner in { Vector3.new(-3.6, 0, -3.5), Vector3.new(3.6, 0, -3.5), Vector3.new(-3.6, 0, 3.5), Vector3.new(3.6, 0, 3.5) } do
local front = corner.Z < 0 -- the car faces -Z, the pad's look direction
local pos = at:PointToWorldSpace(Vector3.new(corner.X, WHEEL_RADIUS, corner.Z))
local wheel = newPart("Wheel", Vector3.new(1, WHEEL_RADIUS * 2, WHEEL_RADIUS * 2), CFrame.fromMatrix(pos, R, U), car)
wheel.Shape = Enum.PartType.Cylinder -- a cylinder's round faces are on its X axis
wheel.Color = Color3.fromRGB(30, 30, 34)
wheel.CustomPhysicalProperties = PhysicalProperties.new(0.7, 1.5, 0, 100, 1)
local hub: BasePart = base
local steer: HingeConstraint? = nil
if front then
-- Front wheels turn on a small knuckle part, steered by a servo hinge with an up axis
local knuckle = newPart("Knuckle", Vector3.new(0.5, 0.5, 0.5), CFrame.fromMatrix(pos, R, U), car)
knuckle.CanCollide = false
knuckle.Transparency = 1
knuckle.Massless = true
local axisUp = CFrame.fromMatrix(pos, U, F)
local servo = Instance.new("HingeConstraint")
servo.Attachment0 = attach(base, "SteerBase", axisUp)
servo.Attachment1 = attach(knuckle, "SteerKnuckle", axisUp)
servo.ActuatorType = Enum.ActuatorType.Servo
servo.ServoMaxTorque = 20000
servo.AngularSpeed = 5
servo.LimitsEnabled = true
servo.LowerAngle = -STEER_ANGLE
servo.UpperAngle = STEER_ANGLE
servo.Parent = knuckle
hub = knuckle
steer = servo
end
-- Every wheel spins on a motor hinge whose axis points to the car's left:
-- a positive AngularVelocity about that axis rolls the wheel forward
local axisLeft = CFrame.fromMatrix(pos, -R, U)
local drive = Instance.new("HingeConstraint")
drive.Attachment0 = attach(hub, "DriveHub", axisLeft)
drive.Attachment1 = attach(wheel, "DriveWheel", axisLeft)
drive.ActuatorType = Enum.ActuatorType.Motor
drive.MotorMaxTorque = BRAKE_TORQUE
drive.AngularVelocity = 0
drive.Parent = wheel
table.insert(wheels, { drive = drive, steer = steer })
end
return car, seat, base, wheels
end
-- Reads the driver's keys (W/S or the thumbstick) from the seat and turns them into wheel motion
local function wireControls(seat: VehicleSeat, base: BasePart, wheels: { Wheel })
local function update()
local throttle = seat.ThrottleFloat
local speed = if throttle > 0 then TOP_SPEED * throttle else REVERSE_SPEED * throttle
for _, w in wheels do
w.drive.AngularVelocity = speed / WHEEL_RADIUS
w.drive.MotorMaxTorque = if throttle == 0 then BRAKE_TORQUE else DRIVE_TORQUE
if w.steer then
-- SteerFloat is 1 for right (D); a positive servo angle about the up axis turns left
w.steer.TargetAngle = -seat.SteerFloat * STEER_ANGLE
end
end
end
seat:GetPropertyChangedSignal("ThrottleFloat"):Connect(update)
seat:GetPropertyChangedSignal("SteerFloat"):Connect(update)
-- The driver's computer simulates the car, so it reacts without lag
seat:GetPropertyChangedSignal("Occupant"):Connect(function()
local humanoid = seat.Occupant
local character = humanoid and humanoid.Parent
local driver = if character and character:IsA("Model") then Players:GetPlayerFromCharacter(character) else nil
if driver then
base:SetNetworkOwner(driver)
elseif base:IsDescendantOf(workspace) then
base:SetNetworkOwnershipAuto()
update()
end
end)
end
local prompt = Instance.new("ProximityPrompt")
prompt.ActionText = "Spawn car"
prompt.ObjectText = "Car"
prompt.RequiresLineOfSight = false
prompt.Parent = pad
prompt.Triggered:Connect(function(player: Player)
local old = cars[player]
if old then
old:Destroy()
end
-- the car appears in front of the pad, facing the same way as the pad
local spawnAt = pad.CFrame * CFrame.new(0, pad.Size.Y / 2, -12)
local car, seat, base, wheels = buildCar(spawnAt)
car.Parent = workspace
cars[player] = car
wireControls(seat, base, wheels)
local character = player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if humanoid then
seat:Sit(humanoid)
end
end)
Players.PlayerRemoving:Connect(function(player: Player)
local car = cars[player]
if car then
car:Destroy()
cars[player] = nil
end
end)
RoCode builds it in the place you have open in Roblox Studio, on Windows or macOS with a desktop browser; on a phone the button sends the prompt and the link to your computer. Free to start, with free credits every day. More Roblox Studio scripts
Where to put it
Workspace
CarPad Part new
ServerScriptService
CarSpawner Script new
Settings you can change
TOP_SPEEDIn the script- Top speed in studs per second. A character walks at 16. Default:
60. STEER_ANGLEIn the script- How far the front wheels turn, in degrees. Lower is steadier at speed. Default:
30. DRIVE_TORQUEIn the script- How hard the wheels push. Raise it for steep hills or a heavier body. Default:
6000.
The pad needs empty, flat ground about 12 studs in front of it, the way its front face points: the car appears there, facing the same way.
Type-checked with luau-lsp strict against Roblox's API definitions on 25 October 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.
Prompt for RoCode
Add a drivable car to my game: a pad in Workspace with a Spawn car prompt, one car per player, built from a VehicleSeat, motor HingeConstraints on four wheels and servo steering on the front wheels, with the driver given network ownership so it drives smoothly.
On this page
How a constraint car works
Every part of the car is unanchored and simulated by physics. Only the constraints hold it together.
| Part | Joined by | Job |
|---|---|---|
| Base | Nothing: everything joins to it | The chassis. It carries the mass and is the part the driver owns |
| Body, DriveSeat | WeldConstraint to Base | The look, and the seat that reads W, A, S, D or a thumbstick |
| Rear wheels | HingeConstraint (Motor) to Base | Spin at the speed the script asks for |
| Knuckles | HingeConstraint (Servo) to Base | Turn left and right with an upright axis |
| Front wheels | HingeConstraint (Motor) to a knuckle | Spin like the rear wheels and turn with their knuckle |
A HingeConstraint turns around the X axis of its attachments. The script builds each attachment with CFrame.fromMatrix so its X axis points where it should: to the car's left for the wheel motors, and straight up for the steering servos.
Step 1: Make the spawn pad
- Insert a Part in Workspace, name it
CarPad, make it about 8 by 1 by 8 studs and set Anchored on. - Turn it to face an open road: the car spawns 12 studs in front of it. If the car appears behind the pad instead, turn the pad around 180 degrees.
Step 2: Add the car script
- Insert a Script in ServerScriptService named
CarSpawnerand paste the code below. - Press Play, walk to the pad and hold E on Spawn car. You are seated in a new car: W and S drive, A and D steer, Space gets out.
-- Script in ServerScriptService named CarSpawner.
-- Builds a drivable car from parts and constraints when a player uses the
-- "Spawn car" prompt on Workspace.CarPad. One car per player.
local Players = game:GetService("Players")
local TOP_SPEED = 60 -- studs per second
local REVERSE_SPEED = 25
local DRIVE_TORQUE = 6000 -- how hard the wheels push
local BRAKE_TORQUE = 600 -- slows the car down when you let go
local STEER_ANGLE = 30 -- degrees the front wheels turn
local WHEEL_RADIUS = 1.4
local pad = workspace:WaitForChild("CarPad") :: BasePart
local cars: { [Player]: Model } = {}
local function newPart(name: string, size: Vector3, cframe: CFrame, parent: Instance): Part
local p = Instance.new("Part")
p.Name = name
p.Size = size
p.CFrame = cframe
p.TopSurface = Enum.SurfaceType.Smooth
p.BottomSurface = Enum.SurfaceType.Smooth
p.Parent = parent
return p
end
local function weld(a: BasePart, b: BasePart)
local w = Instance.new("WeldConstraint")
w.Part0 = a
w.Part1 = b
w.Parent = a
end
-- An attachment whose X axis (the hinge axis) points along worldCFrame's X axis
local function attach(part: BasePart, name: string, worldCFrame: CFrame): Attachment
local a = Instance.new("Attachment")
a.Name = name
a.CFrame = part.CFrame:ToObjectSpace(worldCFrame)
a.Parent = part
return a
end
type Wheel = { drive: HingeConstraint, steer: HingeConstraint? }
local function buildCar(at: CFrame): (Model, VehicleSeat, BasePart, { Wheel })
local car = Instance.new("Model")
car.Name = "Car"
local R, U, F = at.RightVector, at.UpVector, at.LookVector
-- The chassis: everything else hangs off this part
local base = newPart("Base", Vector3.new(6, 1, 10), at * CFrame.new(0, WHEEL_RADIUS + 0.5, 0), car)
base.Color = Color3.fromRGB(200, 40, 50)
car.PrimaryPart = base
local body = newPart("Body", Vector3.new(5.6, 2, 5), at * CFrame.new(0, WHEEL_RADIUS + 2, 1), car)
body.Color = Color3.fromRGB(230, 60, 70)
body.Massless = true
weld(base, body)
local seat = Instance.new("VehicleSeat")
seat.Name = "DriveSeat"
seat.Size = Vector3.new(2, 1, 2)
seat.CFrame = at * CFrame.new(0, WHEEL_RADIUS + 1.5, -2)
seat.HeadsUpDisplay = false
seat.Massless = true
seat.Parent = car
weld(base, seat)
local wheels: { Wheel } = {}
for _, corner in { Vector3.new(-3.6, 0, -3.5), Vector3.new(3.6, 0, -3.5), Vector3.new(-3.6, 0, 3.5), Vector3.new(3.6, 0, 3.5) } do
local front = corner.Z < 0 -- the car faces -Z, the pad's look direction
local pos = at:PointToWorldSpace(Vector3.new(corner.X, WHEEL_RADIUS, corner.Z))
local wheel = newPart("Wheel", Vector3.new(1, WHEEL_RADIUS * 2, WHEEL_RADIUS * 2), CFrame.fromMatrix(pos, R, U), car)
wheel.Shape = Enum.PartType.Cylinder -- a cylinder's round faces are on its X axis
wheel.Color = Color3.fromRGB(30, 30, 34)
wheel.CustomPhysicalProperties = PhysicalProperties.new(0.7, 1.5, 0, 100, 1)
local hub: BasePart = base
local steer: HingeConstraint? = nil
if front then
-- Front wheels turn on a small knuckle part, steered by a servo hinge with an up axis
local knuckle = newPart("Knuckle", Vector3.new(0.5, 0.5, 0.5), CFrame.fromMatrix(pos, R, U), car)
knuckle.CanCollide = false
knuckle.Transparency = 1
knuckle.Massless = true
local axisUp = CFrame.fromMatrix(pos, U, F)
local servo = Instance.new("HingeConstraint")
servo.Attachment0 = attach(base, "SteerBase", axisUp)
servo.Attachment1 = attach(knuckle, "SteerKnuckle", axisUp)
servo.ActuatorType = Enum.ActuatorType.Servo
servo.ServoMaxTorque = 20000
servo.AngularSpeed = 5
servo.LimitsEnabled = true
servo.LowerAngle = -STEER_ANGLE
servo.UpperAngle = STEER_ANGLE
servo.Parent = knuckle
hub = knuckle
steer = servo
end
-- Every wheel spins on a motor hinge whose axis points to the car's left:
-- a positive AngularVelocity about that axis rolls the wheel forward
local axisLeft = CFrame.fromMatrix(pos, -R, U)
local drive = Instance.new("HingeConstraint")
drive.Attachment0 = attach(hub, "DriveHub", axisLeft)
drive.Attachment1 = attach(wheel, "DriveWheel", axisLeft)
drive.ActuatorType = Enum.ActuatorType.Motor
drive.MotorMaxTorque = BRAKE_TORQUE
drive.AngularVelocity = 0
drive.Parent = wheel
table.insert(wheels, { drive = drive, steer = steer })
end
return car, seat, base, wheels
end
-- Reads the driver's keys (W/S or the thumbstick) from the seat and turns them into wheel motion
local function wireControls(seat: VehicleSeat, base: BasePart, wheels: { Wheel })
local function update()
local throttle = seat.ThrottleFloat
local speed = if throttle > 0 then TOP_SPEED * throttle else REVERSE_SPEED * throttle
for _, w in wheels do
w.drive.AngularVelocity = speed / WHEEL_RADIUS
w.drive.MotorMaxTorque = if throttle == 0 then BRAKE_TORQUE else DRIVE_TORQUE
if w.steer then
-- SteerFloat is 1 for right (D); a positive servo angle about the up axis turns left
w.steer.TargetAngle = -seat.SteerFloat * STEER_ANGLE
end
end
end
seat:GetPropertyChangedSignal("ThrottleFloat"):Connect(update)
seat:GetPropertyChangedSignal("SteerFloat"):Connect(update)
-- The driver's computer simulates the car, so it reacts without lag
seat:GetPropertyChangedSignal("Occupant"):Connect(function()
local humanoid = seat.Occupant
local character = humanoid and humanoid.Parent
local driver = if character and character:IsA("Model") then Players:GetPlayerFromCharacter(character) else nil
if driver then
base:SetNetworkOwner(driver)
elseif base:IsDescendantOf(workspace) then
base:SetNetworkOwnershipAuto()
update()
end
end)
end
local prompt = Instance.new("ProximityPrompt")
prompt.ActionText = "Spawn car"
prompt.ObjectText = "Car"
prompt.RequiresLineOfSight = false
prompt.Parent = pad
prompt.Triggered:Connect(function(player: Player)
local old = cars[player]
if old then
old:Destroy()
end
-- the car appears in front of the pad, facing the same way as the pad
local spawnAt = pad.CFrame * CFrame.new(0, pad.Size.Y / 2, -12)
local car, seat, base, wheels = buildCar(spawnAt)
car.Parent = workspace
cars[player] = car
wireControls(seat, base, wheels)
local character = player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if humanoid then
seat:Sit(humanoid)
end
end)
Players.PlayerRemoving:Connect(function(player: Player)
local car = cars[player]
if car then
car:Destroy()
cars[player] = nil
end
end)
Step 3: How driving works
A VehicleSeat sets ThrottleFloat from -1 to 1 for W and S and SteerFloat from -1 to 1 for A and D (and the thumbstick on a phone or controller). wireControls listens for both and turns them into motion:
- Speed: a wheel rolling at
vstuds per second spins atv / radiusradians per second, so each motor getsspeed / WHEEL_RADIUS. Reverse is capped atREVERSE_SPEED. - Braking: with no throttle the motors aim for 0 spin with
BRAKE_TORQUE, a gentle brake, so the car rolls to a stop instead of drifting forever. - Steering: each front servo gets
TargetAngle = -SteerFloat * STEER_ANGLE. Positive angles about an upright axis turn left, and D makes SteerFloat positive, hence the minus.
When someone sits, the seat's Occupant changes and the script calls base:SetNetworkOwner(driver). Physics for the whole car then runs on the driver's device, so it answers their keys at once. When they get out, ownership goes back to automatic.
Step 4: Tune it and use your own body
| If the car... | Change |
|---|---|
| Is too slow up hills | Raise DRIVE_TORQUE |
| Flips in corners | Lower STEER_ANGLE or TOP_SPEED, or widen the wheel corners in buildCar |
| Slides in corners | Raise the wheels' friction, the second number in CustomPhysicalProperties |
| Stops too sharply | Lower BRAKE_TORQUE |
To use your own car model, keep the chassis, wheels and constraints from buildCar and replace the Body part. Weld every part of your body to Base with a WeldConstraint, set them unanchored and Massless, and turn off CanCollide on parts that hang low, so they do not scrape the ground.
Common errors and fixes
If the car does not move the way it should, check these first.
The car drives backwards or steers the wrong way
An attachment axis points the other way in your build. Put a minus in front of speed / WHEEL_RADIUS for driving, or remove the minus from the TargetAngle line for steering.
Nothing happens when I press W
Check you are in the DriveSeat (a VehicleSeat, not a Seat) and that no part of the car is Anchored. An anchored part freezes the whole car.
The car jitters or lags behind my keys
The driver needs network ownership. Check the Occupant handler runs, and that the car has no anchored parts, which make SetNetworkOwner fail.
Infinite yield possible on Workspace:WaitForChild("CarPad")
The pad is missing or named differently. It must be a part named CarPad directly in Workspace.
The car spawns inside a wall
It appears 12 studs in front of the pad's front face. Turn the pad toward open ground, or change the -12 in the Triggered handler.
Build it with RoCode
If you would rather describe the car than build the chassis by hand, RoCode can build it in the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the parts, constraints and scripts in your Explorer.
Add a car spawner next to my spawn: a red sports car with a VehicleSeat, motor hinges on the wheels and servo steering, top speed 70, one car per player.
- Builds the car and the spawner script, or makes a car model you already have drivable
- Can read your place first to put the pad where there is open road
- Is told to compile-check each script with Check Script, and sets undo points around each batch of changes
You still drive it yourself in a playtest to check the speed and steering feel right. How RoCode connects to Studio.
Questions
How do you make a car drive in Roblox Studio?
Join the wheels to the chassis with HingeConstraints set to Motor, weld a VehicleSeat to the chassis, and in a server script set each motor's AngularVelocity from the seat's ThrottleFloat. Steps 1 and 2 have the whole car.
Why does my Toolbox car not drive?
Free models often lose their scripts, or use old BodyVelocity and BodyGyro movers that behave badly now. Rebuild the chassis with constraints and weld the model's body onto it, as Step 4 shows.
What is a VehicleSeat in Roblox?
A seat that reads the driver's keys. It sets Throttle and Steer (whole numbers) and ThrottleFloat and SteerFloat (smooth values) that your script turns into motion.
How do I make a car spawner in Roblox?
Put a ProximityPrompt on a pad. When it fires, destroy the player's old car, build or clone a new one in front of the pad and seat them with Seat:Sit, as the CarSpawner script does.
Sources and further reading
- Roblox Creator Docs: VehicleSeat. ThrottleFloat, SteerFloat and Occupant
- Roblox Creator Docs: HingeConstraint. Motor and Servo actuators
- Roblox Creator Docs: Network ownership. SetNetworkOwner and why it matters for vehicles
- Roblox Creator Docs: Mechanical constraints. hinges, attachments and axes
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 25 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.