An explanation of what I mean:
wait() yields the current script for a short amount of time that can vary in case of lag. Your script also sets the door at regular position offsets, meaning that it will move fast without lag, slowly with it.
Heartbeat/RenderStepped/Stepped are events which you can bind a function to fire per-frame. One of the parameters it gives is called “step” a time difference between the current and last frame; in other words, dividing 1 with it will give you the current frame rate. By knowing this time difference, you can sync your door to the lag, making larger offsets if the frame rate is lower to compensate.
Every connection, when the :Connect method is used, returns a userdata object which you can cancel the connection with by calling its:Disconnect() method. I used it to cancel the door’s movement when it reached its target.
There is also a debounce value (deb) which blocks interaction until the current movement is over. It can be removed to allow reversing midway through, but it’s much harder.
Heartbeat fires after each frame, meaning that it’s perfect for complex physics simulations, while RenderStepped fires before each render frame, which might cause lag if the function takes too long.