Make a menu slide into view and close smoothly. The controller handles interrupted transitions so an old closing animation cannot hide a newly opened menu.
Before you start
Complete Build a Responsive Menu. Keep its MenuGui, OpenButton, Panel, and CloseButton. Replace the entire MenuController script with the code below. Do not run both controllers at once.
1. Add the animation controller
local TweenService = game:GetService("TweenService")
local gui = script.Parent
local openButton = gui:WaitForChild("OpenButton")
local panel = gui:WaitForChild("Panel")
local closeButton = panel:WaitForChild("CloseButton")
local openPosition = UDim2.fromScale(0.5, 0.5)
local closedPosition = UDim2.fromScale(0.5, 1.5)
local info = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local activeTween = nil
local completionConnection = nil
local revision = 0
panel.Position = closedPosition
panel.Visible = false
openButton.Visible = true
local function setOpen(isOpen)
revision += 1
local thisRevision = revision
if completionConnection then
completionConnection:Disconnect()
completionConnection = nil
end
if activeTween then
activeTween:Cancel()
activeTween = nil
end
panel.Visible = true
openButton.Visible = not isOpen
local target = isOpen and openPosition or closedPosition
local tween = TweenService:Create(panel, info, { Position = target })
activeTween = tween
completionConnection = tween.Completed:Connect(function(state)
if thisRevision ~= revision then
return
end
if completionConnection then
completionConnection:Disconnect()
completionConnection = nil
end
activeTween = nil
if state == Enum.PlaybackState.Completed and not isOpen then
panel.Visible = false
end
end)
tween:Play()
end
openButton.Activated:Connect(function()
setOpen(true)
end)
closeButton.Activated:Connect(function()
setOpen(false)
end)
2. Understand the transition
The panel keeps the centered anchor from the previous lesson. Its open position is halfway across and down the available space. Its closed position puts it below the screen.
The controller makes the panel visible before playing the tween. When closing, it hides the panel only after successful completion. Setting Visible to false immediately would prevent the player from seeing the closing motion.
3. Handle fast input
Each request disconnects the previous completion listener and cancels the current tween. The next tween begins from the current position, so reversing direction does not require snapping back to the start.
The revision number rejects a callback from an older request. Checking PlaybackState also matters because cancellation can fire Completed without reaching the intended destination.
4. Test interruptions
Start a Play test and open the menu. Close it, then quickly press Open menu while the closing transition is still moving. It should reopen and remain visible. Repeat several times and check Output for errors.
To make this easier to inspect, temporarily change the duration to 2 seconds. Restore 0.25 after testing. Also check portrait and landscape layouts. Keep essential controls usable without requiring the player to watch a long animation.
Troubleshooting
- The panel jumps: confirm the old controller was replaced, not left running.
- Position changes have no effect: a layout object may be controlling the panel's placement. Keep this panel directly under MenuGui.
- The panel disappears after reopening: check that the revision and playback-state checks were preserved.