Hello and welcome back!
In our last session, you successfully transformed your UI FSM from an automated sequence into a fully interactive system driven by user button clicks. The logic is solid, but the visual transitions are instant and can feel jarring. Today, we'll polish this experience by making the state changes smooth and professional.
This lesson focuses on the learning outcome: Implement animated transitions between UI states using TweenService. We will leverage your existing experience with Roblox's TweenService to animate the ScreenGuis as they enter and exit, creating a much more appealing user interface.
1. A Quick Refresher on TweenService
Since you're already familiar with TweenService, we'll just do a quick review to bring the key components back to the front of your mind. A "tween" is essentially an animation that smoothly interpolates an object's properties from a starting point to an end goal over a set duration.
The three core ingredients are:
TweenInfo: A data object that defines how the animation should run—its duration, easing style, direction, repeats, etc.- Goal Table: A Luau table that specifies the target properties and their final values (e.g.,
{ Transparency = 1, Size = UDim2.new(0, 200, 0, 100) }). - The Tween Object: Created by calling
TweenService:Create()with the object to animate, theTweenInfo, and the goal table. You then start the animation withtween:Play().
To see these pieces in action again, let's watch a concise tutorial.
TweenService Made Easy ⚡ Roblox Scripting Tutorial
This video, 'TweenService Made Easy' by jotslo, provides a clear and quick overview of creating tweens. It covers defining properties, setting up TweenInfo, and creating and playing the animation.
Please watch from the beginning to 02:55. Pay special attention to: How the target properties are stored in a table. The different arguments of TweenInfo.new(). The final call to TweenService:Create(). The example at the end where the code is adapted for a GUI Frame.
2. Choosing the Right Animation Style
A simple linear animation gets the job done, but to make your UI feel truly polished and responsive, you can use different Enum.EasingStyle options. These functions control the rate of change during the animation, creating effects like bouncing, accelerating, or decelerating.

