Moud

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:

luau
-- 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. up turns 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.
  • servoMaxTorque is 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:

luau
-- 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 up points 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.
  • servoMaxForce has to hold up the platform and whatever is on it. A platform's weight is platform:getMass() * game.gravity, about 800 here at density = 4; the rest is for the load.
  • Each call button sends the lift to its floor. Reading lift.currentPosition tells 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:

luau
-- 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 = -1 runs it for ever, and reverses brings 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:

luau
-- 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 pivotTo or by a far cframe. 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 LocalScript moves 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.