Tutorial·2026-10-22·8 min

Throttle Script Roblox: Make a VehicleSeat Drive Your Car

Roblox throttle script that makes a VehicleSeat drive your car: read ThrottleFloat, spin hinge motors, fix mobile buttons and cars that won't move.

S

Sametcan Tasgiran

Founder & Developer · Picoo

A throttle script in Roblox reads a VehicleSeat's ThrottleFloat (the W/S input, from -1 to 1) and turns it into movement, usually by setting the AngularVelocity of Motor-mode HingeConstraints on the drive wheels every frame. The seat's built-in driving only works with legacy motor joints connected to it, so a car built with HingeConstraints needs this script to move.

Looking for codes for the Roblox bike game Throttle? This guide is about scripting.

What the VehicleSeat gives you

When a player sits in a VehicleSeat, it welds them in place and fills in these properties:

  • ThrottleFloat: forward and backward input. 1 is forward, -1 is reverse. It goes back to 0 unless something keeps setting it.
  • SteerFloat: left and right input. 1 is right, -1 is left. It also goes back to 0 unless constantly set.
  • Occupant: the Humanoid sitting in the seat.
  • **MaxSpeed, Torque, TurnSpeed**: the top speed, how fast the vehicle reaches it, and the turning speed. These only affect the seat's built-in driving of legacy motor joints. With the script below, speed comes from MAX_WHEEL_SPIN and the hinges' MotorMaxTorque.

Throttle vs ThrottleFloat

Older tutorials and free models use seat.Throttle and seat.Steer. Those are whole numbers (1, 0 or -1), and the current Creator Hub reference labels both as deprecated. ThrottleFloat and SteerFloat are the non-deprecated versions, so use them when you write or update a script.

Step by step: a hinge motor car

  • Step 1: Put a VehicleSeat in your car model and weld the chassis parts together so the seat is part of the vehicle.
  • **Step 2:** Attach each rear wheel to the chassis with a HingeConstraint and set its ActuatorType to Motor. These are your drive wheels.
  • Step 3: Front wheels need two hinges each, because a wheel held by a Servo cannot roll. Add a small steering part for each front wheel. Connect the steering part to the chassis with a Servo HingeConstraint whose axis points up. Then connect the wheel to the steering part with a HingeConstraint set to None, so the wheel spins freely.
  • **Step 4:** Set the hinge values. MotorMaxTorque starts at 0, so the car will not move until you raise it. Start around 20000 and adjust until it works for your car. The Servo hinges also need ServoMaxTorque (for example 10000) and AngularSpeed (for example 5), or the front wheels will not turn.
  • Step 5: Put the hinge objects directly inside the car model with the names below (or change the names in the code), then add a Script inside the car model.

The front left corner ends up like this:

  • Chassis

- FrontLeftSteer (Servo, axis up) connects to the steering part - Axle hinge (None) connects the steering part to the front left wheel

local RunService = game:GetService("RunService")

local car = script.Parent
local seat = car:WaitForChild("VehicleSeat")

local MAX_WHEEL_SPIN = 30 -- radians/sec; car speed is about this times the wheel radius
local MAX_STEER_ANGLE = 30 -- degrees

local motors = {
	{hinge = car:WaitForChild("RearLeftMotor"), dir = 1},
	{hinge = car:WaitForChild("RearRightMotor"), dir = -1},
}

local steering = {
	car:WaitForChild("FrontLeftSteer"),
	car:WaitForChild("FrontRightSteer"),
}

RunService.Heartbeat:Connect(function()
	local throttle = seat.ThrottleFloat
	local steer = seat.SteerFloat

	for _, motor in motors do
		motor.hinge.AngularVelocity = throttle * MAX_WHEEL_SPIN * motor.dir
	end

	for _, hinge in steering do
		hinge.TargetAngle = steer * MAX_STEER_ANGLE
	end
end)

