roblox-gui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
<!-- Source: brockmartin/roblox-game-skill (MIT) -->
<!-- 来源:brockmartin/roblox-game-skill(MIT协议) -->

Roblox GUI/UI Systems Reference

Roblox GUI/UI系统参考文档

1. Overview

1. 概述

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
StarterGui
at edit time and are cloned into each player's
PlayerGui
at runtime.

在Roblox中处理任何UI相关任务时,请参考本文档:
  • 构建菜单(主菜单、暂停菜单、设置菜单)
  • 抬头显示(HUD)(生命值条、小地图、弹药计数器、分数显示)
  • 商店与库存界面
  • 通知与提示系统
  • 对话框与弹窗
  • 任何2D或依附于3D物体的界面元素
所有GUI代码均在客户端(LocalScripts)运行。UI对象在编辑阶段位于
StarterGui
下,运行时会被克隆到每个玩家的
PlayerGui
中。

Quick Reference

快速参考

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)。

Full Reference

完整参考

Design Guidelines

设计指南

<!-- 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
Size = {1,0},{1,0}
and
BackgroundTransparency = 1
. Its
AbsoluteSize
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作为第一个子元素,设置
Size = {1,0},{1,0}
BackgroundTransparency = 1
。它的
AbsoluteSize
始终与屏幕分辨率匹配。
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 (
    UDim.new(0.5, 0)
    = 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(
    UDim.new(0.5, 0)
    = 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
    ScreenGui.ScreenInsets
    .
  • 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)。
  • 通过
    ScreenGui.ScreenInsets
    适配刘海屏。
  • 不要将UI放在顶部58px(Roblox顶部栏)或底部虚拟控制区域。
  • 发布前使用设备模拟器测试(视图 → 设备模拟器)。

Typography

排版

<!-- 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 (
    #FFFFFF
    ) on pure black (
    #000000
    ). Use off-white (
    #F0F0F0
    ) on dark gray (
    #1E1E1E
    ).
  • Size hierarchy: bigger/bolder = more important.
<!-- 来源:PictureFolder,Roblox开发者论坛(获119个赞) --> <!-- 《UI设计 - 技巧与最佳实践》 -->
  • 最多使用两种字体:标题字体(标题/按钮)+ 正文字体(描述)。
  • Gotham是当前Roblox的主流现代字体。
  • 绝对不要在纯黑色(
    #000000
    )背景上使用纯白色(
    #FFFFFF
    )文字。使用米白色(
    #F0F0F0
    )搭配深灰色(
    #1E1E1E
    )。
  • 尺寸层级:越大/越粗的文字,重要性越高。

Color

色彩

<!-- Source: DevForum "Modern UI Colour Schemes" -->
  • Dark palette:
    20,20,20
    (Modern Black) to
    35,35,35
    (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配色方案》 -->
  • 深色调色板:
    20,20,20
    (现代黑)到
    35,35,35
    (极浅灰)。不要使用比35更浅的颜色。
  • 灰色底色 + 强调色用于交互元素。
  • 选定一套调色板并坚持使用。一致性 > 多样性。

Common AI UI Mistakes

AI常见UI错误

MistakeFix
Elements directly under ScreenGuiUse a Container Frame child
Pure Scale onlyToo small on mobile - add Offset minimums
Pure Offset onlyBreaks on different resolutions
Pure white on pure black textUse off-white on dark gray
Ignoring mobile players50%+ are mobile; design touch-first
Text-heavy UI for young audiencesUse icons, images, minimal text
UI overlapping top bar / chat / leaderboardRespect safe areas (top 58px, bottom 100px)
Not testing with Device EmulatorAlways test before publishing

错误修复方案
元素直接放在ScreenGui下使用Container Frame作为子容器
仅使用纯Scale在移动设备上过小 - 添加Offset最小尺寸
仅使用纯Offset在不同分辨率下会失效
纯黑背景配纯白文字使用米白色搭配深灰色
忽略移动玩家超过50%的玩家使用移动设备;优先为触摸操作设计
面向低龄用户的文字密集型UI使用图标、图片,减少文字
UI与顶部栏/聊天框/排行榜重叠预留安全区域(顶部58px,底部100px)
未使用设备模拟器测试发布前务必测试

2. GUI Hierarchy

2. GUI层级

ScreenGui (2D Overlay)

ScreenGui(2D覆盖层)

The primary container for all 2D UI. Placed in
StarterGui
; Roblox copies it into each player's
PlayerGui
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:
PropertyPurpose
DisplayOrder
Controls layering. Higher values render on top of lower values.
ResetOnSpawn
true
(default): destroyed and re-cloned on respawn. Set to
false
for persistent UI (shops, settings).
IgnoreGuiInset
true
: UI extends behind the top bar (CoreGui area). Use for fullscreen overlays.
Enabled
Toggle visibility without destroying.
所有2D UI的主容器。放置在
StarterGui
中;Roblox会在玩家生成时将其复制到每个玩家的
PlayerGui
中。
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
核心属性:
属性用途
DisplayOrder
控制层级。值越大,渲染在值较小的元素上方。
ResetOnSpawn
true
(默认):重生时销毁并重新克隆。设置为
false
用于持久化UI(商店、设置)。
IgnoreGuiInset
true
:UI延伸到顶部栏(CoreGui区域)后方。用于全屏覆盖层。
Enabled
切换可见性,无需销毁元素。

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元素

Frame

Frame

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
Activated
instead of
MouseButton1Click
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)
为了跨平台支持,使用
Activated
而非
MouseButton1Click

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

Layout Modifiers

布局修饰器

ModifierPurpose
UIListLayout
Arranges children in a vertical or horizontal list
UIGridLayout
Arranges children in a grid
UIPageLayout
Swipeable pages (one child visible at a time)
UIPadding
Inner padding on a container
UICorner
Rounded corners
UIStroke
Outline/border effect
UIGradient
Color gradient on an element
UISizeConstraint
Min/max pixel size
UIAspectRatioConstraint
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

修饰器用途
UIListLayout
将子元素按垂直或水平列表排列
UIGridLayout
将子元素按网格排列
UIPageLayout
可滑动页面(一次仅显示一个子元素)
UIPadding
容器的内边距
UICorner
圆角效果
UIStroke
描边/边框效果
UIGradient
元素的颜色渐变
UISizeConstraint
最小/最大像素尺寸
UIAspectRatioConstraint
锁定宽高比
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

4. Layout Systems

4. 布局系统

UIListLayout

UIListLayout

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
LayoutOrder
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
子元素按
LayoutOrder
属性排序(值越小越靠前)。

UIGridLayout

UIGridLayout

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

UIPageLayout

UIPageLayout

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)

