Builtin Library
README.md
VehicleController
Drives a car, truck, kart or buggy built from WheelColliders. Put it on the
chassis, the dynamic rigid body the wheels sit under, and a driving intent
(throttle, steer, brake, handbrake, boost) reaches the wheels. The intent comes
from the source controlSource names:
input(the default): the player's controls, on keyboard, gamepad and touch alike, from@builtin::inputMaps.vehiclethroughZin.controllers.vehicle.script: the intent fieldsthrottle,steer,brake,handbrakeandboosting, which code sets: an AI driver, a replay, an autopilot, a test.setIntentsets them and switches the source (see "Driving it from code").
Its flight and its rollovers are physics alone: it flies ballistically off a
ramp and stays on its roof after a rollover until the recover control or
recover() rights it. Air control and self-righting are arcade assists a
field turns on (see "Arcade assists").
It drives the wheels from the physics step (its fixedUpdate): each step it
reads what the step left and writes the drive, the brakes and the steering for
the next one. So the car answers its intent at the step rate whatever the
frame rate, the same on every platform, and under Physics.advance from a
held clock as in real time: an acceleration run or a braking distance is
measured faster than real time with the intent set before the advance (or
changed from a step hook).
| Control | Keyboard | Gamepad | Touch |
|---|---|---|---|
| throttle | W / S | right / left trigger | stick, vertical |
| steer | D / A | left stick | stick, horizontal |
| brake | Space | south | button |
| handbrake | Ctrl | east | button |
| boost | Shift | north | button |
| recover | R | select | button |
| zoom | scroll | shoulders | pinch |
What each one does to the vehicle:
- throttle drives the driven wheels (
drive:all,frontorrear): directly, the torque fading to nothing attopSpeed, or through an engine and a gearbox whenengineCurveis set (see "An engine and a gearbox"). Pulled back while the vehicle rolls forward it brakes; at a stop it reverses, up toreverseTopSpeed. Pushed forward while it rolls backward it brakes too. Under the direct drive, a driven wheel that spins on its own (inertiaabove 0) is spun no faster thantopSpeedat its tread (reverseTopSpeedin reverse,topSpeedtimesboostwhile boosting): the drive's rev limit, written to the wheel'smaxDriveSpeed. So full throttle spins the wheels up on ice, in a burnout or in the air, and they settle at that tread speed. - steer turns the steered wheels, the pair nearest the chassis' forward
(its local -Z), at up to
maxSteerAngleat rest, narrowing tosteerAtTopSpeedattopSpeed, turning atsteerRate; withcounterSteerset, they turn toward the way the vehicle travels as it slides (see "Holding a slide"). - brake brakes the wheels,
brakeBiasof it on the front and the rest on the rear (see "Brakes"); handbrake holds the wheelshandbrakeWheelsnames (the rear pair by default) withhandbrakeTorque. - boost multiplies the drive torque, the top speed and the driven
wheels' spin limit by
boost. - recover (the control, or
recover()from code) sets the chassis back on its wheels where it lay,recoverLiftabove it, facing the way it faced. - zoom changes how far the
camera's chase controller trails, when that camera hears no zoom control of its own (a chase camera on its default@builtin::inputMaps.cameraRigmap zooms itself).
The component writes motorTorque, maxDriveSpeed, brakeTorque and
steerAngle on the wheels, each only when its value changes.
Wheelspin
On wheels with an inertia (see the WheelCollider README, "Spin") the
drive spins a wheel faster than the road whenever it asks more than the tyre
puts down. The spin is bounded by the controller's top speeds: a 600 kg car on
wheels of inertia = 1.2 and radius 0.35, its rear pair driven with the
default 1500 N·m from rest, spins its rear treads up to topSpeed (25 m/s)
on asphalt and in the air and holds them there, and on forwardFriction = 0.15
still spins them past twice the road speed as it pulls away. The limit holds
the drive: a gripping tyre turns its wheel at the speed the road passes under
it, so a car rolling faster than topSpeed (downhill, or its rear end lifting
over a crest) turns its wheels faster too. A lower topSpeed is a lower rev
limit, and while boosting the limit is topSpeed times boost (a boost of 1
keeps it at topSpeed). tractionControl holds the drive short of spinning
the wheels (see "Traction control"). Wheels with no inertia roll at the road
speed, and their drive is held by their grip.
Brakes
brakeTorque is each wheel's brake at full brake with brakeBias at 0.5, its
default. brakeBias moves the brake between the axles: each front wheel brakes
with brakeTorque times 2 x brakeBias, and each rear wheel with brakeTorque
times 2 x (1 - brakeBias). So 0.5 brakes every wheel with brakeTorque on any
vehicle, a trike or a six-wheeler as well, and on a four-wheeled car (or any
with as many wheels at the front as at the rear) the front wheels take
brakeBias of the whole brake: a bias of 0.65 puts 1.3 times brakeTorque on
each front wheel and 0.7 times on each rear one. A road car's bias is 0.6 to
0.7: under a hard stop its weight moves onto the front tyres, and rear wheels
braked as hard as the front ones lock first, after which the rear of the car
lets go across the road and a brake into a corner spins it. A bias toward the
front keeps the rear wheels turning, so the car stops straight and turns while
it stops.
antiLock = true adds anti-lock brakes on wheels that spin on their own (an
inertia above 0; a wheel without one rolls at the road speed and never
locks). While braking above 1 m/s, each wheel is braked with the force its
tyre pushes back with at 0.95 of the slip its grip peaks at, read each step
from the grip curve its WheelCollider states (Physics.tyreCurve) at the
load it carries, on the surface it touches and at the slip angle it runs at,
so the wheel settles at that slip and turns as it brakes. A rear wheel takes
that share of its tyre's peak in its slip along the road and its slip angle
together, so the back of the car keeps the side grip its cornering needs; a
front wheel brakes along the road whatever it corners with. The load is the
one the tyre carries over the step to come: a falling load goes on falling by
the share it fell by over the last step, and a rising one counts at what it
was the step before, so the jolt of a bump or a landing does not brake the
wheel into a lock. A wheel that slips past twice its peak all the same (its
load fell away, it met a slippery patch) has its brake eased to half the
torque its tyre pushed back with, and let back on by 2 times itself each
second. A wheel off the ground, or touching it with no load on it, is left
unbraked. Under an engine drive the clutch is out while the anti-lock brakes,
so the engine neither brakes the driven wheels nor turns them. A tyre held
short of its peak grips harder than a sliding one (slideGrip, see the
WheelCollider README), so the car stops shorter and keeps steering: the
1420 kg rear-driven road car of the regression tests, on default tyres and
braked from 25 m/s through its engine and gearbox, stops in about 36 m with no
wheel locked above 2 m/s, and in about 40 m across three ridges 0.15 m high,
against 31.9 m at its tyres' full grip on a flat floor; the 1200 kg car
braked from 20 m/s stops with its wheels turning shorter
than on an even brake that locks all four, and braked into a gentle corner
with a bias of 0.65 it turns while it stops, where the locked car slides
straight on.
Set up a road car with both: a bias toward the front and anti-lock. The
anti-lock holds each rear wheel inside the grip its cornering leaves it, so a
car braked into a corner keeps its back in line on any split; the bias divides
the brake wherever the anti-lock leaves it as asked, on a wheel with no
inertia and under a brake lighter than the tyres' grip.
handbrakeWheels names the wheels the handbrake holds: rear (the default, a
road car's: locking the rear wheels steps the back end out into a slide),
front or all. While it is held under an engine drive the clutch is out, so
the locked wheels do not drag the engine down.
-- a road car that stops straight, and a drift car whose handbrake swings the back out
car.component.add("VehicleController", { brakeBias = 0.65, antiLock = true })
drifter.component.add("VehicleController", { drive = "rear", brakeBias = 0.6, handbrakeTorque = 4000 })
Traction control
tractionControl = true holds each driven wheel near the slip its tyre grips
best at under the throttle, the way antiLock holds a braked one, on wheels
that spin on their own (an inertia above 0). Each step, each driven wheel on
the ground takes no more drive than its tyre pushes back with at 0.95 of the
slip its grip peaks at, read from the grip curve its WheelCollider states
(Physics.tyreCurve) at the load it carries, on the surface it touches and at
the slip angle it runs at, so it holds from a standstill up, on tarmac, dirt,
grass or ice, and on ground that changes under the wheel. A rear wheel takes
that share of its tyre's peak in its slip along the road and its slip angle
together, so a car driven out of a corner keeps the side grip its back needs.
A wheel that slips past twice its peak all the same (a bump unloads it, it
lands spinning) has its drive eased to half the torque its tyre pushed back
with, and let back on by 8 times itself each second. It reads the player's
controls (keyboard, gamepad and touch alike) and setIntent, and acts
whatever the throttle asks; a wheel in the air is left to the drive's rev
limit.
- The direct drive: each driven wheel's
motorTorqueis held to that torque. - Through an engine (
engineCurve, or aShaftNetworkof your own with a part namedengine): each driven wheel's share of the engine's torque is measured from the network each step (the torque its links put on the wheel over the torque the engine puts into the drivetrain), so it holds through any gearing, clutch and differentials. The engine's throttle is eased to the torque that drives the wheel with the least room with what its tyre holds, plus the engine's own drag, trimmed toward the slip the wheels run at while it is what holds the drive back (neither the throttle asked for nor the clutch taking up the drive holds it to less). A wheel's share follows each shift of the gearbox until it is measured in the new gear, and the trim starts over off the throttle or on the brake, so every pull-away from a stop holds the drive the way the first one did, whatever gear and speed the car drove at before it. While it eases the throttle the link namedclutchcarries no more than that torque, so neither the clutch taking up the drive from a standstill nor the engine speed carried into a shift turns the wheels past their grip; a clutch holding an engine that turns faster than the gear it is in after a shift closes the throttle until the engine has come down to it.
driveState().tractionLimited reads whether it held the drive back this tick,
what a dashboard's traction light reads. The 1420 kg rear-driven road car of
the regression tests (an engine peaking at 452 N·m, six gears, a limited-slip
differential), launched at full throttle from rest, reaches 25 m/s on tarmac
(friction 1) in 4.7 s with no driven wheel past a slip ratio of 0.35 after its
first second, against 5.8 s with its rear wheels spinning to a slip ratio of 8
without it; on dirt (friction 0.65) in 8.2 s against 10.3 s, and on grass
(friction 0.45) in 13.5 s against 18.5 s; and it pulls away straight, where
without it the wheelspin turns it 19 degrees on dirt and spins it round 118
degrees on grass. The same car on the direct drive at 2500 N·m reaches 25 m/s
in 4.9 s against 5.8 s, and driven through an engine network of the author's,
in 4.7 s against 5.8 s.
It is off by default, so the throttle drives the wheels as asked: set it
false for a burnout, a power slide, a drift car steered on the throttle or a
monster truck's wheelspin.
-- a road car that pulls away and stops at the edge of its tyres' grip
car.component.add("VehicleController", {
drive = "rear", brakeBias = 0.65, antiLock = true, tractionControl = true,
engineCurve = { {1000, 180}, {4500, 260}, {6500, 220}, {7000, 0} },
})
An engine and a gearbox
With engineCurve set, the throttle drives the wheels through an engine
rather than with motorTorque: the controller puts a ShaftNetwork on the
chassis (see its README) holding an engine with that torque curve, a clutch,
a gearbox and differentials down to the driven wheels, solved with the tyres
inside the physics step. The car accelerates with the torque the curve gives
at the engine's speed, multiplied by the gear and the final drive, and the
gearbox shifts by itself.
car.component.add("VehicleController", {
drive = "rear",
engineCurve = { {1000, 180}, {4500, 260}, {6500, 220}, {7000, 0} }, -- rpm, N·m
gears = { 3.6, 2.2, 1.5, 1.1, 0.9 }, reverseGear = 3.4, finalDrive = 3.9,
differential = "limited",
})
- The engine turns with
engineInertia(kg·m²) and the torque ofengineCurvetimes the throttle (andboostwhile boosting). The curve's last point is its rev limit: a curve whose last point still gives torque is cut off 5% past it.engineBrakingis the engine's own friction (N·m), always on, so off the throttle in gear the car slows through its wheels. - The clutch takes up the drive as the engine revs past the curve's first
point, and is in by the time the engine (or the gearbox side of it) turns 30%
of the way from there to the rev limit: the car pulls away from a stop with the
engine revving, and the engine idles down with the clutch out once the car
stops. It is out while the handbrake is held, and carries no more than
tractionControlasks the engine for while it eases the throttle. - The gearbox starts in first and picks its gear by the road speed: it
shifts up once the engine speed the road speed calls for in the gear (the
driven wheels rolling at the road speed under them, through the gear and the
final drive) passes
shiftUpRpmon the throttle, and down once it falls belowshiftDownRpm(0, their default, is 90% and 45% of the curve's last rpm), holding each gear 0.4 s. So driven wheels spinning on a full-throttle launch keep it in first, and wheels locked under the brake keep it in the gear the car's speed calls for. It shifts only to a gear the engine would turn in between the two. The throttle pulled back at a stop selects reverse (reverseGear), and forward throttle at a stop first gear again. - The differentials split the gearbox's output, through
finalDrive, equally between the driven wheels: the front and rear halves first, then each axle's left and right.differentialmakes themopen(the wheel with less grip spins),limited(a torque bias ratio of 3) orlocked(the wheels turn together). - A driven wheel that states no
inertiaspins under the engine as a 20 kg wheel of its radius does (10 times its radius squared).
topSpeed still sets the speed the steering lock narrows to steerAtTopSpeed
at; motorTorque, reverseTorque and reverseTopSpeed are the direct drive's
alone. Changing an engine field, drive or boost builds the drivetrain
again; gears, reverseGear and the shift points apply from the next
physics step, in the gear the car is in.
driveState() reads drivetrain (engine), gear (1 for first, -1 for
reverse) and rpm, and the network itself reads through Physics.shaftNetworkState(car.id):
the engine part is engine, the links clutch and gearbox.
A ShaftNetwork the author put on the chassis is driven in its place: a
network with a part named engine takes the throttle on that part, the gear on
its link named gearbox (shifting through gears and reverseGear, with the
rev limit and the clutch's take-up read from the engine part's curve) and the
clutch on its link named clutch, where it has them. So a drivetrain of your
own (a transfer case, a locked rear and an open front differential, a tank's
two tracks) keeps the controller's input, intent and shifting.
Holding a slide
The A and D keys steer full lock or none, so a driver on a keyboard cannot
give a slide the small, changing opposite lock a stick gives it, and the
wheels spring back to straight the moment a key is let go. counterSteer
(0..1) turns the steered wheels toward the way the vehicle travels by that
share of its slide angle, the angle between its heading and its path over the
ground, and adds the driver's steer to it, within maxSteerAngle at any speed
(the driver's own lock narrows with speed). At 1, with the keys let go the
front wheels point along the path, as a car's front wheels trail it with the
wheel let go, and the driver holds the slide by working the keys against it as
it comes: W and D while it is narrower than wanted, A while it is wider. On
the ground the slide angle is driveState().slideAngle (degrees, positive
with the path to the right of the nose; 0 in the air, below 2 m/s and backing
up), what a drift score or a tyre sound reads.
-- a drift car driven from the keyboard
car.component.add("VehicleController", { drive = "rear", motorTorque = 2000, counterSteer = 1 })
The 1100 kg rear-driven car of the regression tests (wheels of radius 0.32,
inertia 1), with stability off, driven from the keys at 17 m/s into a
handbrake slide (D and Ctrl for 0.6 s) and then held with W, D and A decided
every 0.1 s from the slide a quarter of a second ahead (its angle plus a
quarter of a second of its rate), holds a slide of 14 to 38 degrees for 5 s,
gaining speed, its yaw rate within 40 degrees per second from half a second
after the handbrake is let go, with counterSteer = 1; with it at 0 the same
keys spin the car through 180 degrees at 100 degrees per second. With counterSteer = 1 the same car still
spins when its driver decides from the slide as it stands, or a tenth of a
second ahead, and when W, or W and D, are held throughout; lifting W as the
slide grows, with no steering key, catches the slide at 46 degrees and the car
straightens. counterSteer applies to whatever the intent comes from: the
keys, a stick, or code. It is 0 by default, which steers only where the driver
steers.
An ordinary corner holds a few degrees between heading and path as well, and
counterSteer acts on those too. On the same car at 20 m/s a steer of 0.3
turns it at 11 degrees per second with counterSteer = 1 and at 25 with it at
0 (its front wheels at 1.7 degrees against 3.8), while full lock turns it
about as fast either way; at 6 m/s the same steer turns it a little faster
with it (24 degrees per second against 20). A share below 1 catches a slide
less and leaves an ordinary corner's steering closer to the driver's.
Stability
A car whose front and rear tyres grip alike, steered past what they grip with,
turns faster than its path can follow; the rear tyres slide past their peak
and lose grip while the front ones still push, and the car spins. stability
(on by default) watches for that: once the rear tyres' slip, along the road
and across it together, is past the slip they grip best at, and while the car
turns toward the side the rear slides from faster than its path does (the
slide grows), the steered wheels turn no further into that turn than lets the
front tyres push with the share of their grip the rear tyres still grip with,
times the share of the car's turning its path keeps up with, and never past
maxSteerAngle. Read from the grip curves the wheels' WheelColliders state
(Physics.tyreCurve) at the slip each runs at, its brake and drive counted,
so it holds on any tyre and surface, braking, coasting or on the throttle.
Each tyre is read on the surface under it at the step the assist acts, so a
wheel crossing onto other ground (a painted terrain's layers included), a
surface redefined and a tyre retuned count from that step. The steering is
the driver's again once the slide holds steady or closes, so a slide the
driver makes with the throttle, or with a pull of the handbrake, and holds
with the steer stays theirs; what the assist takes away is the spin.
driveState().stabilizing reads whether it held the steering back this tick.
A rear wheel the handbrake holds grips with next to nothing across the road,
so while the handbrake is held at speed the assist lets the front turn the
car little further than its path: the road car below, held on the handbrake
from 25 m/s for 4 s with the wheel at full lock and counterSteer at 0,
turns 5 degrees, where with stability off it spins.
The 1420 kg rear-driven road car of the regression tests (an engine and six
gears, brakeBias = 0.66, antiLock, counterSteer = 0.6, its full lock 32
degrees at rest narrowing to 5 at 32 m/s) steered at 0.3 to 1 from 25 m/s
keeps its slide under 17 degrees at full brake and under 13 coasting (with
counterSteer at 0 or 1 as well) and keeps turning; with stability off the
same car slides to 93 degrees at full brake and steer 0.8, and to 68 coasting
at steer 0.6. The 1100 kg keyboard car of the regression tests, with every
default, still steps out into a handbrake slide and holds it at 15 to 19
degrees for 4.8 s on the keys, whether its driver decides from the slide a
quarter of a second ahead or from the slide as it stands; with
counterSteer = 1 as well it holds it at 15 to 17 degrees for 4 s.
Set it false for a car whose spins or handbrake turns are the game, or to
leave the steering wholly to the driver.
Arcade assists
Two assists turn on with a field, for a stunt game, a kart racer or a monster truck show:
- Air control (
airControlabove 0): with no wheel on the ground the chassis turns toward level, its nose raised toairPitchby throttle and lowered by reverse or brake, spinning atairSpinwhere it is steered;airControlis how fast it gets there. 4 is a stunt vehicle's. - Self-righting (
recoverAfterabove 0): lying on its side or roof slower than 1 m/s forrecoverAfterseconds sets it back on its wheels, the wayrecoverdoes. 3 is a stunt vehicle's.
car.component.add("VehicleController", { airControl = 4, recoverAfter = 3 })
Both are 0 by default, so a vehicle added with no fields set is a simulation
vehicle. A scene that relied on a vehicle steering in the air or righting
itself states airControl = 4 and recoverAfter = 3, the values these fields
held before they defaulted to 0.
Driving it from code
setIntent drives the vehicle the way the player's controls do: the drive
split, the top speed, the steering lock that narrows with speed and the
assists all apply. It takes the keys driveState reports (throttle -1..1
positive forward, steer -1..1 positive right, brake 0..1, handbrake and
boost booleans) and switches controlSource to script. The table is the
whole intent: a key left out is neutral (0, or false), and the numbers are
clamped to their ranges. An unknown key, or a value of the wrong type
(handbrake = 1), is refused with an error naming it, and the intent stays as
it was. Writing the fields (throttle, steer, brake,
handbrake, boosting, controlSource) directly changes one at a time.
-- an AI car that steers toward a target each tick
local car = entity.find("aiCar")
local target = entity.find("waypoint")
local vc = car.component.get("VehicleController")
task.loop(function()
if not car:isLocal() then return end
local p, w = car.position, target.position
local dx, dz = w.x - p.x, w.z - p.z
local f, r = car.transform.forward, car.transform.right
local ahead = dx * f.x + dz * f.z -- metres ahead of the car
local side = dx * r.x + dz * r.z -- metres to its right
vc:setIntent({ throttle = 0.6, steer = math.clamp(2 * side / math.max(math.abs(ahead), 1), -1, 1) })
end)
Set controlSource = "input" to hand a vehicle that holds the controls back
to the player; a vehicle that never took them answers them once takeControl()
is called. A vehicle added with controlSource = "script" does not take the
controls as it wakes, so a player car and a code-driven car drive side by
side. A player car switched to script (an autopilot) keeps the controls it
holds, so the walking controls stay stood down until releaseControl.
Only the peer that owns the chassis drives it. The intent fields are synced:
code sets them on the owning peer (car:isLocal()), the way every synced field
is written, every peer reads the same intent, and a peer that takes the
vehicle over drives on with it.
Fields
| Field | Default | Unit | Meaning |
|---|---|---|---|
inputMap | @builtin::inputMaps.vehicle | The controls it reads. | |
takeControlOnAwake | true | Take the controls as it wakes; false waits for takeControl(). | |
drive | all | Which wheels the throttle drives. | |
motorTorque | 1500 | N·m | Drive torque per driven wheel at full throttle. |
reverseTorque | 800 | N·m | Drive torque per driven wheel at full reverse. |
brakeTorque | 3000 | N·m | Brake torque per wheel at full brake, with brakeBias at 0.5. |
brakeBias | 0.5 | 0..1 | Front against rear: each front wheel brakes with 2 x it, each rear one with 2 x (1 - it), times brakeTorque. |
antiLock | false | Anti-lock brakes on wheels with an inertia: each braked wheel held just short of its tyre's peak slip, a rear wheel within the grip its cornering leaves it, the clutch out. | |
tractionControl | false | Traction control on wheels with an inertia: each driven wheel's drive held to what its tyre grips with just short of its peak slip, a rear wheel within the grip its cornering leaves it; an engine's throttle (and its clutch) eased to it. | |
handbrakeTorque | 6000 | N·m | Brake torque per wheel the handbrake holds. |
handbrakeWheels | rear | Which wheels the handbrake holds: rear, front or all. | |
topSpeed | 25 | m/s | Forward speed where the direct drive has faded to nothing, the fastest tread speed it spins a driven wheel with an inertia to, and the speed the steering lock narrows to steerAtTopSpeed at. |
reverseTopSpeed | 8 | m/s | Reverse speed where the direct drive has faded to nothing, and the fastest tread speed the reverse drive spins a driven wheel with an inertia to. |
engineCurve | {} | rpm, N·m | The engine's torque curve; set, the throttle drives through an engine and a gearbox. |
gears | { 3.6, 2.2, 1.5, 1.1, 0.9 } | × | Forward gear ratios, first gear first. |
reverseGear | 3.4 | × | Reverse gear ratio. |
finalDrive | 3.9 | × | Final drive ratio. |
shiftUpRpm | 0 | rpm | Shift up once the engine speed the road speed calls for passes it; 0 is 90% of the curve's last rpm. |
shiftDownRpm | 0 | rpm | Shift down once the engine speed the road speed calls for falls below it; 0 is 45% of the curve's last rpm. |
engineInertia | 0.2 | kg·m² | The engine's moment of inertia. |
engineBraking | 20 | N·m | The engine's friction, always on: its braking off the throttle. |
differential | open | open, limited or locked. | |
boost | 1.6 | × | Drive and top speed multiplier while boosting. |
maxSteerAngle | 30 | degrees | Full lock at rest. |
steerAtTopSpeed | 8 | degrees | Full lock at topSpeed. |
steerRate | 150 | degrees/s | How fast the wheels turn toward the lock asked for. |
counterSteer | 0 | 0..1 | Share of the slide angle the steered wheels turn toward the path by, added to the driver's steer within maxSteerAngle; 1 trails them along the path with the steer let go. |
stability | true | Stability assist: while the rear tyres slide past their grip and the slide grows, the steered wheels turn no further into it than the rear tyres' grip answers; false leaves the steering to the driver and counterSteer. | |
airControl | 0 | rad/s² | Air control assist: how fast the chassis turns in the air toward the attitude asked for; 0 leaves its flight to physics. |
airPitch | 20 | degrees | Nose angle held in the air at full throttle or full reverse, under air control. |
airSpin | 90 | degrees/s | Spin about the up axis at full steer in the air, under air control. |
recoverAfter | 0 | s | Self-righting assist: time overturned before it rights itself; 0 waits for the recover control or recover(). |
recoverLift | 1 | m | Height above where it lay that a righted vehicle is set down. |
camera | A camera with @builtin::controller.chase that zoom moves, while that camera hears no zoom of its own. | ||
controlSource | input | Where the intent comes from: input (the player's controls) or script (the intent fields). | |
throttle | 0 | -1..1 | Intent under script: positive forward, negative reverse. |
steer | 0 | -1..1 | Intent under script: positive right. |
brake | 0 | 0..1 | Intent under script: share of brakeTorque. |
handbrake | false | Intent under script: the handbrake holds the wheels handbrakeWheels names. | |
boosting | false | Intent under script: boost held. |
Methods
takeControl(): the controls go live; the walking controls stand down.releaseControl(): the controls go back; the wheels coast straight.hasControl() -> booleansetIntent({ throttle?, steer?, brake?, handbrake?, boost? }): drive it from code; switchescontrolSourcetoscript.recover(): set it back on its wheels now.driveState():{ source, throttle, steer, brake, handbrake, boost, speed, groundedWheels, wheels, steerAngle, slideAngle, stabilizing, tractionLimited, overturned, recoveries, lastRecover, drivetrain, gear, rpm }: the intent from the current source;slideAngleis the slide in degrees (see "Holding a slide");stabilizingandtractionLimitedwhetherstabilityheld the steering back andtractionControlthe drive this tick;lastRecoveriscontrol,calloroverturned;drivetrainisdirectorengine, with the gear (1 first, -1 reverse) and the engine's rpm under an engine.
Example
A monster truck: a heavy chassis on four big wheels with long travel, the suspension force raised to carry the landings, with the arcade assists on.
local truck = entity.spawn("truck")
truck.position = { 0, 2.2, 0 }
truck.component.add("Physics", { kind = "dynamic", mass = 2000 })
truck.component.add("BoxCollider", { half = { 1.3, 0.4, 2.2 } })
for _, at in { { -1.5, -1.7 }, { 1.5, -1.7 }, { -1.5, 1.7 }, { 1.5, 1.7 } } do
local wheel = entity.spawn("wheel", { parent = truck.id })
wheel.localPosition = { at[1], -0.2, at[2] }
local tyre = entity.spawn("tyre", { parent = wheel.id })
tyre.component.add("Model", { model = "cylinder" })
tyre.localRotation = { 0, 0, 0.7071068, 0.7071068 }
tyre.localScale = { 1.6, 0.6, 1.6 }
wheel.component.add("WheelCollider", {
radius = 0.8, width = 0.6,
suspensionDistance = 0.8, springRate = 25000, damperRate = 5000,
maxSuspensionForce = 40000,
visual = tyre,
})
end
truck.component.add("VehicleController", {
motorTorque = 4000, brakeTorque = 8000, topSpeed = 25,
airControl = 4, recoverAfter = 3,
})
A car the player gets into takes the controls when the driver enters:
car.component.add("VehicleController", { takeControlOnAwake = false })
car.component.get("VehicleController"):takeControl() -- driver in
car.component.get("VehicleController"):releaseControl() -- driver out