Here is what each part does:

  • A Motor hinge tries to reach its AngularVelocity. MotorMaxTorque and MotorMaxAcceleration cap how hard it can push.
  • A Servo hinge rotates to its TargetAngle, so steer * MAX_STEER_ANGLE turns the front wheels left or right.
  • Left and right wheels usually have mirrored attachments, so one side spins backwards. That is what dir is for. If a wheel still spins the wrong way, flip its dir between 1 and -1. If steering is reversed, use -steer instead of steer.

If you would rather react only when input changes, seat:GetPropertyChangedSignal("ThrottleFloat") works too. Heartbeat is simpler when you want to smooth the throttle over time.

Debugging the setup

  • Add print(seat.ThrottleFloat) inside the loop to confirm the seat is getting your W/S input.
  • While driving in a playtest, select a motor hinge and watch its AngularVelocity in the Properties window.
  • Turn on the seat's HeadsUpDisplay to show a speed bar on screen while driving.

Common throttle problems

The seat does not move anything

This is the most common question on the DevForum. The seat's built-in driving only detects legacy motor joints connected directly to it or through a rigid connection. With HingeConstraints, sitting in the seat sets ThrottleFloat and SteerFloat and nothing else. You need a script that reads them and applies them to the motors. If you have the script and the car still sits there, check that MotorMaxTorque is not still 0.

Mobile button throttle resets to 0

Roblox's default mobile controls already set the seat's throttle, so custom pedals are optional. If you want your own gas button, do not write the seat's throttle from it, because it resets to 0 within milliseconds. Have the button send its own value to the server with a RemoteEvent.

Create a RemoteEvent named PedalThrottle in ReplicatedStorage. Put this LocalScript inside a ScreenGui that has a TextButton named GasButton:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local pedalEvent = ReplicatedStorage:WaitForChild("PedalThrottle")
local gasButton = script.Parent:WaitForChild("GasButton")

gasButton.MouseButton1Down:Connect(function()
	pedalEvent:FireServer(1)
end)

gasButton.MouseButton1Up:Connect(function()
	pedalEvent:FireServer(0)
end)

Then add this to the car Script, above the Heartbeat connection:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local pedalEvent = ReplicatedStorage:WaitForChild("PedalThrottle")
local pedalThrottle = 0

pedalEvent.OnServerEvent:Connect(function(player, value)
	if typeof(value) ~= "number" then
		return
	end
	local occupant = seat.Occupant
	if not occupant or occupant.Parent ~= player.Character then
		return
	end
	pedalThrottle = math.clamp(value, -1, 1)
end)

seat:GetPropertyChangedSignal("Occupant"):Connect(function()
	pedalThrottle = 0
end)

Inside the Heartbeat function, change the throttle line so the pedal wins when it is pressed:

local throttle = if pedalThrottle ~= 0 then pedalThrottle else seat.ThrottleFloat

The car spins out of control

Very high or infinite MotorMaxTorque and MotorMaxAcceleration values can make a car spin out of control when it hits something. Set finite numbers in the HingeConstraint properties and lower them until the car stays stable.

A notch throttle for trains

Trains often use a lever that stays where you put it. Instead of reading the seat, keep your own throttle position, step it with W and S, clamp it, and send it to the server when it changes. Create a RemoteEvent named TrainThrottle in ReplicatedStorage, then put this LocalScript in StarterPlayerScripts:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")

local throttleEvent = ReplicatedStorage:WaitForChild("TrainThrottle")

local MIN_THROTTLE = -10
local MAX_THROTTLE = 10
local throttlePosition = 0

UserInputService.InputBegan:Connect(function(input, gameProcessed)
	if gameProcessed then
		return
	end

	local step = 0
	if input.KeyCode == Enum.KeyCode.W then
		step = 1
	elseif input.KeyCode == Enum.KeyCode.S then
		step = -1
	end
	if step == 0 then
		return
	end

	throttlePosition = math.clamp(throttlePosition + step, MIN_THROTTLE, MAX_THROTTLE)
	throttleEvent:FireServer(throttlePosition)
end)

