Doors, lifts and moving platforms#
This page builds four things: a door that swings on a hinge, a lift that rides a slider between floors, a platform that shuttles back and forth, and a turntable. The door and the lift are loose parts joined to an anchored one by a joint with a servo, the platform and the turntable are anchored parts moved by a tween or a script, and the joints themselves are explained in Physics: hinges, sliders and moving anchored parts.
There are two ways to move a part, and the choice decides how it behaves:
| Anchored, moved by a script or tween | Unanchored, on a hinge or slider | |
|---|---|---|
| Path | exactly the one you write | wherever the joint and the forces take it |
| Blocked by something in the way | no, it pushes through | yes, a crate in a door stops it |
| Carries players and loose parts | yes | yes |
| Who simulates it | the server | the server, while it is joined to an anchored part |
Use an anchored part when it has to arrive on time, like a platform on a route. Use a joint when it should feel physical, like a door a player can shove or a lift a heavy load slows down.
A door#
A door is a loose part on a hinge to an anchored frame. A servo turns it to an angle and holds it there:
-- server
local world = game.world
local frame = world:add("Part", { name = "DoorFrame", size = vec3(0.2, 2.2, 0.2),
cframe = cframe(10, 65.1, 0), anchored = true }) :: Part
local door = world:add("Part", { name = "Door", size = vec3(1, 2, 0.1),
cframe = cframe(10.6, 65.1, 0), anchored = false }) :: Part
local up = cframe.angles(0, 0, math.pi / 2)
local hinge = world:add("HingeConstraint", {
attachment0 = frame:add("Attachment", { cframe = cframe(0.1, 0, 0) * up }),
attachment1 = door:add("Attachment", { cframe = cframe(-0.5, 0, 0) * up }),
actuatorType = "servo", angularSpeed = 2, servoMaxTorque = 2000,
limitsEnabled = true, lowerAngle = 0, upperAngle = 100,
}) :: HingeConstraint
local prompt = door:add("ProximityPrompt", { actionText = "Open", keys = "e" }) :: ProximityPrompt
prompt.triggered:connect(function()
local open = hinge.targetAngle == 0
hinge.targetAngle = if open then 90 else 0
prompt.actionText = if open then "Close" else "Open"
end)- A hinge turns about attachment0's X axis.
upturns both attachments so that X points up, and the door swings like a door. - The attachments sit on the frame's inner face and on the door's edge, where the two meet.
- The limits keep the swing between 0 and 100 degrees, so it never goes round through the wall.
servoMaxTorqueis how hard it holds. Low, a player walking into the door pushes it; high, it does not give.
For a door that swings both ways and falls shut, leave actuatorType at "none" and add a
spring from the frame to the door's far edge. For a revolving door, use
actuatorType = "motor" with an angularVelocity and no limits.
A lift#
A lift is a loose platform on a slider to an anchored base. A servo moves it to a floor:
-- server
local world = game.world
local base = world:add("Part", { name = "LiftBase", size = vec3(4, 0.2, 4),
cframe = cframe(20, 64.1, 0), anchored = true }) :: Part
local platform = world:add("Part", { name = "Lift", size = vec3(4, 0.4, 4), density = 4,
cframe = cframe(20, 64.4, 0), anchored = false }) :: Part
local up = cframe.angles(0, 0, math.pi / 2)
local lift = world:add("PrismaticConstraint", {
attachment0 = base:add("Attachment", { cframe = cframe(0, 0.1, 0) * up }),
attachment1 = platform:add("Attachment", { cframe = cframe(0, -0.2, 0) * up }),
actuatorType = "servo", speed = 2, servoMaxForce = 20000,
limitsEnabled = true, lowerLimit = 0, upperLimit = 8,
}) :: PrismaticConstraint
local FLOORS = { 0, 4, 8 }
for n, height in FLOORS do
local button = world:add("Part", { name = "Call" .. n, size = vec3(0.3, 0.3, 0.1), anchored = true,
cframe = cframe(22.4, 65.5 + height, -2.1) }) :: Part
local prompt = button:add("ProximityPrompt", { actionText = "Floor " .. n, keys = "e" }) :: ProximityPrompt
prompt.triggered:connect(function()
lift.targetPosition = height
end)
end- A slider slides along attachment0's X axis, and
uppoints it up. The position is how far the platform's attachment is above the base's, so 0 is resting on the base. - The two attachments meet at the base's top face, so the platform starts at rest at position 0.
- The slider also holds the platform level and stops it sliding sideways, so a player stepping on at one corner does not tip it.
servoMaxForcehas to hold up the platform and whatever is on it. A platform's weight isplatform:getMass() * game.gravity, about 800 here atdensity = 4; the rest is for the load.- Each call button sends the lift to its floor. Reading
lift.currentPositiontells you where it is, for a light or a sound when it arrives.
The platform is joined to an anchored part, so it always stays with the server and every player sees it in the same place.
A moving platform#
A platform on a route is an anchored part moved by a tween. It goes where the tween says, whatever is on it, and carries players and loose parts:
-- server
local world = game.world
local platform = world:add("Part", { name = "Platform", size = vec3(3, 0.5, 3),
cframe = cframe(0, 66, 30), anchored = true }) :: Part
platform:tween({ cframe = cframe(12, 70, 30) }, {
time = 4, easing = "sine", direction = "inOut",
repeats = -1, reverses = true,
})repeats = -1runs it for ever, andreversesbrings it back each time, so it goes back and forth between the two spots.easing = "sine"with"inOut"slows it at each end, so a player standing on it is not jolted when it turns round.- Any part on top is carried along by friction, and one in its way is pushed.
A turntable is the same idea, turned instead of moved:
-- server
local turntable = world:add("Part", { name = "Turntable", shape = "cylinder", size = vec3(0.5, 8, 8),
cframe = cframe(0, 64.25, 50) * cframe.angles(0, 0, math.pi / 2),
anchored = true }) :: Part
game.stepped:connect(function(dt)
turntable.cframe = turntable.cframe * cframe.angles(math.rad(30 * dt), 0, 0)
end)The cylinder lies along its X axis, so it is turned upright first, and then turned about that same X axis thirty degrees a second. A player on it goes round with it and turns to face the way it turns.
How a moving platform stays under the player#
- A tween on an anchored part is not streamed position by position. The server sends the tween once, with the moment it started on the game clock, and every client plays it itself. Each client keeps its copy of the game clock in step with the server, so the platform is in the same place on every screen, and a player who joins late gets the running tween.
- Your own client plays it ahead of the server by half your ping, the time your own steps take to reach the server, so where you stand on it is where the server has it when your step arrives.
- A part moved by a script every tick, like the turntable above, is streamed, and your client carries it on at the speed of its last moves until the next one arrives.
- A player standing on a moving part moves with it in its own frame: carried along, turned with it and taken down with a lift going down. Jumping off keeps the part's speed. The server places the player on its own copy of the part, so other players see them standing on it too.
- Pausing, cancelling or finishing a tween reaches every client the same way.
When something goes wrong#
- Loose parts left behind by a platform mean it teleported, by
pivotToor by a farcframe. Only moves of under eight metres and a quarter turn in a tick carry anything. - An anchored platform pushes through everything, players included, so keep its route clear of walls a player could be pinned against.
- A platform moved from a
LocalScriptmoves only on that player's screen. Move platforms from the server. - Hinges and sliders do nothing in the editor. Press Play to see the door swing.
