
Plane
Vehicles · by Brixborg
A script flies it where the camera looks: W goes, look up to climb. Give it room to take off.
Use it in your games: in the studio, open View > Archive, go to the Vehicles shelf and pick "Plane".
The script
-- Flies the plane. Sit in the seat, then:
-- W go: it flies the way the CAMERA is looking
-- S slow down
-- the mouse hold the right button and look where you want to go:
-- look up to climb, look down to dive, look round to turn
-- Hold W to roll down the runway. At TAKE_OFF speed the wings start to
-- carry it: look up and it climbs. Let go of W and it slows and sinks
-- gently back down: that is how to land.
--
-- It flies the way planes were scripted in 2013:
-- a BodyGyro points the plane the way it should face
-- a BodyVelocity moves it the way it is pointing
-- and this script decides, many times a second, what those should be.
--
-- Where the pilot is looking comes from the pilot's own computer: while
-- someone sits in the seat, the PilotView LocalScript (inside this script)
-- is put on their screen, and it sends their camera's direction here
-- through the Look RemoteEvent, many times a second. (In a game with many
-- players, the server has no camera of its own to look through.)
local plane = script.Parent
local body = plane.Fuselage
local seat = plane.DriverSeat
local point = body.BodyGyro
local push = body.BodyVelocity
local propeller = body:FindFirstChild("Motor") -- the joint that spins the propeller
local TOP_SPEED = 90 -- studs per second at full power
local TAKE_OFF = 40 -- the wings carry the plane at this speed
local POWER_UP = 0.35 -- how fast W adds power (0.35 = full power in 3 seconds)
local POWER_DOWN = 0.5 -- how fast S takes it off
local COASTING = 0.12 -- how fast the power fades with neither key held
local QUICKNESS = 22 -- how fast it gains and loses speed, studs per second each second
local LEVEL_VIEW = 20 -- the camera usually looks DOWN on the plane by about this many
-- degrees. That counts as "straight ahead"; above it climbs
local STEEPEST = 45 -- the steepest it will climb or dive, in degrees
local BANK = 40 -- how far it leans over in a sharp turn, in degrees
local TURNING = 1.1 -- how fast it can turn, in radians per second
local SINK = 45 -- how fast it falls with no speed at all
local GENTLE_BELOW = 14 -- this close to the ground...
local GENTLE_SINK = 10 -- ...it sinks no faster than this
local GROUND_BELOW = 4 -- the ground is this far under the middle of the fuselage when it stands on its wheels (and a bit)
local weight = 0
for _, piece in ipairs(plane:GetChildren()) do
if piece:IsA("BasePart") then weight = weight + piece:GetMass() end
end
local strong = weight * workspace.Gravity
local power = 0 -- 0 = engine off, 1 = flat out
local speed = 0
local look = body.CFrame.lookVector
local heading = math.atan2(-look.X, -look.Z) -- which way it points, as an angle
local pitch, bank = 0, 0
-- The pilot's view, as their computer last sent it.
local pilotLook = nil
local lookEvent = Instance.new("RemoteEvent")
lookEvent.Name = "Look"
lookEvent.Parent = plane
local pilotView = script:WaitForChild("PilotView")
local pilot, viewScript = nil, nil
seat:GetPropertyChangedSignal("Occupant"):connect(function()
if viewScript then viewScript:Destroy() viewScript = nil end
pilot, pilotLook = nil, nil
local humanoid = seat.Occupant
local player = humanoid and game:GetService("Players"):GetPlayerFromCharacter(humanoid.Parent)
if not player then return end
pilot = player
viewScript = pilotView:Clone()
local link = Instance.new("ObjectValue")
link.Name = "Plane"
link.Value = plane
link.Parent = viewScript
viewScript.Disabled = false
viewScript.Parent = player:FindFirstChild("PlayerGui")
end)
lookEvent.OnServerEvent:connect(function(player, view)
if player == pilot and typeof(view) == "Vector3" and view.magnitude > 0 then pilotLook = view.unit end
end)
-- The difference between two angles, the short way round.
local function angleBetween(from, to)
return (to - from + math.pi) % (2 * math.pi) - math.pi
end
while true do
local passed = wait()
local flying = seat.Occupant ~= nil
-- W adds power, S takes it off, and with neither it slowly fades.
-- (With nobody flying it, the engine winds down.)
local throttle = flying and seat.Throttle or -1
if throttle > 0 then
power = power + POWER_UP * passed
elseif throttle < 0 then
power = power - POWER_DOWN * passed
else
power = power - COASTING * passed
end
power = math.max(0, math.min(1, power))
-- Speed creeps towards what this much power gives.
local wanted = power * TOP_SPEED
if speed < wanted then
speed = math.min(wanted, speed + QUICKNESS * passed)
else
speed = math.max(wanted, speed - QUICKNESS * passed)
end
-- How much of the plane's weight are the wings carrying? (0 to 1)
local lift = math.min(1, (speed / TAKE_OFF) ^ 2)
-- Where is the pilot looking?
local wantedHeading, wantedPitch = heading, 0
if flying and pilotLook then
local view = pilotLook
wantedHeading = math.atan2(-view.X, -view.Z)
-- Up and down only count once the wings are carrying it.
if lift >= 1 then
wantedPitch = math.deg(math.asin(math.max(-1, math.min(1, view.Y)))) + LEVEL_VIEW
wantedPitch = math.max(-STEEPEST, math.min(STEEPEST, wantedPitch))
end
end
-- Turn towards it, no faster than TURNING (and more slowly when slow),
-- leaning into the turn.
local most = TURNING * math.min(1, speed / TAKE_OFF + 0.2) * passed
local turn = math.max(-most, math.min(most, angleBetween(heading, wantedHeading)))
heading = heading + turn
local wantedBank = 0
if lift >= 1 and passed > 0 then wantedBank = (turn / passed) / TURNING * BANK end
-- (Lean and tip gradually, not all at once.)
bank = bank + (wantedBank - bank) * math.min(1, passed * 3)
pitch = pitch + (wantedPitch - pitch) * math.min(1, passed * 2)
local facing = CFrame.Angles(0, heading, 0) * CFrame.Angles(math.rad(pitch), 0, math.rad(bank))
-- Are the wheels on the ground? (Is there anything just underneath?)
local below = workspace:FindPartOnRay(Ray.new(body.Position, Vector3.new(0, -GROUND_BELOW, 0)), plane)
local onGround = below ~= nil
if onGround and power == 0 and speed == 0 then
-- Parked: let go, and leave it standing on its wheels, with just
-- enough of a brake to stop it rolling away.
point.maxTorque = Vector3.new()
push.velocity = Vector3.new()
push.maxForce = Vector3.new(strong, 0, strong) * 0.2
look = body.CFrame.lookVector
heading = math.atan2(-look.X, -look.Z)
else
point.maxTorque = Vector3.new(strong, strong, strong) * 40
point.cframe = facing
-- Go the way the nose points, sinking if the wings aren't carrying
-- everything.
local sink = (1 - lift) * SINK
-- Close to the ground it eases off, to touch down gently.
if workspace:FindPartOnRay(Ray.new(body.Position, Vector3.new(0, -GENTLE_BELOW, 0)), plane) then
sink = math.min(sink, GENTLE_SINK)
end
push.velocity = facing.lookVector * speed - Vector3.new(0, sink, 0)
if onGround and pitch <= 1 then
-- Rolling along the ground: push forwards only, and let the wheels
-- carry the weight.
push.maxForce = Vector3.new(strong, 0, strong) * 2
else
push.maxForce = Vector3.new(strong, strong, strong) * 2
end
end
-- Spin the propeller. (A Motor turns towards DesiredAngle; one that is
-- very far away never gets there, so it spins for ever.)
if propeller then
propeller.MaxVelocity = power * 0.6
propeller.DesiredAngle = 1e9
end
end