<!-- Source: brockmartin/roblox-game-skill (MIT) -->
<!-- 来源:brockmartin/roblox-game-skill(MIT协议) -->
Roblox GUI/UI Systems Reference
Roblox GUI/UI系统参考文档
Load this reference when working on any UI-related task in Roblox:
- Building menus (main menu, pause menu, settings)
- HUDs (health bars, minimaps, ammo counters, score displays)
- Shops and inventory screens
- Notification and toast systems
- Dialog and popup windows
- Any 2D or 3D-attached interface elements
All GUI code runs on the
client (LocalScripts). UI objects live under
at edit time and are cloned into each player's
at runtime.
在Roblox中处理任何UI相关任务时,请参考本文档:
- 构建菜单(主菜单、暂停菜单、设置菜单)
- 抬头显示(HUD)(生命值条、小地图、弹药计数器、分数显示)
- 商店与库存界面
- 通知与提示系统
- 对话框与弹窗
- 任何2D或依附于3D物体的界面元素
所有GUI代码均在
客户端(LocalScripts)运行。UI对象在编辑阶段位于
下,运行时会被克隆到每个玩家的
中。
Load Full Reference below only when you need specific layout examples or implementation patterns.
Key rules:
- Mobile-first: design for phone, scale up. Touch targets minimum 48x48px.
- Scale (0-1 proportional) for position/size. Offset only for fixed padding/icons.
- Container Frame Rule: every logical group gets a Frame with layout modifier inside.
- UIListLayout/UIGridLayout: set on parent Frame, children auto-arrange. AutomaticSize on parent.
- ScreenGui.ResetOnSpawn = false for persistent UI. IgnoreGuiInset = true for fullscreen.
- ZIndex for layering within same ScreenGui. DisplayOrder for ScreenGui priority.
- Never use absolute pixel sizes for main containers. UISizeConstraint for min/max bounds.
- ScrollingFrame: set CanvasSize or AutomaticCanvasSize. UIListLayout inside for content.
- Common AI mistake: forgetting to set LayoutOrder on children when using layout modifiers.
- For complex stateful UI (shops, inventories, settings), consider reactive frameworks like Fusion (dphfox/Fusion, MIT) or React-Lua (jsdotlua/react).
仅当需要特定布局示例或实现模式时,才查看下方完整参考内容。
核心规则:
- 移动优先:先为手机设计,再向上适配。触摸目标最小为48×48px。
- 位置/尺寸使用比例(Scale,0-1之间的比例值)。仅固定内边距/图标使用偏移(Offset)。
- 容器框架规则:每个逻辑组都要创建一个Frame,并在内部添加布局修饰器。
- UIListLayout/UIGridLayout:添加到父Frame上,子元素会自动排列。开启父元素的AutomaticSize。
- 持久化UI需设置ScreenGui.ResetOnSpawn = false。全屏UI需设置IgnoreGuiInset = true。
- 同一ScreenGui内的层级使用ZIndex控制。ScreenGui的优先级使用DisplayOrder控制。
- 主容器绝不要使用绝对像素尺寸。使用UISizeConstraint设置最小/最大边界。
- ScrollingFrame:设置CanvasSize或AutomaticCanvasSize,内部添加UIListLayout来管理内容。
- AI常见错误:使用布局修饰器时,忘记为子元素设置LayoutOrder。
- 对于复杂的有状态UI(商店、库存、设置),可考虑使用响应式框架如Fusion(dphfox/Fusion,MIT协议)或React-Lua(jsdotlua/react)。
<!-- Guidelines sourced from Roblox DevForum, official docs, and community standards -->
<!-- 指南来源:Roblox开发者论坛、官方文档及社区标准 -->
Container Frame Rule
容器框架规则
<!-- Source: epochzx, Roblox DevForum -->
Never place UI elements directly under a ScreenGui. Always create a transparent Container Frame as the first child with
and
BackgroundTransparency = 1
. Its
always matches screen resolution.
luau
local container = Instance.new("Frame")
container.Name = "Container"
container.Size = UDim2.new(1, 0, 1, 0)
container.BackgroundTransparency = 1
container.BorderSizePixel = 0
container.Parent = screenGui
-- All UI children go under container, not directly under screenGui
<!-- 来源:epochzx,Roblox开发者论坛 -->
绝不要将UI元素直接放在ScreenGui下。 始终创建一个透明的容器Frame作为第一个子元素,设置
和
BackgroundTransparency = 1
。它的
始终与屏幕分辨率匹配。
luau
local container = Instance.new("Frame")
container.Name = "Container"
container.Size = UDim2.new(1, 0, 1, 0)
container.BackgroundTransparency = 1
container.BorderSizePixel = 0
container.Parent = screenGui
-- 所有UI子元素都放在container下,而非直接放在screenGui下
Scale vs Offset
比例(Scale)与偏移(Offset)
<!-- Source: uiuxartist (Roblox Staff), DevForum -->
- Scale = percentage of parent (responsive). Use for Size and Position.
- Offset = fixed pixels. Use for pixel-perfect icons, small graphics, UIStroke.
- UIStroke does NOT support Scale - only Offset.
- UICorner DOES support Scale ( = 50% radius).
- Hybrid pattern: start pure Scale, add Offset for minimum size, reduce Scale.
luau
-- Scale for responsive sizing
frame.Size = UDim2.new(0.5, 0, 0, 40) -- 50% width, 40px height
-- Offset for UIStroke (Scale not supported)
stroke.Thickness = 2 -- always Offset
-- UICorner supports Scale
corner.CornerRadius = UDim.new(0, 8) -- Offset
corner.CornerRadius = UDim.new(0.5, 0) -- Scale (50% of smallest axis)
<!-- 来源:uiuxartist(Roblox员工),开发者论坛 -->
- Scale = 父元素的百分比(响应式)。用于尺寸(Size)和位置(Position)。
- Offset = 固定像素值。用于像素级精确的图标、小型图形、UIStroke。
- UIStroke 不支持Scale,仅支持Offset。
- UICorner 支持Scale( = 50%圆角半径)。
- 混合模式:先使用纯Scale,再添加Offset设置最小尺寸,调整Scale值。
luau
-- 响应式尺寸使用Scale
frame.Size = UDim2.new(0.5, 0, 0, 40) -- 宽度50%,高度40px
-- UIStroke使用Offset(不支持Scale)
stroke.Thickness = 2 -- 始终为Offset
-- UICorner支持Scale
corner.CornerRadius = UDim.new(0, 8) -- Offset
corner.CornerRadius = UDim.new(0.5, 0) -- Scale(最小轴的50%)
Mobile-First Principles
移动优先原则
<!-- Source: Roblox official docs - Adaptive Design Guidelines -->
<!-- https://create.roblox.com/docs/production/publishing/adaptive-design -->
- 50%+ of Roblox players are on mobile. Design touch-first.
- Minimum touch target: ~0.15 width scale (≈44-48px).
- Account for notches via .
- Don't place UI in the top 58px (Roblox top bar) or bottom virtual controls.
- Test with Device Emulator before publishing (View → Device Emulator).
<!-- 来源:Roblox官方文档 - 自适应设计指南 -->
<!-- https://create.roblox.com/docs/production/publishing/adaptive-design -->
- 超过50%的Roblox玩家使用移动设备。 优先为触摸操作设计。
- 最小触摸目标:约0.15宽度比例(≈44-48px)。
- 通过适配刘海屏。
- 不要将UI放在顶部58px(Roblox顶部栏)或底部虚拟控制区域。
- 发布前使用设备模拟器测试(视图 → 设备模拟器)。
<!-- Source: PictureFolder, Roblox DevForum (119 likes) -->
<!-- "Designing UI - Tips and Best Practices" -->
- Two fonts max: Display (headers/buttons) + Body (descriptions).
- Gotham is the de facto modern Roblox font.
- NEVER pure white () on pure black (). Use off-white () on dark gray ().
- Size hierarchy: bigger/bolder = more important.
<!-- 来源:PictureFolder,Roblox开发者论坛(获119个赞) -->
<!-- 《UI设计 - 技巧与最佳实践》 -->
- 最多使用两种字体:标题字体(标题/按钮)+ 正文字体(描述)。
- Gotham是当前Roblox的主流现代字体。
- 绝对不要在纯黑色()背景上使用纯白色()文字。使用米白色()搭配深灰色()。
- 尺寸层级:越大/越粗的文字,重要性越高。
<!-- Source: DevForum "Modern UI Colour Schemes" -->
- Dark palette: (Modern Black) to (Very Light). Don't go lighter than 35.
- Grey base + accent colors for interactive elements.
- Pick a palette and stick to it. Consistency > variety.
<!-- 来源:开发者论坛《现代UI配色方案》 -->
- 深色调色板:(现代黑)到(极浅灰)。不要使用比35更浅的颜色。
- 灰色底色 + 强调色用于交互元素。
- 选定一套调色板并坚持使用。一致性 > 多样性。
Common AI UI Mistakes
AI常见UI错误
| Mistake | Fix |
|---|
| Elements directly under ScreenGui | Use a Container Frame child |
| Pure Scale only | Too small on mobile - add Offset minimums |
| Pure Offset only | Breaks on different resolutions |
| Pure white on pure black text | Use off-white on dark gray |
| Ignoring mobile players | 50%+ are mobile; design touch-first |
| Text-heavy UI for young audiences | Use icons, images, minimal text |
| UI overlapping top bar / chat / leaderboard | Respect safe areas (top 58px, bottom 100px) |
| Not testing with Device Emulator | Always test before publishing |
| 错误 | 修复方案 |
|---|
| 元素直接放在ScreenGui下 | 使用Container Frame作为子容器 |
| 仅使用纯Scale | 在移动设备上过小 - 添加Offset最小尺寸 |
| 仅使用纯Offset | 在不同分辨率下会失效 |
| 纯黑背景配纯白文字 | 使用米白色搭配深灰色 |
| 忽略移动玩家 | 超过50%的玩家使用移动设备;优先为触摸操作设计 |
| 面向低龄用户的文字密集型UI | 使用图标、图片,减少文字 |
| UI与顶部栏/聊天框/排行榜重叠 | 预留安全区域(顶部58px,底部100px) |
| 未使用设备模拟器测试 | 发布前务必测试 |
ScreenGui (2D Overlay)
ScreenGui(2D覆盖层)
The primary container for all 2D UI. Placed in
; Roblox copies it into each player's
on spawn.
luau
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
Key Properties:
| Property | Purpose |
|---|
| Controls layering. Higher values render on top of lower values. |
| (default): destroyed and re-cloned on respawn. Set to for persistent UI (shops, settings). |
| : UI extends behind the top bar (CoreGui area). Use for fullscreen overlays. |
| Toggle visibility without destroying. |
所有2D UI的主容器。放置在
中;Roblox会在玩家生成时将其复制到每个玩家的
中。
luau
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 -- 重生后保留
screenGui.DisplayOrder = 10 -- 值越大,渲染层级越高
screenGui.IgnoreGuiInset = true -- 延伸到顶部栏后方
screenGui.Parent = playerGui
核心属性:
| 属性 | 用途 |
|---|
| 控制层级。值越大,渲染在值较小的元素上方。 |
| (默认):重生时销毁并重新克隆。设置为用于持久化UI(商店、设置)。 |
| :UI延伸到顶部栏(CoreGui区域)后方。用于全屏覆盖层。 |
| 切换可见性,无需销毁元素。 |
SurfaceGui (On Part Surfaces)
SurfaceGui(依附于部件表面)
Renders UI on a Part's surface. Used for in-world signs, screens, control panels.
luau
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
在Part的表面渲染UI。用于游戏内标识、屏幕、控制面板。
luau
local surfaceGui = Instance.new("SurfaceGui")
surfaceGui.Face = Enum.NormalId.Front
surfaceGui.SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud
surfaceGui.PixelsPerStud = 50
surfaceGui.Parent = workspace.SignPart
-- 如果放在其他位置,需设置surfaceGui.Adornee = workspace.SignPart
BillboardGui (Floating in 3D)
BillboardGui(悬浮在3D空间)
Always faces the camera. Used for nametags, damage numbers, quest markers.
luau
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
始终面向相机。用于姓名标签、伤害数值、任务标记。
luau
local billboardGui = Instance.new("BillboardGui")
billboardGui.Size = UDim2.new(0, 200, 0, 50)
billboardGui.StudsOffset = Vector3.new(0, 3, 0) -- 在部件上方
billboardGui.AlwaysOnTop = false -- 会被3D几何体遮挡
billboardGui.MaxDistance = 100 -- 超出该距离后隐藏
billboardGui.Adornee = workspace.NPC.Head
billboardGui.Parent = workspace.NPC.Head
Display Order Hierarchy
DisplayOrder层级
DisplayOrder 100 -- Modal dialogs (on top of everything)
DisplayOrder 50 -- Notifications / toasts
DisplayOrder 10 -- HUD elements
DisplayOrder 1 -- Background UI
DisplayOrder 100 -- 模态对话框(最顶层)
DisplayOrder 50 -- 通知/提示框
DisplayOrder 10 -- HUD元素
DisplayOrder 1 -- 背景UI
3. Core UI Elements
3. 核心UI元素
Container for grouping and styling. No text or image by default.
luau
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 (with AnchorPoint)
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
用于分组和样式的容器。默认无文字或图片。
luau
local frame = Instance.new("Frame")
frame.Size = UDim2.new(0.3, 0, 0.4, 0) -- 宽度30%,高度40%
frame.Position = UDim2.new(0.5, 0, 0.5, 0) -- 居中(需配合AnchorPoint)
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
TextLabel / TextButton
TextLabel / TextButton
luau
local label = Instance.new("TextLabel")
label.Size = UDim2.new(1, 0, 0, 40)
label.Text = "Score: 0"
label.TextColor3 = Color3.fromRGB(255, 255, 255)
label.TextScaled = true
label.Font = Enum.Font.GothamBold
label.BackgroundTransparency = 1
label.Parent = frame
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)
Use
instead of
for cross-platform support.
luau
local label = Instance.new("TextLabel")
label.Size = UDim2.new(1, 0, 0, 40)
label.Text = "分数: 0"
label.TextColor3 = Color3.fromRGB(255, 255, 255)
label.TextScaled = true
label.Font = Enum.Font.GothamBold
label.BackgroundTransparency = 1
label.Parent = frame
local button = Instance.new("TextButton")
button.Size = UDim2.new(0.5, 0, 0, 50)
button.Text = "购买"
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支持鼠标点击、触摸点击和手柄操作
end)
ImageLabel / ImageButton
ImageLabel / ImageButton
luau
local icon = Instance.new("ImageLabel")
icon.Size = UDim2.new(0, 64, 0, 64)
icon.Image = "rbxassetid://123456789"
icon.ScaleType = Enum.ScaleType.Fit
icon.BackgroundTransparency = 1
icon.Parent = frame
luau
local icon = Instance.new("ImageLabel")
icon.Size = UDim2.new(0, 64, 0, 64)
icon.Image = "rbxassetid://123456789"
icon.ScaleType = Enum.ScaleType.Fit
icon.BackgroundTransparency = 1
icon.Parent = frame
ScrollingFrame
ScrollingFrame
See section 14 (ScrollingFrame Patterns) for full coverage including AutomaticCanvasSize, UIListLayout integration, and elastic overscroll.
完整内容请查看第14节(ScrollingFrame模式),包括AutomaticCanvasSize、UIListLayout集成和弹性滚动效果。
ViewportFrame
ViewportFrame
Renders 3D content inside a 2D GUI (item previews, character displays).
luau
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
在2D GUI中渲染3D内容(物品预览、角色展示)。
luau
local viewport = Instance.new("ViewportFrame")
viewport.Size = UDim2.new(0, 200, 0, 200)
viewport.BackgroundColor3 = Color3.fromRGB(20, 20, 20)
viewport.Parent = frame
-- 将模型克隆到viewport中
local previewModel = workspace.SwordModel:Clone()
previewModel.Parent = viewport
-- 添加相机
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
| Modifier | Purpose |
|---|
| Arranges children in a vertical or horizontal list |
| Arranges children in a grid |
| Swipeable pages (one child visible at a time) |
| Inner padding on a container |
| Rounded corners |
| Outline/border effect |
| Color gradient on an element |
| Min/max pixel size |
| Locks width/height ratio |
luau
-- Rounded corners
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0, 8)
corner.Parent = frame
-- Stroke/border
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
-- Gradient
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
-- Padding
local padding = Instance.new("UIPadding")
padding.PaddingLeft = UDim.new(0, 12)
padding.PaddingRight = UDim.new(0, 12)
padding.PaddingTop = UDim.new(0, 12)
padding.PaddingBottom = UDim.new(0, 12)
padding.Parent = frame
| 修饰器 | 用途 |
|---|
| 将子元素按垂直或水平列表排列 |
| 将子元素按网格排列 |
| 可滑动页面(一次仅显示一个子元素) |
| 容器的内边距 |
| 圆角效果 |
| 描边/边框效果 |
| 元素的颜色渐变 |
| 最小/最大像素尺寸 |
| 锁定宽高比 |
luau
-- 圆角
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
-- 内边距
local padding = Instance.new("UIPadding")
padding.PaddingLeft = UDim.new(0, 12)
padding.PaddingRight = UDim.new(0, 12)
padding.PaddingTop = UDim.new(0, 12)
padding.PaddingBottom = UDim.new(0, 12)
padding.Parent = frame
Arranges children sequentially. Best for menus, sidebars, chat messages, vertical/horizontal lists.
luau
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
property (lower values first).
按顺序排列子元素。最适合菜单、侧边栏、聊天消息、垂直/水平列表。
luau
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) -- 元素间间距
listLayout.Parent = frame
Arranges children in rows and columns. Best for inventories, shops, icon grids.
luau
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
将子元素按行列排列。最适合库存、商店、图标网格。
luau
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列后换行
gridLayout.SortOrder = Enum.SortOrder.LayoutOrder
gridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
gridLayout.Parent = scrollFrame
Shows one child at a time with animated page transitions. Best for tabbed menus, tutorials, onboarding flows.
luau
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 -- cannot loop from last to first
pageLayout.Padding = UDim.new(0, 0)
pageLayout.Parent = frame
-- Navigate pages
pageLayout:JumpToIndex(0)
pageLayout:Next()
pageLayout:Previous()
pageLayout:JumpTo(someChildFrame)
一次显示一个子元素,支持动画页面切换。最适合标签菜单、教程、引导流程。
luau
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.Padding = UDim.new(0, 0)
pageLayout.Parent = frame
-- 页面导航
pageLayout:JumpToIndex(0)
pageLayout:Next()
pageLayout:Previous()
pageLayout:JumpTo(someChildFrame)
| Scenario | Layout |
|---|
| Vertical menu, chat log, leaderboard | |
| Inventory grid, shop items, emoji picker | |
| Settings tabs, tutorial slides, shop categories | |
| HUD element pinned to a corner | Absolute positioning (no layout) |
| Overlapping elements (health bar segments) | Absolute positioning |
| 场景 | 布局方式 |
|---|
| 垂直菜单、聊天记录、排行榜 | |
| 库存网格、商店物品、表情选择器 | |
| 设置标签页、教程幻灯片、商店分类 | |
| 固定在屏幕角落的HUD元素 | 绝对定位(不使用布局) |
| 重叠元素(生命值条分段) | 绝对定位 |
Absolute vs Layout-Driven
绝对定位 vs 布局驱动
- Absolute positioning: Set and directly. Use for HUD elements pinned to specific screen locations.
- Layout-driven: Add a layout object as a child. The layout overrides children's . Use for dynamic lists where content count changes.
You can nest both: a frame with absolute position containing children managed by a UIListLayout.
- 绝对定位:直接设置和。用于固定在特定屏幕位置的HUD元素。
- 布局驱动:添加布局对象作为子元素。布局会覆盖子元素的。用于内容数量动态变化的列表。
两种方式可以嵌套使用:一个绝对定位的Frame,内部包含由UIListLayout管理的子元素。
5. Responsive Design
5. 响应式设计
Scale vs Offset rules are in Design Guidelines above. This section covers constraints and adaptive patterns.
比例与偏移规则见上方设计指南。本节介绍约束与自适应模式。
UISizeConstraint
UISizeConstraint
Prevents elements from becoming too small on phones or too large on ultrawide monitors.
luau
local sizeConstraint = Instance.new("UISizeConstraint")
sizeConstraint.MinSize = Vector2.new(200, 150)
sizeConstraint.MaxSize = Vector2.new(600, 450)
sizeConstraint.Parent = frame
防止元素在手机上过小,或在超宽显示器上过大。
luau
local sizeConstraint = Instance.new("UISizeConstraint")
sizeConstraint.MinSize = Vector2.new(200, 150)
sizeConstraint.MaxSize = Vector2.new(600, 450)
sizeConstraint.Parent = frame
UIAspectRatioConstraint
UIAspectRatioConstraint
Locks an element's aspect ratio so it does not stretch.
luau
local aspect = Instance.new("UIAspectRatioConstraint")
aspect.AspectRatio = 16 / 9
aspect.AspectType = Enum.AspectType.FitWithinMaxSize
aspect.DominantAxis = Enum.DominantAxis.Width
aspect.Parent = frame
锁定元素的宽高比,防止拉伸变形。
luau
local aspect = Instance.new("UIAspectRatioConstraint")
aspect.AspectRatio = 16 / 9
aspect.AspectType = Enum.AspectType.FitWithinMaxSize
aspect.DominantAxis = Enum.DominantAxis.Width
aspect.Parent = frame
Adapting to Screen Size
适配屏幕尺寸
luau
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()
luau
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) -- 移动端几乎全屏
elseif isPortrait then
frame.Size = UDim2.new(0.7, 0, 0.5, 0)
else
frame.Size = UDim2.new(0.3, 0, 0.4, 0) -- 标准桌面端尺寸
end
end
camera:GetPropertyChangedSignal("ViewportSize"):Connect(adaptUI)
adaptUI()
6. Animation with TweenService
6. 使用TweenService实现动画
luau
local TweenService = game:GetService("TweenService")
-- TweenInfo.new(time, easingStyle, easingDirection, repeatCount, reverses, delayTime)
local tweenInfo = TweenInfo.new(
0.5, -- duration in seconds
Enum.EasingStyle.Quad, -- easing curve
Enum.EasingDirection.Out, -- direction
0, -- repeat count (0 = no repeat, -1 = infinite)
false, -- reverses
0 -- delay before starting
)
Common Easing Styles:
| Style | Use Case |
|---|
| General-purpose, smooth |
| Slight overshoot, bouncy buttons |
| Springy, attention-grabbing |
| Bouncing effect at the end |
| Constant speed, progress bars |
| Gentle, subtle motion |
| Dramatic acceleration/deceleration |
luau
local TweenService = game:GetService("TweenService")
-- TweenInfo.new(时长, 缓动样式, 缓动方向, 重复次数, 是否反转, 延迟时间)
local tweenInfo = TweenInfo.new(
0.5, -- 时长(秒)
Enum.EasingStyle.Quad, -- 缓动曲线
Enum.EasingDirection.Out, -- 缓动方向
0, -- 重复次数(0=不重复,-1=无限重复)
false, -- 是否反转
0 -- 启动前延迟时间
)
常用缓动样式:
| 样式 | 使用场景 |
|---|
| 通用场景,平滑过渡 |
| 轻微过冲,适用于弹性按钮 |
| 弹簧效果,吸引注意力 |
| 结束时弹跳效果 |
| 恒定速度,适用于进度条 |
| 柔和、微妙的运动 |
| 剧烈的加速/减速 |
Tweening UI Properties
UI属性动画
luau
-- 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()
-- Fade in
frame.BackgroundTransparency = 1
local fadeIn = TweenService:Create(frame, TweenInfo.new(0.3), {
BackgroundTransparency = 0,
})
fadeIn:Play()
-- Color transition
local colorShift = TweenService:Create(frame, TweenInfo.new(0.5), {
BackgroundColor3 = Color3.fromRGB(255, 50, 50),
})
colorShift:Play()
-- Size pulse
local pulse = TweenService:Create(button, TweenInfo.new(0.6, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true), {
Size = UDim2.new(0.55, 0, 0, 55),
})
pulse:Play()
luau
-- 从右侧滑入
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()
-- 淡入
frame.BackgroundTransparency = 1
local fadeIn = TweenService:Create(frame, TweenInfo.new(0.3), {
BackgroundTransparency = 0,
})
fadeIn:Play()
-- 颜色过渡
local colorShift = TweenService:Create(frame, TweenInfo.new(0.5), {
BackgroundColor3 = Color3.fromRGB(255, 50, 50),
})
colorShift:Play()
-- 尺寸脉动
local pulse = TweenService:Create(button, TweenInfo.new(0.6, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true), {
Size = UDim2.new(0.55, 0, 0, 55),
})
pulse:Play()
luau
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()
luau
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()
Common UI Animation Recipes
常见UI动画示例
luau
-- Bounce entrance
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
luau
-- 弹跳入场
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
-- 淡入+缩放退场
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
UserInputService
UserInputService
Low-level input detection. Best for keyboard shortcuts, mouse tracking, detecting input type.
luau
local UserInputService = game:GetService("UserInputService")
-- Keyboard input
UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessed: boolean)
if gameProcessed then return end -- ignore if typing in a TextBox, etc.
if input.KeyCode == Enum.KeyCode.E then
toggleInventory()
elseif input.KeyCode == Enum.KeyCode.Escape then
togglePauseMenu()
end
end)
-- Mouse position
local mousePos = UserInputService:GetMouseLocation() -- Vector2
-- Detect platform
local isMobile = UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled
local isConsole = UserInputService.GamepadEnabled
-- Hide mouse cursor
UserInputService.MouseIconEnabled = false
低级别输入检测。最适合键盘快捷键、鼠标跟踪、检测输入类型。
luau
local UserInputService = game:GetService("UserInputService")
-- 键盘输入
UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessed: boolean)
if gameProcessed then return end -- 如果正在输入文本框等,忽略
if input.KeyCode == Enum.KeyCode.E then
toggleInventory()
elseif input.KeyCode == Enum.KeyCode.Escape then
togglePauseMenu()
end
end)
-- 鼠标位置
local mousePos = UserInputService:GetMouseLocation() -- Vector2
-- 检测平台
local isMobile = UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled
local isConsole = UserInputService.GamepadEnabled
-- 隐藏鼠标光标
UserInputService.MouseIconEnabled = false
ContextActionService
ContextActionService
Higher-level action binding. Automatically generates mobile buttons. Best for game actions (interact, reload, sprint).
luau
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 -- consume the input
end
-- Bind to E key, touch button auto-created on mobile
ContextActionService:BindAction("Interact", onInteract, true, Enum.KeyCode.E)
-- Customize the mobile button
ContextActionService:SetPosition("Interact", UDim2.new(0.8, 0, 0.5, 0))
ContextActionService:SetTitle("Interact", "E")
ContextActionService:SetImage("Interact", "rbxassetid://123456789")
-- Unbind when no longer needed
ContextActionService:UnbindAction("Interact")
高级别动作绑定。自动生成移动端按钮。最适合游戏动作(交互、 reload、 sprint)。
luau
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
-- 绑定到E键,移动端自动创建触摸按钮
ContextActionService:BindAction("Interact", onInteract, true, Enum.KeyCode.E)
-- 自定义移动端按钮
ContextActionService:SetPosition("Interact", UDim2.new(0.8, 0, 0.5, 0))
ContextActionService:SetTitle("Interact", "E")
ContextActionService:SetImage("Interact", "rbxassetid://123456789")
-- 不再需要时解绑
ContextActionService:UnbindAction("Interact")
| Service | Best For |
|---|
| Global hotkeys, mouse tracking, detecting input device type, custom cursor |
| In-game actions that need mobile buttons, context-sensitive controls (e.g., "interact" only near objects) |
| UI button clicks (already cross-platform) |
| 服务 | 最佳场景 |
|---|
| 全局热键、鼠标跟踪、检测输入设备类型、自定义光标 |
| 需要移动端按钮的游戏动作、上下文敏感控制(如仅在物体附近显示“交互”) |
| UI按钮点击(已支持跨平台) |
8. Common UI Patterns (Skeletons)
8. 常见UI模式(框架)
Problem: Display purchasable items in a grid with hover feedback and server-validated purchase.
Structure:
- ScrollingFrame + UIGridLayout (container)
- Frame per item (card)
- ImageLabel (icon)
- TextLabel (name + price)
- TextButton (buy) with hover tween
Key properties: UIGridLayout.CellSize for responsive cards, UICorner for rounded edges, TweenService for hover color shift. Purchase via RemoteFunction (server validates, returns success/fail).
需求: 以网格形式展示可购买物品,包含悬停反馈和服务器验证的购买流程。
结构:
- ScrollingFrame + UIGridLayout(容器)
- 每个物品对应一个Frame(卡片)
- ImageLabel(图标)
- TextLabel(名称+价格)
- TextButton(购买按钮),带悬停动画
核心属性: UIGridLayout.CellSize实现响应式卡片,UICorner实现圆角,TweenService实现悬停颜色切换。通过RemoteFunction完成购买(服务器验证,返回成功/失败)。
Problem: Show player health with color thresholds and damage trail effect.
Structure:
- Frame (container, dark background)
- Frame (damage trail, red, tweens to match fill with delay)
- Frame (fill, green→yellow→red based on %)
Key logic: On health change, tween fill Size.X.Scale to
. Color lerp between green/yellow/red at thresholds (0.6, 0.3). Damage trail: delay 0.3s then tween to match fill.
需求: 显示玩家生命值,包含颜色阈值和伤害轨迹效果。
结构:
- Frame(容器,深色背景)
- Frame(伤害轨迹,红色,延迟后动画匹配填充进度)
- Frame(填充条,根据百分比从绿色→黄色→红色变化)
核心逻辑: 生命值变化时,将填充条的Size.X.Scale动画到
。在阈值(0.6、0.3)处进行颜色插值。伤害轨迹:延迟0.3秒后动画匹配填充条位置。
Problem: Temporary message that slides in, stays briefly, slides out.
Structure:
- Frame (anchored off-screen right)
- TextLabel (message)
- UICorner + UIStroke
Key logic: Tween Position from off-screen to visible, task.delay(3), tween back out, Destroy. Queue multiple toasts with vertical offset.
需求: 临时消息,滑入后停留一段时间,再滑出。
结构:
- Frame(锚定在屏幕外右侧)
- TextLabel(消息)
- UICorner + UIStroke
核心逻辑: 动画将Position从屏幕外移到可见位置,task.delay(3)后动画移回屏幕外,然后销毁。多个提示框按垂直偏移排队显示。
Dialog / Popup System
对话框/弹窗系统
Problem: Modal dialog with backdrop, title, body, confirm/cancel buttons.
Structure:
- Frame (backdrop, full-screen, semi-transparent black)
- Frame (dialog card, centered, 0.4x0.3 scale)
- TextLabel (title)
- TextLabel (body)
- Frame (button row)
- TextButton (confirm)
- TextButton (cancel)
Key logic: Show/hide by toggling Visible + tween BackgroundTransparency on backdrop. Return result via Promise or callback. Destroy on close.
需求: 模态对话框,包含背景遮罩、标题、内容、确认/取消按钮。
结构:
- Frame(背景遮罩,全屏,半透明黑色)
- Frame(对话框卡片,居中,0.4x0.3比例)
- TextLabel(标题)
- TextLabel(内容)
- Frame(按钮行)
- TextButton(确认)
- TextButton(取消)
核心逻辑: 通过切换Visible属性+动画背景遮罩的BackgroundTransparency来显示/隐藏。通过Promise或回调返回结果。关闭时销毁。
Positioning and Sizing
定位与尺寸
- Always use Scale for and to support all screen resolutions.
- Use Offset only for small fixed-pixel elements (icon sizes, padding, border thickness).
- Always set AnchorPoint when centering elements:
AnchorPoint = Vector2.new(0.5, 0.5)
.
- Use UISizeConstraint to set minimum and maximum sizes so UI does not become unusable on small screens or absurdly large on ultrawide monitors.
- 始终使用Scale设置和,以支持所有屏幕分辨率。
- 仅对小型固定像素元素使用Offset(图标尺寸、内边距、边框厚度)。
- 居中元素时务必设置AnchorPoint:
AnchorPoint = Vector2.new(0.5, 0.5)
。
- 使用UISizeConstraint设置最小和最大尺寸,避免UI在小屏幕上无法使用,或在超宽显示器上过大。
Style and Theme Consistency
样式与主题一致性
- Define colors, fonts, and sizes as constants at the top of your module.
- Create a UI style/theme module that all scripts reference.
luau
-- 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
- 在模块顶部将颜色、字体和尺寸定义为常量。
- 创建一个UI样式/主题模块,供所有脚本引用。
luau
-- UITheme.luau(ReplicatedStorage中的ModuleScript)
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
- Minimum text size of 14pt for body text; 12pt only for tertiary labels.
- Maintain high contrast between text and background (white on dark, dark on light).
- Use with sparingly; prefer explicit for control.
- Provide visual feedback on all interactive elements (hover color change, press scale).
- Support gamepad navigation with for console players.
- 正文字体最小尺寸为14pt;仅 tertiary 标签使用12pt。
- 保持文本与背景的高对比度(深色背景配白色,浅色背景配深色)。
- 谨慎使用和;优先使用明确的进行控制。
- 所有交互元素提供视觉反馈(悬停颜色变化、按下缩放)。
- 使用支持手柄导航,适配主机玩家。
Performance: GUI Pooling
性能:GUI对象池
For scrolling lists with many items (inventory, leaderboard), reuse GUI elements instead of creating/destroying them.
luau
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
对于包含大量元素的滚动列表(库存、排行榜),复用GUI元素而非频繁创建/销毁。
luau
local pool: { Frame } = {}
local function getCard(): Frame
local card = table.remove(pool)
if not card then
card = createNewCard() -- 仅当对象池为空时创建新元素
end
card.Visible = true
return card
end
local function returnCard(card: Frame)
card.Visible = false
card.Parent = nil
table.insert(pool, card)
end
- Set
ScreenGui.Enabled = false
when a UI is not visible rather than destroying and recreating it.
- Use on buttons instead of for cross-platform support.
- Disconnect event connections when UI is destroyed to prevent memory leaks.
- Keep UI logic in ModuleScripts; keep LocalScripts thin (just wiring).
- Always validate purchases on the server. The client UI is only for display.
- 当UI不可见时,设置
ScreenGui.Enabled = false
,而非销毁后重建。
- 按钮使用而非,以支持跨平台。
- UI销毁时断开事件连接,防止内存泄漏。
- 将UI逻辑放在ModuleScripts中;LocalScripts仅负责连接逻辑。
- 购买操作始终在服务器端验证。客户端UI仅用于显示。
Hardcoded pixel positions
硬编码像素位置
luau
-- 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)
luau
-- 错误:在不同分辨率下会失效
frame.Position = UDim2.new(0, 500, 0, 300)
frame.Size = UDim2.new(0, 400, 0, 200)
-- 正确:响应式设置
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 new GUIs every frame
每帧创建新GUI元素
luau
-- 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)
luau
-- 错误:每帧产生垃圾,导致卡顿
RunService.RenderStepped:Connect(function()
local label = Instance.new("TextLabel")
label.Text = `分数: ${score}`
label.Parent = screenGui
end)
-- 正确:更新已有元素
RunService.RenderStepped:Connect(function()
scoreLabel.Text = `分数: ${score}`
end)
Not cleaning up event connections
未清理事件连接
luau
-- BAD: connection persists after UI is removed, leaks memory
button.Activated:Connect(function()
doSomething()
end)
-- GOOD: store and disconnect
local connection = button.Activated:Connect(function()
doSomething()
end)
-- When done:
connection:Disconnect()
-- OR use :Once() for single-fire events
button.Activated:Once(function()
doSomething()
end)
luau
-- 错误:UI移除后连接仍存在,导致内存泄漏
button.Activated:Connect(function()
doSomething()
end)
-- 正确:存储并断开连接
local connection = button.Activated:Connect(function()
doSomething()
end)
-- 不再需要时:
connection:Disconnect()
-- 或使用:Once()处理单次触发事件
button.Activated:Once(function()
doSomething()
end)
Blocking the UI thread with yields
使用阻塞操作冻结UI线程
luau
-- BAD: freezes the entire UI
button.Activated:Connect(function()
task.wait(5) -- nothing else can happen for 5 seconds
label.Text = "Done"
end)
-- GOOD: use task.delay or task.spawn for async work
button.Activated:Connect(function()
button.Active = false
task.spawn(function()
-- do async work
task.wait(5)
label.Text = "Done"
button.Active = true
end)
end)
luau
-- 错误:冻结整个UI
button.Activated:Connect(function()
task.wait(5) -- 5秒内无法进行任何操作
label.Text = "完成"
end)
-- 正确:使用task.delay或task.spawn处理异步任务
button.Activated:Connect(function()
button.Active = false
task.spawn(function()
-- 执行异步任务
task.wait(5)
label.Text = "完成"
button.Active = true
end)
end)
Trusting client UI for game logic
信任客户端UI处理游戏逻辑
luau
-- BAD: client decides if purchase succeeds
button.Activated:Connect(function()
coins -= item.price -- client-side deduction, exploitable
end)
-- GOOD: server validates everything
button.Activated:Connect(function()
local success = purchaseRemote:InvokeServer(itemId)
if success then
updateCoinsDisplay()
end
end)
luau
-- 错误:客户端决定购买是否成功
button.Activated:Connect(function()
coins -= item.price -- 客户端扣除金币,可被篡改
end)
-- 正确:服务器验证所有操作
button.Activated:Connect(function()
local success = purchaseRemote:InvokeServer(itemId)
if success then
updateCoinsDisplay()
end
end)
Ignoring mobile and gamepad
忽略移动端和手柄
luau
-- BAD: only works with mouse
button.MouseButton1Click:Connect(handler)
-- GOOD: works on mouse, touch, and gamepad
button.Activated:Connect(handler)
luau
-- 错误:仅支持鼠标
button.MouseButton1Click:Connect(handler)
-- 正确:支持鼠标、触摸和手柄
button.Activated:Connect(handler)
11. Mobile-First Design
11. 移动优先设计
78% of Roblox traffic is mobile. Design for touch first, adapt for desktop/gamepad.
78%的Roblox流量来自移动端。优先为触摸操作设计,再适配桌面/手柄。
- Minimum touch target: 44×44 px (Apple guideline) or 48×48 dp (Material Design)
- Buttons smaller than 44px are frustrating on phones
- Scale buttons 1.4× on touch devices:
luau
local UserInputService = game:GetService("UserInputService")
local function isTouchDevice(): boolean
return UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled
end
-- Apply at UI creation time
if isTouchDevice() then
button.Size = UDim2.new(button.Size.X.Scale * 1.4, 0, button.Size.Y.Scale * 1.4, 0)
end
- 最小触摸目标:44×44 px(苹果指南)或48×48 dp(Material Design)
- 小于44px的按钮在手机上操作困难
- 在触摸设备上将按钮放大1.4倍:
luau
local UserInputService = game:GetService("UserInputService")
local function isTouchDevice(): boolean
return UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled
end
-- 在UI创建时应用
if isTouchDevice() then
button.Size = UDim2.new(button.Size.X.Scale * 1.4, 0, button.Size.Y.Scale * 1.4, 0)
end
PreferredInput API (2025+)
PreferredInput API(2025+)
Modern approach to input detection. Use instead of checking individual booleans:
luau
local preferred = UserInputService.PreferredInput
-- Enum.PreferredInput values:
-- Touch, MouseAndKeyboard, Gamepad, None
if preferred == Enum.PreferredInput.Touch then
-- enlarge buttons, simplify layout
elseif preferred == Enum.PreferredInput.Gamepad then
-- highlight focused element, add navigation hints
end
UserInputService:GetPropertyChangedSignal("PreferredInput"):Connect(function()
-- re-adapt UI when input method changes
end)
现代输入检测方式。替代单独检查各个布尔值:
luau
local preferred = UserInputService.PreferredInput
-- Enum.PreferredInput取值:
-- Touch, MouseAndKeyboard, Gamepad, None
if preferred == Enum.PreferredInput.Touch then
-- 放大按钮,简化布局
elseif preferred == Enum.PreferredInput.Gamepad then
-- 高亮选中元素,添加导航提示
end
UserInputService:GetPropertyChangedSignal("PreferredInput"):Connect(function()
-- 输入方式改变时重新适配UI
end)
Safe Areas and Screen Real Estate
安全区域与屏幕空间
- Top bar takes 58px. Use
ScreenGui.IgnoreGuiInset = true
for fullscreen, but don't put critical content behind it.
- Bottom of screen has virtual controls on mobile. Keep interactive elements above the bottom 100px.
- Use the Device Emulator in Studio (View → Device Emulator) to test phone/tablet views before shipping.
- 顶部栏占用58px。使用
ScreenGui.IgnoreGuiInset = true
实现全屏,但不要将关键内容放在顶部栏后方。
- 移动端屏幕底部有虚拟控制区域。将交互元素放在底部100px以上。
- 使用Studio中的设备模拟器(视图 → 设备模拟器)在发布前测试手机/平板视图。
Mobile Layout Rules
移动端布局规则
- Prefer vertical layouts over horizontal on mobile (scrolling down is natural)
- Avoid sidebars - use bottom sheets or full-screen overlays instead
- Use to zoom entire UI proportionally on small screens:
luau
local uiScale = Instance.new("UIScale")
uiScale.Scale = math.min(1, camera.ViewportSize.X / 1080) -- reference width
uiScale.Parent = screenGui
- 移动端优先使用垂直布局(向下滚动更自然)
- 避免侧边栏 - 使用底部弹窗或全屏覆盖层替代
- 在小屏幕上使用按比例放大整个UI:
luau
local uiScale = Instance.new("UIScale")
uiScale.Scale = math.min(1, camera.ViewportSize.X / 1080) -- 参考宽度
uiScale.Parent = screenGui
For console players:
luau
local GuiService = game:GetService("GuiService")
-- Set the initially selected object when opening a menu
GuiService.SelectedObject = myButton
-- On menu close, clear selection
GuiService.SelectedObject = nil
Source: Roblox Adaptive Design Guidelines (create.roblox.com/docs/production/publishing/adaptive-design)
针对主机玩家:
luau
local GuiService = game:GetService("GuiService")
-- 打开菜单时设置初始选中对象
GuiService.SelectedObject = myButton
-- 关闭菜单时清除选中
GuiService.SelectedObject = nil
来源:Roblox自适应设计指南(create.roblox.com/docs/production/publishing/adaptive-design)
12. Text Handling
12. 文本处理
AutomaticSize vs TextScaled
AutomaticSize vs TextScaled
Do NOT use . It makes text unreadably small on mobile and truncates text that doesn't fit.
Use
instead. It resizes the UI element to fit the text at a consistent, readable font size:
luau
-- BAD: text scales to fit, becomes unreadable on small screens
label.TextScaled = true
-- GOOD: label grows/shrinks to fit text at fixed readable size
label.TextSize = 16
label.TextWrapped = true
label.AutomaticSize = Enum.AutomaticSize.Y -- height adjusts to content
不要使用。 它会导致移动端文字过小,且无法容纳的文字会被截断。
改用
。它会调整UI元素尺寸以适应文字,同时保持一致的可读字体大小:
luau
-- 错误:文字缩放以适应容器,在小屏幕上无法阅读
label.TextScaled = true
-- 正确:标签根据固定可读尺寸的文字自动调整高度
label.TextSize = 16
label.TextWrapped = true
label.AutomaticSize = Enum.AutomaticSize.Y -- 高度随内容调整
UITextSizeConstraint
UITextSizeConstraint
If you must use
, always add a constraint:
luau
local textSizeConstraint = Instance.new("UITextSizeConstraint")
textSizeConstraint.MaxTextSize = 24
textSizeConstraint.MinTextSize = 12 -- NEVER below 9 (official hard rule)
textSizeConstraint.Parent = label
luau
local textSizeConstraint = Instance.new("UITextSizeConstraint")
textSizeConstraint.MaxTextSize = 24
textSizeConstraint.MinTextSize = 12 -- 绝对不要低于9(官方硬性规则)
textSizeConstraint.Parent = label
luau
label.TextTruncate = Enum.TextTruncate.AtEnd -- shows "..." when text overflows
label.TextWrapped = true -- wrap instead of truncate for multi-line content
luau
label.TextTruncate = Enum.TextTruncate.AtEnd -- 文字溢出时显示"..."
label.TextWrapped = true -- 换行而非截断,支持多行内容
Calculating Text Size Programmatically
程序化计算文本尺寸
luau
local TextService = game:GetService("TextService")
local bounds = TextService:GetTextSize(
"Hello World",
16, -- TextSize
Enum.Font.Gotham,
Vector2.new(200, math.huge) -- max width, unlimited height
)
-- bounds.X = actual width, bounds.Y = actual height
Source: Roblox Size Modifiers & Constraints docs (create.roblox.com/docs/ui/size-modifiers)
luau
local TextService = game:GetService("TextService")
local bounds = TextService:GetTextSize(
"Hello World",
16, -- TextSize
Enum.Font.Gotham,
Vector2.new(200, math.huge) -- 最大宽度,高度不限
)
-- bounds.X = 实际宽度,bounds.Y = 实际高度
来源:Roblox尺寸修饰器与约束文档(create.roblox.com/docs/ui/size-modifiers)
13. UI Styling (StyleEditor)
13. UI样式(StyleEditor)
Roblox ships a CSS-like styling system (2025+). Use it for consistent theming.
Roblox推出了类CSS的样式系统(2025+)。用于实现一致的主题。
Named values (like CSS variables). Define once, reference everywhere.
Style Rules with Selectors
带选择器的样式规则
luau
-- Style rules can target by class, tag, name, state, modifier, query
-- Similar to CSS selectors
luau
-- 样式规则可按类、标签、名称、状态、修饰器、查询目标
-- 类似CSS选择器
When to Use StyleEditor vs Code Themes
StyleEditor vs 代码主题的选择
| Approach | Best For |
|---|
| Studio StyleEditor | Static UI built visually, team collaboration on design |
| Code-based UITheme module | Dynamic UI built in code, programmatic theming |
Both can coexist. StyleEditor is the "build in Studio" answer; code themes are the "build in code" answer.
Source: Roblox UI Styling docs (create.roblox.com/docs/ui/styling)
| 方式 | 最佳场景 |
|---|
| Studio StyleEditor | 可视化构建静态UI,团队协作设计 |
| 代码实现的UITheme模块 | 代码构建动态UI,程序化主题切换 |
两种方式可共存。StyleEditor是“在Studio中构建”的解决方案;代码主题是“在代码中构建”的解决方案。
来源:Roblox UI样式文档(create.roblox.com/docs/ui/styling)
14. ScrollingFrame Patterns
14. ScrollingFrame模式
AutomaticCanvasSize (Use This)
AutomaticCanvasSize(推荐使用)
luau
-- BAD: manually calculating canvas size
scrollFrame.CanvasSize = UDim2.new(0, 0, 0, listLayout.AbsoluteContentSize.Y)
-- GOOD: engine handles it automatically
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
scrollFrame.ScrollingDirection = Enum.ScrollingDirection.Y
luau
-- 错误:手动计算画布尺寸
scrollFrame.CanvasSize = UDim2.new(0, 0, 0, listLayout.AbsoluteContentSize.Y)
-- 正确:引擎自动处理
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
scrollFrame.ScrollingDirection = Enum.ScrollingDirection.Y
ScrollingFrame + UIListLayout
ScrollingFrame + UIListLayout
The most common pattern - a scrollable list:
luau
local scrollFrame = Instance.new("ScrollingFrame")
scrollFrame.Size = UDim2.new(1, 0, 1, 0)
scrollFrame.BackgroundTransparency = 1
scrollFrame.ScrollBarThickness = 6
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
scrollFrame.ScrollingDirection = Enum.ScrollingDirection.Y
scrollFrame.Parent = container
local listLayout = Instance.new("UIListLayout")
listLayout.FillDirection = Enum.FillDirection.Vertical
listLayout.Padding = UDim.new(0, 8)
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
listLayout.Parent = scrollFrame
-- Add items as children of scrollFrame
-- They auto-arrange vertically, scrollFrame auto-sizes
最常见的模式 - 可滚动列表:
luau
local scrollFrame = Instance.new("ScrollingFrame")
scrollFrame.Size = UDim2.new(1, 0, 1, 0)
scrollFrame.BackgroundTransparency = 1
scrollFrame.ScrollBarThickness = 6
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
scrollFrame.ScrollingDirection = Enum.ScrollingDirection.Y
scrollFrame.Parent = container
local listLayout = Instance.new("UIListLayout")
listLayout.FillDirection = Enum.FillDirection.Vertical
listLayout.Padding = UDim.new(0, 8)
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
listLayout.Parent = scrollFrame
-- 向scrollFrame添加子元素
-- 它们会自动垂直排列,scrollFrame自动调整尺寸
ScrollingFrame + UIGridLayout
ScrollingFrame + UIGridLayout
For scrollable grids (inventory, shop):
luau
local scrollFrame = Instance.new("ScrollingFrame")
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
scrollFrame.ScrollingDirection = Enum.ScrollingDirection.Y
scrollFrame.Parent = container
local gridLayout = Instance.new("UIGridLayout")
gridLayout.CellSize = UDim2.new(0, 80, 0, 80)
gridLayout.CellPadding = UDim2.new(0, 8, 0, 8)
gridLayout.FillDirectionMaxCells = 4 -- 4 columns
gridLayout.SortOrder = Enum.SortOrder.LayoutOrder
gridLayout.Parent = scrollFrame
用于可滚动网格(库存、商店):
luau
local scrollFrame = Instance.new("ScrollingFrame")
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
scrollFrame.ScrollingDirection = Enum.ScrollingDirection.Y
scrollFrame.Parent = container
local gridLayout = Instance.new("UIGridLayout")
gridLayout.CellSize = UDim2.new(0, 80, 0, 80)
gridLayout.CellPadding = UDim2.new(0, 8, 0, 8)
gridLayout.FillDirectionMaxCells = 4 -- 4列
gridLayout.SortOrder = Enum.SortOrder.LayoutOrder
gridLayout.Parent = scrollFrame
The bouncy effect on iOS/Android. Enabled by default. Disable if unwanted:
luau
scrollFrame.ElasticBehavior = Enum.ElasticBehavior.Never
Source: Roblox Scrolling Frames docs (create.roblox.com/docs/ui/scrolling-frames)
iOS/Android上的弹跳效果。默认开启。如需禁用:
luau
scrollFrame.ElasticBehavior = Enum.ElasticBehavior.Never
来源:Roblox滚动框架文档(create.roblox.com/docs/ui/scrolling-frames)
- Roblox Adaptive Design Guidelines: create.roblox.com/docs/production/publishing/adaptive-design
- Roblox Size Modifiers & Constraints: create.roblox.com/docs/ui/size-modifiers
- Roblox UI Styling: create.roblox.com/docs/ui/styling
- Roblox Scrolling Frames: create.roblox.com/docs/ui/scrolling-frames
- Roblox List & Flex Layouts: create.roblox.com/docs/ui/list-flex-layouts
- Roblox Grid & Table Layouts: create.roblox.com/docs/ui/grid-table-layouts
- DevForum: Designing UI - Tips and Best Practices (Roblox Staff)
- DevForum: Design Mobile First
- DevForum: GUI Optimization Tips
- DevForum: epochzx - Container Frame Rule for ScreenGui
- DevForum: uiuxartist (Roblox Staff) - Scale vs Offset guidelines
- DevForum: PictureFolder - UI Design Tips and Best Practices (119 likes)
- DevForum: Modern UI Colour Schemes - dark palette guidelines
- Fusion: github.com/dphfox/Fusion (MIT)
- Vide: github.com/centau/vide (MIT)
- brockmartin/roblox-game-skill (MIT) - base content
- Roblox自适应设计指南:create.roblox.com/docs/production/publishing/adaptive-design
- Roblox尺寸修饰器与约束:create.roblox.com/docs/ui/size-modifiers
- Roblox UI样式:create.roblox.com/docs/ui/styling
- Roblox滚动框架:create.roblox.com/docs/ui/scrolling-frames
- Roblox列表与弹性布局:create.roblox.com/docs/ui/list-flex-layouts
- Roblox网格与表格布局:create.roblox.com/docs/ui/grid-table-layouts
- 开发者论坛:《UI设计 - 技巧与最佳实践》(Roblox员工)
- 开发者论坛:《移动优先设计》
- 开发者论坛:《GUI优化技巧》
- 开发者论坛:epochzx - ScreenGui的容器框架规则
- 开发者论坛:uiuxartist(Roblox员工) - Scale与Offset指南
- 开发者论坛:PictureFolder - 《UI设计技巧与最佳实践》(获119个赞)
- 开发者论坛:《现代UI配色方案》 - 深色调色板指南
- Fusion:github.com/dphfox/Fusion(MIT协议)
- Vide:github.com/centau/vide(MIT协议)
- brockmartin/roblox-game-skill(MIT协议) - 基础内容