You can experiment with styles like Quad, Cubic, or even Bounce to give your UI a distinct personality. The video below gives a great visual demonstration of these styles.
How to Animate UI SMOOTHLY on Roblox (TweenService Tutorial)
This segment from 'How to Animate UI SMOOTHLY on Roblox' by script_ing visually compares different easing styles.
Watch from 03:43 to 04:31 to see how different EasingStyle choices affect the feel of the animation.
3. Integrating Animations into FSM Callbacks
Now, let's connect TweenService to our FSM. The strategy is to use the enter and exit callbacks we already have:
- The
exitcallback will handle animating a UI panel out (e.g., fading to transparent). - The
entercallback will handle animating a new UI panel in (e.g., fading from transparent).
A crucial part of this is managing the animation timeline. We don't want the new screen to appear before the old one has finished disappearing. We can achieve this by making our exit function wait for the "out" animation to complete before the transitionTo function proceeds. The tween.Completed event is perfect for this.
The following devforum tutorial demonstrates exactly this pattern for opening and closing a shop UI.
An Introduction to Tweening GUIS (Animations)
This community tutorial, 'An Introduction to Tweening GUIS (Animations)', provides practical code examples for common UI animations.
Please read the section titled 'From Center'. Notice the code for the isOpen.Changed event. When closing the shop (isOpen.Value == false), it plays a tween and then uses shopTween.Completed:Connect(function() ... end) to hide the frame after the animation is finished. We will use a similar blocking wait pattern: tween.Completed:Wait().
4. Your Turn: Implementing Animated Transitions
It's time to apply this to your UI FSM. We'll implement a smooth fade-in/fade-out effect. The best tool for fading an entire UI panel with all its children (buttons, text, etc.) is the CanvasGroup object. It acts as a container, and by tweening its GroupTransparency property, you can fade everything inside it at once.
Your Task:
-
Restructure Your UI:
- In the Explorer, for each of your
ScreenGuis(MainMenuGui,SettingsGui,GameplayGui), add aCanvasGroupobject. - Drag and drop all the other elements of that GUI (like your buttons and any frames) to be children of the new
CanvasGroup.
Your
MainMenuGuistructure should look something like this:- MainMenuGui (ScreenGui) - CanvasGroup - PlayButton (TextButton) - SettingsButton (TextButton) - In the Explorer, for each of your
-
Update Your
LocalScript:- Modify your FSM script to get references to these new
CanvasGroupobjects. - Define a
TweenInfothat you can reuse for your animations. A duration of0.3seconds withEnum.EasingStyle.Quadis a good starting point. - Update your
enterandexitfunctions to perform the fade animations. Theexitfunction should create the fade-out tween and wait for it to complete before disabling the GUI.
- Modify your FSM script to get references to these new
Here is a template to guide you. Fill in the missing parts based on the logic we've discussed.
-- In a LocalScript in StarterPlayerScripts
local TweenService = game:GetService("TweenService")
-- FSM table from previous lesson
local UiFsm = { ... }
-- UI References
local player = game.Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
-- Get references to ScreenGuis
local mainMenuGui = playerGui:WaitForChild("MainMenuGui")
local settingsGui = playerGui:WaitForChild("SettingsGui")
local gameplayGui = playerGui:WaitForChild("GameplayGui")
-- TODO: Get references to the CanvasGroup inside each ScreenGui
local mainMenuCanvas = mainMenuGui:WaitForChild("CanvasGroup")
-- ... and for settings and gameplay
-- Button References
-- ... (from previous lesson)
-- Define a reusable TweenInfo for our animations
local FADE_INFO = TweenInfo.new(0.3, Enum.EasingStyle.Quad)
-- Define the states and their enter/exit callbacks
UiFsm.states = {
MainMenu = {
enter = function()
-- 1. Enable the GUI
mainMenuGui.Enabled = true
-- 2. Create a tween to fade IN (GroupTransparency from 1 to 0)
local tweenIn = TweenService:Create(mainMenuCanvas, FADE_INFO, {GroupTransparency = 0})
-- 3. Play the tween
tweenIn:Play()
end,
exit = function()
-- TODO: Create a tween to fade OUT (GroupTransparency from 0 to 1)
-- TODO: Play the tween
-- TODO: Wait for the tween to complete
-- TODO: Disable the GUI
end
},
Settings = {
-- TODO: Implement enter and exit for the Settings state
},
Gameplay = {
-- TODO: Implement enter and exit for the Gameplay state
}
}
-- Connect button events (from previous lesson)
-- ...
-- Start the FSM
-- Before starting, make all canvases fully transparent
mainMenuCanvas.GroupTransparency = 1
settingsGui.CanvasGroup.GroupTransparency = 1
gameplayGui.CanvasGroup.GroupTransparency = 1
UiFsm:transitionTo("MainMenu")
Click here for the complete solution
-- In a LocalScript in StarterPlayerScripts
local TweenService = game:GetService("TweenService")
-- FSM table
local UiFsm = {
currentState = nil,
states = {},
transitionTo = function(self, newStateName)
if self.currentState and self.states[self.currentState].exit then
self.states[self.currentState].exit()
end
self.currentState = newStateName
if self.states[self.currentState].enter then
self.states[self.currentState].enter()
end
print("UI transitioned to state:", self.currentState)
end
}
-- UI References
local player = game.Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local mainMenuGui = playerGui:WaitForChild("MainMenuGui")
local settingsGui = playerGui:WaitForChild("SettingsGui")
local gameplayGui = playerGui:WaitForChild("GameplayGui")
-- CanvasGroup References
local mainMenuCanvas = mainMenuGui:WaitForChild("CanvasGroup")
local settingsCanvas = settingsGui:WaitForChild("CanvasGroup")
local gameplayCanvas = gameplayGui:WaitForChild("CanvasGroup")
-- Button References
local playButton = mainMenuCanvas:WaitForChild("PlayButton")
local settingsButton = mainMenuCanvas:WaitForChild("SettingsButton")
local backButton = settingsCanvas:WaitForChild("BackButton")
local menuButton = gameplayCanvas:WaitForChild("MenuButton")
-- Define a reusable TweenInfo for our animations
local FADE_INFO = TweenInfo.new(0.3, Enum.EasingStyle.Quad)
-- Define the states and their animated enter/exit callbacks
UiFsm.states = {
MainMenu = {
enter = function()
mainMenuGui.Enabled = true
local tweenIn = TweenService:Create(mainMenuCanvas, FADE_INFO, {GroupTransparency = 0})
tweenIn:Play()
end,
exit = function()
local tweenOut = TweenService:Create(mainMenuCanvas, FADE_INFO, {GroupTransparency = 1})
tweenOut:Play()
tweenOut.Completed:Wait() -- Wait for fade-out to finish
mainMenuGui.Enabled = false
end
},
Settings = {
enter = function()
settingsGui.Enabled = true
local tweenIn = TweenService:Create(settingsCanvas, FADE_INFO, {GroupTransparency = 0})
tweenIn:Play()
end,
exit = function()
local tweenOut = TweenService:Create(settingsCanvas, FADE_INFO, {GroupTransparency = 1})
tweenOut:Play()
tweenOut.Completed:Wait()
settingsGui.Enabled = false
end
},
Gameplay = {
enter = function()
gameplayGui.Enabled = true
local tweenIn = TweenService:Create(gameplayCanvas, FADE_INFO, {GroupTransparency = 0})
tweenIn:Play()
end,
exit = function()
local tweenOut = TweenService:Create(gameplayCanvas, FADE_INFO, {GroupTransparency = 1})
tweenOut:Play()
tweenOut.Completed:Wait()
gameplayGui.Enabled = false
end
}
}
-- Connect button events to FSM transitions
playButton.MouseButton1Click:Connect(function()
UiFsm:transitionTo("Gameplay")
end)
settingsButton.MouseButton1Click:Connect(function()
UiFsm:transitionTo("Settings")
end)
backButton.MouseButton1Click:Connect(function()
UiFsm:transitionTo("MainMenu")
end)
menuButton.MouseButton1Click:Connect(function()
UiFsm:transitionTo("MainMenu")
end)
-- Before starting, make all canvases transparent and disabled
mainMenuCanvas.GroupTransparency = 1
mainMenuGui.Enabled = false
settingsCanvas.GroupTransparency = 1
settingsGui.Enabled = false
gameplayCanvas.GroupTransparency = 1
gameplayGui.Enabled = false
-- Start the FSM
UiFsm:transitionTo("MainMenu")
When you run this code, you should see your UI panels smoothly fade in and out as you click the navigation buttons.
Conclusion
Fantastic work! You have now elevated your UI FSM from a purely logical construct to one that produces a polished, visually appealing user experience.
Key Takeaways:
- FSM
enterandexitcallbacks are the perfect place to manage state-specific animations. TweenServicecan be used to animate UI properties, creating smooth transitions.- The
CanvasGroupobject is an efficient tool for fading an entire UI panel and all of its children. - Using
tween.Completed:Wait()in anexitcallback ensures that animations complete before the FSM moves on, preventing visual glitches.
Our UI FSM is now quite capable, but the code itself—a single large table in a LocalScript—isn't very portable. In the next module, we will begin refactoring our work into a flexible, reusable ModuleScript. This will be the foundation for creating many FSMs throughout your game, for everything from NPC AI to game state management.