§ How to · with Picoo

How to make a Roblox vehicle system (with AI)

By Sametcan Tasgiran, Founder & Developer·Published ·Updated

A Roblox vehicle needs three pieces: a VehicleSeat that reads throttle and steer, a body welded into one assembly, and a drive loop that moves it. Picoo builds all three as one car; suspension, collision damage, passenger seats and engine sound are follow-up prompts.

2 files · 125 lines · one recipe call. Driven on the seated player's client; the wheels are cosmetic.

VehicleSeat driving

Standard Roblox VehicleSeat. W/S set Throttle, A/D set Steer, and the VehicleDriver script turns those into movement.

Welded body

Chassis, VehicleSeat and four cosmetic wheels, all WeldConstraint-joined into one assembly in Workspace.Car. Sit on the seat to drive; jump to exit.

Kinematic movement

While someone is seated, the driver's client sets the car's velocity along its own look vector every frame. No constraint assembly to tune, so it drives on a fresh baseplate.

Two constants to tune

Top speed (default 55) and turn speed (default 2.4).

Fall reset

If the car drops below Y -50, the builder moves it back to its spawn point.

Files Picoo ships for this prompt

2 files · 125 lines · one recipe call

ServerScriptService/CarBuilder

Builds the chassis, VehicleSeat and four cosmetic wheels in Workspace.Car, welded into one assembly.

86 lines

StarterPlayer/StarterPlayerScripts/VehicleDriver

Reads the seat's Throttle and Steer on the driver's client and sets the car's velocity.

39 lines

Sample output: StarterPlayer/StarterPlayerScripts/VehicleDriver

--!strict
-- Generated by Picoo recipe: drivable_car (client driver)
-- Lives in StarterPlayerScripts. Whenever THIS player sits in a VehicleSeat,
-- read its Throttle/Steer and move the car kinematically. Works for any
-- VehicleSeat assembly, not just the one above.

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

local player = Players.LocalPlayer
local TOP_SPEED = 55
local TURN_SPEED = 2.4

RunService.RenderStepped:Connect(function()
	local char = player.Character
	local humanoid = char and char:FindFirstChildOfClass("Humanoid")
	if not humanoid then return end
	local seatPart = humanoid.SeatPart
	if not seatPart or not seatPart:IsA("VehicleSeat") then return end

	-- find the assembly root (PrimaryPart of the car model, or the seat)
	local model = seatPart:FindFirstAncestorOfClass("Model")
	local root: BasePart = (model and model.PrimaryPart) or seatPart
	if not root then return end

	local throttle = seatPart.Throttle  -- -1..1
	local steer = seatPart.Steer        -- -1..1

	-- preserve vertical velocity (gravity) so the car stays grounded/can fall
	local vy = root.AssemblyLinearVelocity.Y
	root.AssemblyLinearVelocity = root.CFrame.LookVector * (throttle * TOP_SPEED) + Vector3.new(0, vy, 0)
	-- only turn while moving, scaled by throttle direction for natural steering
	if math.abs(throttle) > 0.05 then
		root.AssemblyAngularVelocity = Vector3.new(0, -steer * TURN_SPEED * (throttle >= 0 and 1 or -1), 0)
	else
		root.AssemblyAngularVelocity = Vector3.new(0, 0, 0)
	end
end)

Building a Roblox vehicle system

Vehicle systems are where Roblox physics either shines or breaks. The usual failure is a constraint chassis with its drive wheels welded rigid: it looks right, you sit in it, and it does not move. Picoo avoids that by driving the car kinematically.

The prompt produces two scripts. CarBuilder, in ServerScriptService, builds one car in Workspace.Car — a chassis, a VehicleSeat and four cosmetic wheels, all welded into a single unanchored assembly. VehicleDriver, in StarterPlayerScripts, runs every frame on the driver's client: while the local player sits in a VehicleSeat, it reads the seat's Throttle and Steer and sets the car's velocity along its own look vector, keeping the vertical velocity so the car still falls.

Sitting takes control and jumping off exits, because VehicleSeat handles both natively; once nobody is seated, the drive loop stops setting velocity. If the car falls below Y -50, CarBuilder moves it back to its spawn point.

The drive loop lives on the client on purpose. A seated player's client owns the car's physics, so pushing it from the server as well would mean two simulations fighting. The trade-off is that the car's speed is set client-side, and the generated car has no server-side speed check.

Tuning is two constants: top speed (55 by default) and turn speed (2.4). Suspension constraints, collision damage, passenger seats and engine sound are follow-up prompts, and because the chassis is ordinary parts, you can add them without rebuilding the model.

See more on the Luau generator, the AI Luau script writer, the 3D model generator and the game builder, read how Picoo compares with ChatGPT, or browse the full blog.

Frequently asked

How do I add a new vehicle type?+

VehicleDriver drives any VehicleSeat the local player sits in, so another model with a VehicleSeat, welded into one unanchored assembly, drives the same way. Speed and turning come from the TOP_SPEED and TURN_SPEED constants at the top of VehicleDriver, so every vehicle shares them.

Can passengers be added?+

Yes. Add additional Seat (not VehicleSeat) instances to the body, welded to the same root. Passengers sit but cannot drive. You can ask for passenger seats in a follow-up prompt.

Does the server check the car's speed?+

No. The drive loop runs on the seated player's client, which owns the car's physics while seated, so the car's velocity is set client-side. Ask for a server-side speed check in a follow-up prompt if your game needs one.

What about flying or boat vehicles?+

The car recipe drives along the ground and keeps gravity. Flying or boat movement needs its own setup; ask Picoo for it in a follow-up prompt and it writes that code for your game.

Related Picoo prompts