ui-ux
π―Skillfrom taozhuo/game-dev-skills
Designs responsive, adaptive UI systems with dynamic screen sizing, safe area handling, and smooth animations for game interfaces.
Installation
npx skills add https://github.com/taozhuo/game-dev-skills --skill ui-uxSkill Details
Implements UI/UX systems including responsive design, animations, input handling, HUD elements, and menu systems. Use when building game interfaces, menus, HUDs, or any user-facing UI.
Overview
# Roblox UI/UX Systems
When implementing UI systems, follow these patterns for responsive and polished interfaces.
Responsive Design
UIAspectRatioConstraint
```lua
-- Maintain aspect ratio regardless of screen size
local frame = Instance.new("Frame")
frame.Size = UDim2.new(0.5, 0, 0.5, 0) -- Will be adjusted
local aspectRatio = Instance.new("UIAspectRatioConstraint")
aspectRatio.AspectRatio = 16/9
aspectRatio.AspectType = Enum.AspectType.FitWithinMaxSize
aspectRatio.DominantAxis = Enum.DominantAxis.Width
aspectRatio.Parent = frame
```
UISizeConstraint
```lua
-- Limit min/max size
local sizeConstraint = Instance.new("UISizeConstraint")
sizeConstraint.MinSize = Vector2.new(100, 50)
sizeConstraint.MaxSize = Vector2.new(500, 300)
sizeConstraint.Parent = frame
```
Screen Size Detection
```lua
local camera = workspace.CurrentCamera
local function getScreenCategory()
local viewportSize = camera.ViewportSize
if viewportSize.X < 600 then
return "mobile"
elseif viewportSize.X < 1200 then
return "tablet"
else
return "desktop"
end
end
local function updateLayout()
local category = getScreenCategory()
if category == "mobile" then
mainFrame.Size = UDim2.new(0.95, 0, 0.9, 0)
fontSize = 14
elseif category == "tablet" then
mainFrame.Size = UDim2.new(0.8, 0, 0.8, 0)
fontSize = 16
else
mainFrame.Size = UDim2.new(0.6, 0, 0.7, 0)
fontSize = 18
end
end
camera:GetPropertyChangedSignal("ViewportSize"):Connect(updateLayout)
```
Safe Area (Notch/Button Handling)
```lua
local GuiService = game:GetService("GuiService")
local function getSafeInsets()
local insets = GuiService:GetGuiInset()
return insets
end
-- Apply safe area padding
local topInset = getSafeInsets()
mainFrame.Position = UDim2.new(0.5, 0, 0, topInset.Y + 10)
```
UI Animation
TweenService for UI
```lua
local TweenService = game:GetService("TweenService")
local function animateIn(frame)
frame.Position = UDim2.new(0.5, 0, 1.5, 0) -- Start below screen
frame.Visible = true
local tween = TweenService:Create(
frame,
TweenInfo.new(0.5, Enum.EasingStyle.Back, Enum.EasingDirection.Out),
{Position = UDim2.new(0.5, 0, 0.5, 0)}
)
tween:Play()
return tween
end
local function animateOut(frame)
local tween = TweenService:Create(
frame,
TweenInfo.new(0.3, Enum.EasingStyle.Back, Enum.EasingDirection.In),
{Position = UDim2.new(0.5, 0, 1.5, 0)}
)
tween:Play()
tween.Completed:Connect(function()
frame.Visible = false
end)
return tween
end
```
Spring Animation
```lua
local function springAnimation(frame, targetSize, stiffness, damping)
stiffness = stiffness or 200
damping = damping or 20
local currentSize = {frame.Size.X.Scale, frame.Size.Y.Scale}
local velocity = {0, 0}
local target = {targetSize.X.Scale, targetSize.Y.Scale}
local conn
conn = RunService.RenderStepped:Connect(function(dt)
for i = 1, 2 do
local displacement = target[i] - currentSize[i]
local springForce = displacement * stiffness
local dampingForce = velocity[i] * damping
local acceleration = springForce - dampingForce
velocity[i] = velocity[i] + acceleration * dt
currentSize[i] = currentSize[i] + velocity[i] * dt
end
frame.Size = UDim2.new(currentSize[1], 0, currentSize[2], 0)
-- Check if settled
local totalVelocity = math.abs(velocity[1]) + math.abs(velocity[2])
local totalDisplacement = math.abs(target[1] - currentSize[1]) + math.abs(target[2] - currentSize[2])
if totalVelocity < 0.001 and totalDisplacement < 0.001 then
frame.Size = targetSize
conn:Disconnect()
end
end)
end
```
Stagger Animation
```lua
local function staggerChildren(parent, delay, animateFunc)
delay = delay or 0.1
local children = parent:GetChildren()
for i, child in ipairs(children) do
task.delay((i - 1) * delay, function()
animateFunc(child)
end)
end
end
-- Usage
staggerChildren(menuFrame, 0.05, function(button)
button.Position = button.Position + UDim2.new(0.1, 0, 0, 0)
button.BackgroundTransparency = 1
TweenService:Create(button, TweenInfo.new(0.3), {
Position = button.Position - UDim2.new(0.1, 0, 0, 0),
BackgroundTransparency = 0
}):Play()
end)
```
Input Handling
Button Interactions
```lua
local function setupButton(button)
local originalSize = button.Size
local originalColor = button.BackgroundColor3
-- Hover
button.MouseEnter:Connect(function()
TweenService:Create(button, TweenInfo.new(0.1), {
Size = originalSize + UDim2.new(0.02, 0, 0.02, 0),
BackgroundColor3 = originalColor:Lerp(Color3.new(1, 1, 1), 0.1)
}):Play()
end)
button.MouseLeave:Connect(function()
TweenService:Create(button, TweenInfo.new(0.1), {
Size = originalSize,
BackgroundColor3 = originalColor
}):Play()
end)
-- Click
button.MouseButton1Down:Connect(function()
TweenService:Create(button, TweenInfo.new(0.05), {
Size = originalSize - UDim2.new(0.01, 0, 0.01, 0)
}):Play()
end)
button.MouseButton1Up:Connect(function()
TweenService:Create(button, TweenInfo.new(0.1), {
Size = originalSize
}):Play()
end)
end
```
Drag and Drop
```lua
local function makeDraggable(frame)
local dragging = false
local dragStart
local startPos
frame.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or
input.UserInputType == Enum.UserInputType.Touch then
dragging = true
dragStart = input.Position
startPos = frame.Position
input.Changed:Connect(function()
if input.UserInputState == Enum.UserInputState.End then
dragging = false
end
end)
end
end)
frame.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement or
input.UserInputType == Enum.UserInputType.Touch then
if dragging then
local delta = input.Position - dragStart
frame.Position = UDim2.new(
startPos.X.Scale,
startPos.X.Offset + delta.X,
startPos.Y.Scale,
startPos.Y.Offset + delta.Y
)
end
end
end)
end
```
Scroll Handling
```lua
local scrollingFrame = Instance.new("ScrollingFrame")
scrollingFrame.Size = UDim2.new(0.5, 0, 0.5, 0)
scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, 0) -- Will be auto-sized
scrollingFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
scrollingFrame.ScrollBarThickness = 8
scrollingFrame.ScrollBarImageColor3 = Color3.fromRGB(100, 100, 100)
-- List layout for automatic sizing
local listLayout = Instance.new("UIListLayout")
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
listLayout.Padding = UDim.new(0, 5)
listLayout.Parent = scrollingFrame
-- Smooth scrolling
local function smoothScrollTo(targetPosition)
local tween = TweenService:Create(
scrollingFrame,
TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
{CanvasPosition = Vector2.new(0, targetPosition)}
)
tween:Play()
end
```
HUD Systems
Health Bar
```lua
local function createHealthBar(parent)
local container = Instance.new("Frame")
container.Size = UDim2.new(0.3, 0, 0, 30)
container.Position = UDim2.new(0.02, 0, 0.05, 0)
container.BackgroundColor3 = Color3.fromRGB(30, 30, 30)
container.Parent = parent
local fill = Instance.new("Frame")
fill.Size = UDim2.new(1, 0, 1, 0)
fill.BackgroundColor3 = Color3.fromRGB(0, 200, 0)
fill.Parent = container
-- Delayed damage indicator
local delayedFill = Instance.new("Frame")
delayedFill.Size = UDim2.new(1, 0, 1, 0)
delayedFill.BackgroundColor3 = Color3.fromRGB(200, 0, 0)
delayedFill.ZIndex = fill.ZIndex - 1
delayedFill.Parent = container
local function updateHealth(current, max)
local ratio = current / max
local targetSize = UDim2.new(ratio, 0, 1, 0)
-- Instant fill update
fill.Size = targetSize
-- Delayed red bar
TweenService:Create(delayedFill, TweenInfo.new(0.5), {
Size = targetSize
}):Play()
-- Color based on health
if ratio > 0.5 then
fill.BackgroundColor3 = Color3.fromRGB(0, 200, 0)
elseif ratio > 0.25 then
fill.BackgroundColor3 = Color3.fromRGB(200, 200, 0)
else
fill.BackgroundColor3 = Color3.fromRGB(200, 0, 0)
end
end
return {container = container, update = updateHealth}
end
```
Minimap
```lua
local function createMinimap(parent, worldSize, minimapSize)
local container = Instance.new("Frame")
container.Size = UDim2.new(0, minimapSize, 0, minimapSize)
container.Position = UDim2.new(1, -minimapSize - 10, 0, 10)
container.BackgroundColor3 = Color3.fromRGB(20, 20, 20)
container.ClipsDescendants = true
container.Parent = parent
-- Player marker
local playerMarker = Instance.new("Frame")
playerMarker.Size = UDim2.new(0, 10, 0, 10)
playerMarker.AnchorPoint = Vector2.new(0.5, 0.5)
playerMarker.BackgroundColor3 = Color3.fromRGB(0, 200, 0)
playerMarker.Parent = container
Instance.new("UICorner", playerMarker).CornerRadius = UDim.new(1, 0)
local function worldToMinimap(worldPos)
local x = (worldPos.X / worldSize + 0.5) * minimapSize
local y = (worldPos.Z / worldSize + 0.5) * minimapSize
return UDim2.new(0, x, 0, y)
end
local function update()
local character = Players.LocalPlayer.Character
if not character then return end
local pos = character.PrimaryPart.Position
playerMarker.Position = worldToMinimap(pos)
-- Rotate marker based on facing
local lookVector = character.PrimaryPart.CFrame.LookVector
local angle = math.atan2(lookVector.X, lookVector.Z)
playerMarker.Rotation = math.deg(angle)
end
RunService.RenderStepped:Connect(update)
return container
end
```
Cooldown Indicator
```lua
local function createCooldownIndicator(button, duration)
local overlay = Instance.new("Frame")
overlay.Size = UDim2.new(1, 0, 1, 0)
overlay.BackgroundColor3 = Color3.new(0, 0, 0)
overlay.BackgroundTransparency = 0.5
overlay.ZIndex = button.ZIndex + 1
overlay.Parent = button
local gradient = Instance.new("UIGradient")
gradient.Rotation = -90
gradient.Transparency = NumberSequence.new({
NumberSequenceKeypoint.new(0, 0),
NumberSequenceKeypoint.new(0.5, 0),
NumberSequenceKeypoint.new(0.501, 1),
NumberSequenceKeypoint.new(1, 1)
})
gradient.Parent = overlay
local cooldownText = Instance.new("TextLabel")
cooldownText.Size = UDim2.new(1, 0, 1, 0)
cooldownText.BackgroundTransparency = 1
cooldownText.TextColor3 = Color3.new(1, 1, 1)
cooldownText.TextScaled = true
cooldownText.ZIndex = overlay.ZIndex + 1
cooldownText.Parent = overlay
local startTime = os.clock()
local conn
conn = RunService.RenderStepped:Connect(function()
local elapsed = os.clock() - startTime
local remaining = duration - elapsed
if remaining <= 0 then
overlay:Destroy()
conn:Disconnect()
return
end
-- Update sweep
local progress = elapsed / duration
gradient.Offset = Vector2.new(0, progress * 2 - 1)
-- Update text
cooldownText.Text = string.format("%.1f", remaining)
end)
end
```
Menu Systems
Page Navigation
```lua
local MenuController = {}
MenuController.pageStack = {}
function MenuController.pushPage(pageName)
local currentPage = MenuController.pageStack[#MenuController.pageStack]
if currentPage then
currentPage.Visible = false
end
local newPage = Pages[pageName]
newPage.Visible = true
table.insert(MenuController.pageStack, newPage)
end
function MenuController.popPage()
if #MenuController.pageStack <= 1 then return end
local currentPage = table.remove(MenuController.pageStack)
currentPage.Visible = false
local previousPage = MenuController.pageStack[#MenuController.pageStack]
previousPage.Visible = true
end
function MenuController.goToPage(pageName)
-- Clear stack and go to specific page
for _, page in ipairs(MenuController.pageStack) do
page.Visible = false
end
MenuController.pageStack = {}
MenuController.pushPage(pageName)
end
-- Back button
backButton.MouseButton1Click:Connect(function()
MenuController.popPage()
end)
```
Modal Dialog
```lua
local function showModal(title, message, buttons)
-- Darken background
local backdrop = Instance.new("Frame")
backdrop.Size = UDim2.new(1, 0, 1, 0)
backdrop.BackgroundColor3 = Color3.new(0, 0, 0)
backdrop.BackgroundTransparency = 0.5
backdrop.ZIndex = 100
backdrop.Parent = ScreenGui
local modal = Instance.new("Frame")
modal.Size = UDim2.new(0.4, 0, 0.3, 0)
modal.Position = UDim2.new(0.5, 0, 0.5, 0)
modal.AnchorPoint = Vector2.new(0.5, 0.5)
modal.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
modal.ZIndex = 101
modal.Parent = backdrop
local titleLabel = Instance.new("TextLabel")
titleLabel.Text = title
titleLabel.Size = UDim2.new(1, 0, 0.2, 0)
titleLabel.BackgroundTransparency = 1
titleLabel.TextColor3 = Color3.new(1, 1, 1)
titleLabel.TextScaled = true
titleLabel.Parent = modal
local messageLabel = Instance.new("TextLabel")
messageLabel.Text = message
messageLabel.Size = UDim2.new(0.9, 0, 0.4, 0)
messageLabel.Position = UDim2.new(0.05, 0, 0.25, 0)
messageLabel.BackgroundTransparency = 1
messageLabel.TextColor3 = Color3.new(0.8, 0.8, 0.8)
messageLabel.TextWrapped = true
messageLabel.Parent = modal
local buttonContainer = Instance.new("Frame")
buttonContainer.Size = UDim2.new(0.9, 0, 0.25, 0)
buttonContainer.Position = UDim2.new(0.05, 0, 0.7, 0)
buttonContainer.BackgroundTransparency = 1
buttonContainer.Parent = modal
local listLayout = Instance.new("UIListLayout")
listLayout.FillDirection = Enum.FillDirection.Horizontal
listLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
listLayout.Padding = UDim.new(0, 10)
listLayout.Parent = buttonContainer
local result = nil
for _, buttonData in ipairs(buttons) do
local btn = Instance.new("TextButton")
btn.Size = UDim2.new(0, 100, 1, 0)
btn.Text = buttonData.text
btn.BackgroundColor3 = buttonData.color or Color3.fromRGB(60, 60, 60)
btn.TextColor3 = Color3.new(1, 1, 1)
btn.Parent = buttonContainer
btn.MouseButton1Click:Connect(function()
result = buttonData.value
backdrop:Destroy()
end)
end
-- Wait for result
while not result and backdrop.Parent do
task.wait()
end
return result
end
-- Usage
local choice = showModal("Confirm", "Are you sure?", {
{text = "Yes", value = true, color = Color3.fromRGB(0, 150, 0)},
{text = "No", value = false, color = Color3.fromRGB(150, 0, 0)}
})
```
More from this repository10
Manages audio systems with advanced sound pooling, priority management, and performance optimization for immersive game sound experiences.
Generates dynamic visual effects like particle systems, camera shakes, and impact animations for enhancing game experiences in Roblox.
animation-system skill from taozhuo/game-dev-skills
Implements robust game systems like inventory, shops, trading, and progression mechanics for Roblox RPGs with secure, stackable item management.
optimization skill from taozhuo/game-dev-skills
security-anticheat skill from taozhuo/game-dev-skills
Enables comprehensive combat mechanics with advanced hitbox detection, damage calculation, and hit validation for creating dynamic player-vs-player and player-vs-NPC combat interactions.
Provides expert guidance on navigating Roblox-specific API behaviors, performance optimizations, and common coding pitfalls in Lua scripting.
Generates conceptual game art images using Gemini AI, providing precise style-specific prompts for character, environment, and UI design across various game aesthetics.
Optimizes Roblox multiplayer networking with efficient event batching, delta compression, and rate limiting for smooth gameplay.