On the server, a Script applies the notch to a Motor hinge on the train. This one expects a model named Train in Workspace with a seat named DriverSeat and a Motor HingeConstraint named DriveMotor:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local throttleEvent = ReplicatedStorage:WaitForChild("TrainThrottle")
local train = workspace:WaitForChild("Train")
local seat = train:WaitForChild("DriverSeat")
local motor = train:WaitForChild("DriveMotor")

local MAX_THROTTLE = 10
local MAX_WHEEL_SPIN = 20 -- radians/sec, tune for your train

throttleEvent.OnServerEvent:Connect(function(player, position)
	if typeof(position) ~= "number" then
		return
	end
	local occupant = seat.Occupant
	if not occupant or occupant.Parent ~= player.Character then
		return
	end
	position = math.clamp(math.round(position), -MAX_THROTTLE, MAX_THROTTLE)
	motor.AngularVelocity = position / MAX_THROTTLE * MAX_WHEEL_SPIN
end)

Because the lever stays put, setting the motor once per change is enough. The DevForum notch throttle this is based on used the legacy BodyVelocity on the client only. If your train slides instead of rolling on wheels, use a LinearVelocity constraint, the modern replacement for BodyVelocity, and set its velocity in the same handler.

Throttling remotes (rate limiting)

Some people searching "throttle script" mean limiting how often something runs. Quenty's @quenty/throttle package limits how often a function runs. For RemoteEvents, a DevForum rate limiter module counts each player's requests per remote and ignores the ones over a set maximum. A simpler version is a per-player cooldown in a server Script:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remote = ReplicatedStorage:WaitForChild("MyRemote")
local COOLDOWN = 0.2 -- seconds between accepted requests
local lastRequest = {}

remote.OnServerEvent:Connect(function(player, ...)
	local now = os.clock()
	if now - (lastRequest[player] or 0) < COOLDOWN then
		return
	end
	lastRequest[player] = now
	-- handle the request here
end)

Players.PlayerRemoving:Connect(function(player)
	lastRequest[player] = nil
end)

Do not put a cooldown like this on the pedal remote above, or a quick release can get dropped and leave the gas stuck on. For more server-side guards on RemoteEvents, see Luau patterns that hold up in a live game.

Building a car with Picoo

Hinges give you real wheel physics. A kinematic car is simpler. Picoo's vehicle recipe takes the kinematic route: it builds a car in Workspace.Car with a chassis, a VehicleSeat and four cosmetic wheels, and a LocalScript called VehicleDriver sets AssemblyLinearVelocity = LookVector * (throttle * TOP_SPEED) every frame while you are seated (TOP_SPEED defaults to 55). The generated script currently reads the seat's deprecated Throttle and Steer properties, so swap them for ThrottleFloat and SteerFloat. Press Play in Studio and sit in the VehicleSeat to drive. The generated car has no server-side speed check, so ask for one in a follow-up prompt. Picoo is premium-only: $19/mo, or $12/mo billed yearly, for 200 credits a month. See picoo.io and the drivable car guide.

Next: once your car drives, add a checkpoint system for racing.

FAQ

Why doesn't my VehicleSeat move the car?

With HingeConstraints, the seat only records W/S and A/D input as ThrottleFloat and SteerFloat. You need a script that reads those values and applies them to the AngularVelocity of Motor hinges on the wheels. Also check that MotorMaxTorque is above 0.

Is VehicleSeat.Throttle deprecated?

Yes. The Creator Hub reference labels Throttle and Steer as deprecated. Use ThrottleFloat and SteerFloat, which go from -1 to 1.

Why does my mobile throttle button reset to 0?

The seat's throttle resets to 0 unless it is set constantly. Have your button send its own value to the server with a RemoteEvent, and let the car script use that value instead of the seat's.

Why does my car spin out of control?

Your MotorMaxTorque or MotorMaxAcceleration is probably very high or infinite. Set finite values and lower them until the car stays stable.

Want to try Picoo?

Premium tool for serious Roblox developers. Pro from $12/mo billed yearly — cancel anytime. Plugin installs in two minutes.

Get Pro →