Documentation Index Fetch the complete documentation index at: https://mintlify.com/brockmartin/roblox-game-skill/llms.txt
Use this file to discover all available pages before exploring further.
All GUI code in Roblox runs on the client inside LocalScripts. UI objects live under StarterGui at edit time and are cloned into each player’s PlayerGui at runtime. The client owns presentation — responsive layout, animation, input handling — but the server owns all game logic. Never let the client make purchase decisions, grant items, or modify player state directly from a UI event.
GUI Containers
ScreenGui (2D Overlay)
The primary container for all 2D interface elements. Placed in StarterGui; Roblox copies it into each player’s PlayerGui on spawn.
local Players = game : GetService ( "Players" )
local player = Players . LocalPlayer
local playerGui = player : WaitForChild ( "PlayerGui" )
local screenGui = Instance . new ( "ScreenGui" )
screenGui . Name = "MainHUD"
screenGui . ResetOnSpawn = false -- survives respawn
screenGui . DisplayOrder = 10 -- higher renders on top
screenGui . IgnoreGuiInset = true -- extends behind top bar
screenGui . Parent = playerGui
Property Purpose DisplayOrderControls layering. Higher values render on top. ResetOnSpawntrue (default): destroyed and re-cloned on respawn. Set false for persistent UI (shops, settings).IgnoreGuiInsettrue: UI extends behind the top bar area. Use for fullscreen overlays.EnabledToggle visibility without destroying.
BillboardGui (Floating 3D)
Always faces the camera. Used for nametags, damage numbers, and quest markers.
local billboardGui = Instance . new ( "BillboardGui" )
billboardGui . Size = UDim2 . new ( 0 , 200 , 0 , 50 )
billboardGui . StudsOffset = Vector3 . new ( 0 , 3 , 0 ) -- above the part
billboardGui . AlwaysOnTop = false -- occluded by 3D geometry
billboardGui . MaxDistance = 100 -- hides beyond this range
billboardGui . Adornee = workspace . NPC . Head
billboardGui . Parent = workspace . NPC . Head
SurfaceGui (On Part Surfaces)
Renders UI on a Part’s surface. Used for in-world signs, screens, and control panels.
local surfaceGui = Instance . new ( "SurfaceGui" )
surfaceGui . Face = Enum.NormalId.Front
surfaceGui . SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud
surfaceGui . PixelsPerStud = 50
surfaceGui . Parent = workspace . SignPart
-- Also set surfaceGui.Adornee = workspace.SignPart if parented elsewhere
ViewportFrame (3D Content Inside 2D UI)
Renders 3D content inside a 2D GUI element. Used for item previews, character displays, and trophy cases.
local viewport = Instance . new ( "ViewportFrame" )
viewport . Size = UDim2 . new ( 0 , 200 , 0 , 200 )
viewport . BackgroundColor3 = Color3 . fromRGB ( 20 , 20 , 20 )
viewport . Parent = frame
-- Clone a model into the viewport
local previewModel = workspace . SwordModel : Clone ()
previewModel . Parent = viewport
-- Add a camera
local camera = Instance . new ( "Camera" )
camera . CFrame = CFrame . new ( Vector3 . new ( 0 , 2 , 5 ), Vector3 . new ( 0 , 1 , 0 ))
camera . Parent = viewport
viewport . CurrentCamera = camera
Display Order Hierarchy
DisplayOrder 100 -- Modal dialogs (on top of everything)
DisplayOrder 50 -- Notifications / toasts
DisplayOrder 10 -- HUD elements
DisplayOrder 1 -- Background UI
Core UI Elements
Basic Building Blocks
-- Frame: container for grouping and styling
local frame = Instance . new ( "Frame" )
frame . Size = UDim2 . new ( 0.3 , 0 , 0.4 , 0 ) -- 30% width, 40% height
frame . Position = UDim2 . new ( 0.5 , 0 , 0.5 , 0 ) -- centered
frame . AnchorPoint = Vector2 . new ( 0.5 , 0.5 )
frame . BackgroundColor3 = Color3 . fromRGB ( 30 , 30 , 40 )
frame . BackgroundTransparency = 0.1
frame . BorderSizePixel = 0
frame . Parent = screenGui
-- TextButton: cross-platform interactive element
local button = Instance . new ( "TextButton" )
button . Size = UDim2 . new ( 0.5 , 0 , 0 , 50 )
button . Text = "Purchase"
button . TextColor3 = Color3 . fromRGB ( 255 , 255 , 255 )
button . BackgroundColor3 = Color3 . fromRGB ( 0 , 170 , 80 )
button . Font = Enum.Font.GothamBold
button . TextSize = 18
button . Parent = frame
button . Activated : Connect ( function ()
-- Activated works for mouse click, touch tap, and gamepad
end )
Always use Activated instead of MouseButton1Click on buttons. Activated fires on mouse click, touch tap, and gamepad A/B, giving you cross-platform support for free.
Layout Modifiers
Modifier Purpose UIListLayoutArranges children in a vertical or horizontal list UIGridLayoutArranges children in a grid UIPageLayoutSwipeable pages (one child visible at a time) UIPaddingInner padding on a container UICornerRounded corners UIStrokeOutline/border effect UIGradientColor gradient on an element UISizeConstraintMin/max pixel size UIAspectRatioConstraintLocks width/height ratio
-- Rounded corners + stroke + gradient in one block
local corner = Instance . new ( "UICorner" )
corner . CornerRadius = UDim . new ( 0 , 8 )
corner . Parent = frame
local stroke = Instance . new ( "UIStroke" )
stroke . Color = Color3 . fromRGB ( 255 , 255 , 255 )
stroke . Thickness = 2
stroke . Transparency = 0.5
stroke . ApplyStrokeMode = Enum.ApplyStrokeMode.Border
stroke . Parent = frame
local gradient = Instance . new ( "UIGradient" )
gradient . Color = ColorSequence . new (
Color3 . fromRGB ( 50 , 50 , 80 ),
Color3 . fromRGB ( 20 , 20 , 40 )
)
gradient . Rotation = 90
gradient . Parent = frame
Layout Systems
When to Use Each
Scenario Layout Vertical menu, chat log, leaderboard UIListLayoutInventory grid, shop items, emoji picker UIGridLayoutSettings tabs, tutorial slides, shop categories UIPageLayoutHUD element pinned to a corner Absolute positioning (no layout) Overlapping elements (health bar segments) Absolute positioning
UIListLayout
local listLayout = Instance . new ( "UIListLayout" )
listLayout . FillDirection = Enum.FillDirection.Vertical
listLayout . HorizontalAlignment = Enum.HorizontalAlignment.Center
listLayout . VerticalAlignment = Enum.VerticalAlignment.Top
listLayout . SortOrder = Enum.SortOrder.LayoutOrder
listLayout . Padding = UDim . new ( 0 , 8 ) -- gap between items
listLayout . Parent = frame
Children are sorted by their LayoutOrder property (lower values first).
UIGridLayout
local gridLayout = Instance . new ( "UIGridLayout" )
gridLayout . CellSize = UDim2 . new ( 0 , 80 , 0 , 80 )
gridLayout . CellPadding = UDim2 . new ( 0 , 8 , 0 , 8 )
gridLayout . FillDirection = Enum.FillDirection.Horizontal
gridLayout . FillDirectionMaxCells = 4 -- 4 columns, then wrap
gridLayout . SortOrder = Enum.SortOrder.LayoutOrder
gridLayout . HorizontalAlignment = Enum.HorizontalAlignment.Center
gridLayout . Parent = scrollFrame
UIPageLayout
local pageLayout = Instance . new ( "UIPageLayout" )
pageLayout . Animated = true
pageLayout . EasingStyle = Enum.EasingStyle.Quad
pageLayout . EasingDirection = Enum.EasingDirection.InOut
pageLayout . TweenTime = 0.3
pageLayout . Circular = false
pageLayout . Parent = frame
-- Navigate pages
pageLayout : JumpToIndex ( 0 )
pageLayout : Next ()
pageLayout : Previous ()
Responsive Design
Scale vs Offset
UDim2 has two components: Scale (proportion of parent, 0–1) and Offset (fixed pixels).
-- BAD: hardcoded pixels, breaks on different screens
frame . Size = UDim2 . new ( 0 , 400 , 0 , 300 )
frame . Position = UDim2 . new ( 0 , 100 , 0 , 50 )
-- GOOD: proportional, adapts to any screen
frame . Size = UDim2 . new ( 0.3 , 0 , 0.4 , 0 )
frame . Position = UDim2 . new ( 0.5 , 0 , 0.5 , 0 )
frame . AnchorPoint = Vector2 . new ( 0.5 , 0.5 )
Rule: Use Scale for Position and Size. Use Offset only for small fixed elements (icons, padding, border thickness).
Adapting to Screen Size
local camera = workspace . CurrentCamera
local function adaptUI ()
local viewportSize = camera . ViewportSize
local isPortrait = viewportSize . Y > viewportSize . X
local isSmallScreen = viewportSize . X < 600
if isSmallScreen then
frame . Size = UDim2 . new ( 0.95 , 0 , 0.8 , 0 ) -- nearly fullscreen on mobile
elseif isPortrait then
frame . Size = UDim2 . new ( 0.7 , 0 , 0.5 , 0 )
else
frame . Size = UDim2 . new ( 0.3 , 0 , 0.4 , 0 ) -- standard desktop
end
end
camera : GetPropertyChangedSignal ( "ViewportSize" ): Connect ( adaptUI )
adaptUI ()
Animation with TweenService
Common Easing Styles
Style Use Case QuadGeneral-purpose, smooth BackSlight overshoot — bouncy buttons ElasticSpringy, attention-grabbing BounceBouncing effect at the end LinearConstant speed — progress bars SineGentle, subtle motion
Core Animation Recipes
local TweenService = game : GetService ( "TweenService" )
-- Slide in from the right
frame . Position = UDim2 . new ( 1.5 , 0 , 0.5 , 0 )
local slideIn = TweenService : Create ( frame ,
TweenInfo . new ( 0.4 , Enum.EasingStyle.Back , Enum.EasingDirection.Out ), {
Position = UDim2 . new ( 0.5 , 0 , 0.5 , 0 ),
})
slideIn : Play ()
-- Bounce entrance (scale from zero)
local function bounceIn ( element : GuiObject )
element . Size = UDim2 . new ( 0 , 0 , 0 , 0 )
element . AnchorPoint = Vector2 . new ( 0.5 , 0.5 )
local tween = TweenService : Create ( element ,
TweenInfo . new ( 0.5 , Enum.EasingStyle.Back , Enum.EasingDirection.Out ), {
Size = UDim2 . new ( 0.3 , 0 , 0.4 , 0 ),
})
tween : Play ()
return tween
end
-- Fade + scale dismiss
local function dismiss ( element : GuiObject ): RBXScriptSignal
local tween = TweenService : Create ( element ,
TweenInfo . new ( 0.25 , Enum.EasingStyle.Quad , Enum.EasingDirection.In ), {
Size = UDim2 . new ( 0 , 0 , 0 , 0 ),
BackgroundTransparency = 1 ,
})
tween : Play ()
return tween . Completed
end
Chaining Tweens
Use the Completed event to sequence tweens without coroutines:
local step1 = TweenService : Create ( frame , TweenInfo . new ( 0.3 ), {
Position = UDim2 . new ( 0.5 , 0 , 0.5 , 0 ),
})
local step2 = TweenService : Create ( frame , TweenInfo . new ( 0.2 ), {
Size = UDim2 . new ( 0.4 , 0 , 0.5 , 0 ),
})
step1 . Completed : Connect ( function ()
step2 : Play ()
end )
step1 : Play ()
UserInputService vs ContextActionService
Service Best For UserInputServiceGlobal hotkeys, mouse tracking, detecting input device type, custom cursor ContextActionServiceIn-game actions that need mobile buttons, context-sensitive controls GuiButton.ActivatedUI button clicks (already cross-platform)
local UserInputService = game : GetService ( "UserInputService" )
-- Keyboard shortcuts
UserInputService . InputBegan : Connect ( function ( input : InputObject , gameProcessed : boolean )
if gameProcessed then return end -- ignore when typing in TextBox etc.
if input . KeyCode == Enum.KeyCode.E then
toggleInventory ()
elseif input . KeyCode == Enum.KeyCode.Escape then
togglePauseMenu ()
end
end )
-- Detect platform
local isMobile = UserInputService . TouchEnabled and not UserInputService . KeyboardEnabled
local isConsole = UserInputService . GamepadEnabled
local ContextActionService = game : GetService ( "ContextActionService" )
local function onInteract ( actionName : string , inputState : Enum . UserInputState , inputObject : InputObject )
if inputState == Enum.UserInputState.Begin then
interactWithNearestObject ()
end
return Enum.ContextActionResult.Sink
end
-- Bind to E key; auto-creates touch button on mobile
ContextActionService : BindAction ( "Interact" , onInteract , true , Enum.KeyCode.E )
ContextActionService : SetPosition ( "Interact" , UDim2 . new ( 0.8 , 0 , 0.5 , 0 ))
ContextActionService : SetTitle ( "Interact" , "E" )
Complete Health Bar
A smooth health bar with color transitions, a trailing damage indicator, and a low-health pulse effect.
-- LocalScript in StarterPlayerScripts
local Players = game : GetService ( "Players" )
local TweenService = game : GetService ( "TweenService" )
local player = Players . LocalPlayer
local playerGui = player : WaitForChild ( "PlayerGui" )
-- Build the container
local screenGui = Instance . new ( "ScreenGui" )
screenGui . Name = "HealthBarGui"
screenGui . ResetOnSpawn = true
screenGui . DisplayOrder = 5
screenGui . Parent = playerGui
local container = Instance . new ( "Frame" )
container . Name = "HealthBarContainer"
container . Size = UDim2 . new ( 0.25 , 0 , 0 , 28 )
container . Position = UDim2 . new ( 0.5 , 0 , 0.92 , 0 )
container . AnchorPoint = Vector2 . new ( 0.5 , 0.5 )
container . BackgroundColor3 = Color3 . fromRGB ( 20 , 20 , 20 )
container . BorderSizePixel = 0
container . Parent = screenGui
Instance . new ( "UICorner" , container ). CornerRadius = UDim . new ( 0 , 6 )
-- Damage flash layer (red, shrinks slowly behind the fill)
local damageBar = Instance . new ( "Frame" )
damageBar . Name = "DamageBar"
damageBar . Size = UDim2 . new ( 1 , 0 , 1 , 0 )
damageBar . BackgroundColor3 = Color3 . fromRGB ( 200 , 50 , 50 )
damageBar . BorderSizePixel = 0
damageBar . ZIndex = 1
damageBar . Parent = container
-- Main health fill
local fillBar = Instance . new ( "Frame" )
fillBar . Name = "FillBar"
fillBar . Size = UDim2 . new ( 1 , 0 , 1 , 0 )
fillBar . BackgroundColor3 = Color3 . fromRGB ( 0 , 200 , 80 )
fillBar . BorderSizePixel = 0
fillBar . ZIndex = 2
fillBar . Parent = container
-- Color thresholds
local COLOR_HIGH = Color3 . fromRGB ( 0 , 200 , 80 )
local COLOR_MEDIUM = Color3 . fromRGB ( 230 , 180 , 0 )
local COLOR_LOW = Color3 . fromRGB ( 200 , 40 , 40 )
local function getHealthColor ( fraction : number ): Color3
if fraction > 0.6 then
return COLOR_HIGH
elseif fraction > 0.3 then
local t = ( fraction - 0.3 ) / 0.3
return COLOR_MEDIUM : Lerp ( COLOR_HIGH , t )
else
local t = fraction / 0.3
return COLOR_LOW : Lerp ( COLOR_MEDIUM , t )
end
end
local currentTween : Tween ? = nil
local damageTween : Tween ? = nil
local function updateHealthBar ( health : number , maxHealth : number )
local fraction = math.clamp ( health / maxHealth , 0 , 1 )
if currentTween then currentTween : Cancel () end
currentTween = TweenService : Create ( fillBar ,
TweenInfo . new ( 0.4 , Enum.EasingStyle.Quad , Enum.EasingDirection.Out ), {
Size = UDim2 . new ( fraction , 0 , 1 , 0 ),
BackgroundColor3 = getHealthColor ( fraction ),
})
currentTween : Play ()
-- Trailing damage bar shrinks slower
if damageTween then damageTween : Cancel () end
damageTween = TweenService : Create ( damageBar ,
TweenInfo . new ( 0.8 , Enum.EasingStyle.Quad , Enum.EasingDirection.Out ), {
Size = UDim2 . new ( fraction , 0 , 1 , 0 ),
})
task.delay ( 0.3 , function ()
if damageTween then damageTween : Play () end
end )
end
local function onCharacterAdded ( character : Model )
local humanoid = character : WaitForChild ( "Humanoid" )
updateHealthBar ( humanoid . Health , humanoid . MaxHealth )
humanoid . HealthChanged : Connect ( function ( newHealth : number )
updateHealthBar ( newHealth , humanoid . MaxHealth )
end )
end
if player . Character then onCharacterAdded ( player . Character ) end
player . CharacterAdded : Connect ( onCharacterAdded )
Notification Toast System
local TweenService = game : GetService ( "TweenService" )
local Players = game : GetService ( "Players" )
local toastContainer = Instance . new ( "Frame" )
toastContainer . Size = UDim2 . new ( 0.3 , 0 , 0.5 , 0 )
toastContainer . Position = UDim2 . new ( 1 , - 16 , 0 , 16 )
toastContainer . AnchorPoint = Vector2 . new ( 1 , 0 )
toastContainer . BackgroundTransparency = 1
toastContainer . Parent = playerGui : WaitForChild ( "NotificationGui" )
local listLayout = Instance . new ( "UIListLayout" )
listLayout . FillDirection = Enum.FillDirection.Vertical
listLayout . VerticalAlignment = Enum.VerticalAlignment.Top
listLayout . Padding = UDim . new ( 0 , 8 )
listLayout . SortOrder = Enum.SortOrder.LayoutOrder
listLayout . Parent = toastContainer
local function showToast ( message : string , duration : number ? , color : Color3 ? )
duration = duration or 3
color = color or Color3 . fromRGB ( 40 , 40 , 60 )
local toast = Instance . new ( "Frame" )
toast . Size = UDim2 . new ( 1 , 0 , 0 , 50 )
toast . BackgroundColor3 = color
toast . BackgroundTransparency = 1
toast . BorderSizePixel = 0
toast . LayoutOrder = tick ()
toast . Parent = toastContainer
Instance . new ( "UICorner" , toast ). CornerRadius = UDim . new ( 0 , 8 )
local label = Instance . new ( "TextLabel" )
label . Size = UDim2 . new ( 1 , - 24 , 1 , 0 )
label . Position = UDim2 . new ( 0 , 12 , 0 , 0 )
label . Text = message
label . Font = Enum.Font.GothamBold
label . TextSize = 14
label . TextColor3 = Color3 . fromRGB ( 255 , 255 , 255 )
label . TextTransparency = 1
label . TextXAlignment = Enum.TextXAlignment.Left
label . BackgroundTransparency = 1
label . Parent = toast
TweenService : Create ( toast , TweenInfo . new ( 0.3 , Enum.EasingStyle.Back , Enum.EasingDirection.Out ), {
BackgroundTransparency = 0.1 ,
}): Play ()
TweenService : Create ( label , TweenInfo . new ( 0.3 ), { TextTransparency = 0 }): Play ()
task.delay ( duration , function ()
local fadeOut = TweenService : Create ( toast , TweenInfo . new ( 0.3 ), { BackgroundTransparency = 1 })
TweenService : Create ( label , TweenInfo . new ( 0.3 ), { TextTransparency = 1 }): Play ()
fadeOut : Play ()
fadeOut . Completed : Connect ( function () toast : Destroy () end )
end )
end
-- Usage
showToast ( "Quest completed: Defeat 10 Goblins!" )
showToast ( "Not enough coins!" , 4 , Color3 . fromRGB ( 140 , 30 , 30 ))
UI Theme System
Define colors, fonts, and sizes as constants in a shared module. All LocalScripts reference it for visual consistency.
-- UITheme.luau (ModuleScript in ReplicatedStorage)
local Theme = {
Colors = {
Background = Color3 . fromRGB ( 25 , 25 , 35 ),
Surface = Color3 . fromRGB ( 40 , 40 , 55 ),
Primary = Color3 . fromRGB ( 0 , 150 , 70 ),
Danger = Color3 . fromRGB ( 200 , 40 , 40 ),
TextPrimary = Color3 . fromRGB ( 255 , 255 , 255 ),
TextSecondary = Color3 . fromRGB ( 180 , 180 , 190 ),
Accent = Color3 . fromRGB ( 255 , 215 , 0 ),
},
Fonts = {
Title = Enum.Font.GothamBold ,
Body = Enum.Font.Gotham ,
Mono = Enum.Font.RobotoMono ,
},
TextSizes = {
Title = 24 ,
Subtitle = 18 ,
Body = 14 ,
Small = 12 ,
},
CornerRadius = UDim . new ( 0 , 8 ),
Padding = UDim . new ( 0 , 12 ),
}
return Theme
GUI Pooling for Long Lists
For scrolling lists with many items (inventory, leaderboard), reuse GUI elements instead of creating and destroying them every update:
local pool : { Frame } = {}
local function getCard (): Frame
local card = table.remove ( pool )
if not card then
card = createNewCard () -- only create if pool is empty
end
card . Visible = true
return card
end
local function returnCard ( card : Frame )
card . Visible = false
card . Parent = nil
table.insert ( pool , card )
end
Common Mistakes
Never trust client UI for game logic. Always validate purchases, item grants, and state changes on the server. The client’s shop button fires a RemoteEvent; the server decides if the purchase succeeds.
Hardcoded pixel positions
-- BAD: breaks on different resolutions
frame . Position = UDim2 . new ( 0 , 500 , 0 , 300 )
frame . Size = UDim2 . new ( 0 , 400 , 0 , 200 )
-- GOOD: responsive
frame . Position = UDim2 . new ( 0.5 , 0 , 0.5 , 0 )
frame . Size = UDim2 . new ( 0.3 , 0 , 0.25 , 0 )
frame . AnchorPoint = Vector2 . new ( 0.5 , 0.5 )
Creating GUI instances every frame
-- BAD: creates garbage every frame, causes lag
RunService . RenderStepped : Connect ( function ()
local label = Instance . new ( "TextLabel" )
label . Text = "Score: " .. score
label . Parent = screenGui
end )
-- GOOD: update existing element
RunService . RenderStepped : Connect ( function ()
scoreLabel . Text = "Score: " .. score
end )
Not cleaning up event connections
-- BAD: connection persists after UI is removed, leaks memory
button . Activated : Connect ( function ()
doSomething ()
end )
-- GOOD: store and disconnect, or use :Once() for single-fire
local connection = button . Activated : Connect ( function ()
doSomething ()
end )
-- later:
connection : Disconnect ()
Blocking the UI thread with yields
-- BAD: freezes the entire UI
button . Activated : Connect ( function ()
task.wait ( 5 )
label . Text = "Done"
end )
-- GOOD: async work via task.spawn
button . Activated : Connect ( function ()
button . Active = false
task.spawn ( function ()
task.wait ( 5 )
label . Text = "Done"
button . Active = true
end )
end )
Accessibility Checklist
Minimum 14pt body text; 12pt only for tertiary labels
High contrast between text and background
Visual feedback on all interactive elements (hover color, press scale)
Support GuiService:Select() for gamepad/console navigation
Use Activated instead of MouseButton1Click everywhere