When to Use Each

布局选择指南

ScenarioLayout
Vertical menu, chat log, leaderboard
UIListLayout
Inventory grid, shop items, emoji picker
UIGridLayout
Settings tabs, tutorial slides, shop categories
UIPageLayout
HUD element pinned to a cornerAbsolute positioning (no layout)
Overlapping elements (health bar segments)Absolute positioning
场景布局方式
垂直菜单、聊天记录、排行榜
UIListLayout
库存网格、商店物品、表情选择器
UIGridLayout
设置标签页、教程幻灯片、商店分类
UIPageLayout
固定在屏幕角落的HUD元素绝对定位(不使用布局)
重叠元素(生命值条分段)绝对定位

Absolute vs Layout-Driven

绝对定位 vs 布局驱动

  • Absolute positioning: Set
    Position
    and
    Size
    directly. Use for HUD elements pinned to specific screen locations.
  • Layout-driven: Add a layout object as a child. The layout overrides children's
    Position
    . Use for dynamic lists where content count changes.
You can nest both: a frame with absolute position containing children managed by a UIListLayout.

  • 绝对定位:直接设置
    Position
    Size
    。用于固定在特定屏幕位置的HUD元素。
  • 布局驱动:添加布局对象作为子元素。布局会覆盖子元素的
    Position
    。用于内容数量动态变化的列表。
两种方式可以嵌套使用:一个绝对定位的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实现动画

TweenInfo

TweenInfo

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:
StyleUse Case
Quad
General-purpose, smooth
Back
Slight overshoot, bouncy buttons
Elastic
Springy, attention-grabbing
Bounce
Bouncing effect at the end
Linear
Constant speed, progress bars
Sine
Gentle, subtle motion
Exponential
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                              -- 启动前延迟时间
)
常用缓动样式:
样式使用场景
Quad
通用场景,平滑过渡
Back
轻微过冲,适用于弹性按钮
Elastic
弹簧效果,吸引注意力
Bounce
结束时弹跳效果
Linear
恒定速度,适用于进度条
Sine
柔和、微妙的运动
Exponential
剧烈的加速/减速

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()

Chaining Tweens

动画链式调用

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

7. Input Handling

7. 输入处理

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")

When to Use Each

服务选择指南

ServiceBest For
UserInputService
Global hotkeys, mouse tracking, detecting input device type, custom cursor
ContextActionService
In-game actions that need mobile buttons, context-sensitive controls (e.g., "interact" only near objects)
GuiButton.Activated
UI button clicks (already cross-platform)

服务最佳场景
UserInputService
全局热键、鼠标跟踪、检测输入设备类型、自定义光标
ContextActionService
需要移动端按钮的游戏动作、上下文敏感控制(如仅在物体附近显示“交互”)
GuiButton.Activated
UI按钮点击(已支持跨平台)

8. Common UI Patterns (Skeletons)

8. 常见UI模式(框架)

Shop Interface

商店界面

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完成购买(服务器验证,返回成功/失败)。

Health Bar

生命值条

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
health/maxHealth
. 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动画到
health/maxHealth
。在阈值(0.6、0.3)处进行颜色插值。伤害轨迹:延迟0.3秒后动画匹配填充条位置。

Notification Toast

通知提示框

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或回调返回结果。关闭时销毁。

9. Best Practices

9. 最佳实践

Positioning and Sizing

定位与尺寸

  • Always use Scale for
    Position
    and
    Size
    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设置
    Position
    Size
    ,以支持所有屏幕分辨率。
  • 仅对小型固定像素元素使用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

Accessibility

可访问性

  • 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
    TextScaled = true
    with
    TextWrapped = true
    sparingly; prefer explicit
    TextSize
    for control.
  • Provide visual feedback on all interactive elements (hover color change, press scale).
  • Support gamepad navigation with
    GuiService:Select()
    for console players.
  • 正文字体最小尺寸为14pt;仅 tertiary 标签使用12pt。
  • 保持文本与背景的高对比度(深色背景配白色,浅色背景配深色)。
  • 谨慎使用
    TextScaled = true
    TextWrapped = true
    ;优先使用明确的
    TextSize
    进行控制。
  • 所有交互元素提供视觉反馈(悬停颜色变化、按下缩放)。
  • 使用
    GuiService:Select()
    支持手柄导航,适配主机玩家。

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

General

通用规则

  • Set
    ScreenGui.Enabled = false
    when a UI is not visible rather than destroying and recreating it.
  • Use
    Activated
    on buttons instead of
    MouseButton1Click
    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
    ,而非销毁后重建。
  • 按钮使用
    Activated
    而非
    MouseButton1Click
    ,以支持跨平台。
  • UI销毁时断开事件连接,防止内存泄漏。
  • 将UI逻辑放在ModuleScripts中;LocalScripts仅负责连接逻辑。
  • 购买操作始终在服务器端验证。客户端UI仅用于显示。

10. Anti-Patterns

10. 反模式

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流量来自移动端。优先为触摸操作设计,再适配桌面/手柄。

Touch Targets

触摸目标

  • 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
    UIScale
    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
  • 移动端优先使用垂直布局(向下滚动更自然)
  • 避免侧边栏 - 使用底部弹窗或全屏覆盖层替代
  • 在小屏幕上使用
    UIScale
    按比例放大整个UI:
luau
local uiScale = Instance.new("UIScale")
uiScale.Scale = math.min(1, camera.ViewportSize.X / 1080) -- 参考宽度
uiScale.Parent = screenGui

Gamepad Navigation

手柄导航

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
TextScaled = true
.
It makes text unreadably small on mobile and truncates text that doesn't fit.
Use
AutomaticSize
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
不要使用
TextScaled = true
它会导致移动端文字过小,且无法容纳的文字会被截断。
改用
AutomaticSize
。它会调整UI元素尺寸以适应文字,同时保持一致的可读字体大小:
luau
-- 错误:文字缩放以适应容器,在小屏幕上无法阅读
label.TextScaled = true

-- 正确:标签根据固定可读尺寸的文字自动调整高度
label.TextSize = 16
label.TextWrapped = true
label.AutomaticSize = Enum.AutomaticSize.Y -- 高度随内容调整

UITextSizeConstraint

UITextSizeConstraint

If you must use
TextScaled
, 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
如果必须使用
TextScaled
,务必添加约束:
luau
local textSizeConstraint = Instance.new("UITextSizeConstraint")
textSizeConstraint.MaxTextSize = 24
textSizeConstraint.MinTextSize = 12 -- 绝对不要低于9(官方硬性规则)
textSizeConstraint.Parent = label

Text Truncation

文本截断

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+)。用于实现一致的主题。

Style Tokens

样式令牌

Named values (like CSS variables). Define once, reference everywhere.
命名值(类似CSS变量)。定义一次,全局引用。

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 代码主题的选择

ApproachBest For
Studio StyleEditorStatic UI built visually, team collaboration on design
Code-based UITheme moduleDynamic 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

Elastic Overscroll

弹性滚动

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)

Sources

来源

  • 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协议) - 基础内容