roblox-opencode 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/commands/setup-game.md +108 -0
- package/commands/sync-check.md +53 -0
- package/core/roblox-core.md +93 -0
- package/dist/server.js +167 -0
- package/package.json +35 -0
- package/skills/roblox-analytics/SKILL.md +277 -0
- package/skills/roblox-analytics/references/event-batcher.luau +75 -0
- package/skills/roblox-animation-vfx/SKILL.md +1325 -0
- package/skills/roblox-architecture/SKILL.md +863 -0
- package/skills/roblox-architecture/references/combat-systems.md +1381 -0
- package/skills/roblox-code-review/SKILL.md +687 -0
- package/skills/roblox-data/SKILL.md +889 -0
- package/skills/roblox-data/references/inventory-systems.md +1729 -0
- package/skills/roblox-debug/SKILL.md +99 -0
- package/skills/roblox-gui/SKILL.md +1103 -0
- package/skills/roblox-gui-fusion/SKILL.md +150 -0
- package/skills/roblox-gui-fusion/references/inventory.luau +427 -0
- package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -0
- package/skills/roblox-gui-fusion/references/shop.luau +411 -0
- package/skills/roblox-luau-mastery/SKILL.md +1519 -0
- package/skills/roblox-monetization/SKILL.md +1084 -0
- package/skills/roblox-monetization/references/process-receipt.luau +131 -0
- package/skills/roblox-networking/SKILL.md +669 -0
- package/skills/roblox-networking/references/remote-validator.luau +193 -0
- package/skills/roblox-publish-checklist/SKILL.md +128 -0
- package/skills/roblox-runtime/SKILL.md +753 -0
- package/skills/roblox-sharp-edges/SKILL.md +295 -0
- package/skills/roblox-sync/SKILL.md +126 -0
- package/skills/roblox-testing/SKILL.md +943 -0
- package/skills/roblox-tooling/SKILL.md +150 -0
- package/vendor/LICENSES/ProfileStore-LICENSE +201 -0
- package/vendor/LICENSES/RbxUtil-LICENSE +7 -0
- package/vendor/LICENSES/promise-LICENSE +21 -0
- package/vendor/LICENSES/t-LICENSE +21 -0
- package/vendor/LICENSES/testez-LICENSE +201 -0
- package/vendor/README.md +84 -0
- package/vendor/fusion/Animation/ExternalTime.luau +84 -0
- package/vendor/fusion/Animation/Spring.luau +322 -0
- package/vendor/fusion/Animation/Stopwatch.luau +128 -0
- package/vendor/fusion/Animation/Tween.luau +187 -0
- package/vendor/fusion/Animation/getTweenDuration.luau +27 -0
- package/vendor/fusion/Animation/getTweenRatio.luau +47 -0
- package/vendor/fusion/Animation/lerpType.luau +164 -0
- package/vendor/fusion/Animation/packType.luau +100 -0
- package/vendor/fusion/Animation/springCoefficients.luau +80 -0
- package/vendor/fusion/Animation/unpackType.luau +103 -0
- package/vendor/fusion/Colour/Oklab.luau +70 -0
- package/vendor/fusion/Colour/sRGB.luau +55 -0
- package/vendor/fusion/External.luau +168 -0
- package/vendor/fusion/ExternalDebug.luau +70 -0
- package/vendor/fusion/Graph/Observer.luau +114 -0
- package/vendor/fusion/Graph/castToGraph.luau +29 -0
- package/vendor/fusion/Graph/change.luau +81 -0
- package/vendor/fusion/Graph/depend.luau +33 -0
- package/vendor/fusion/Graph/evaluate.luau +56 -0
- package/vendor/fusion/Instances/Attribute.luau +58 -0
- package/vendor/fusion/Instances/AttributeChange.luau +47 -0
- package/vendor/fusion/Instances/AttributeOut.luau +63 -0
- package/vendor/fusion/Instances/Child.luau +21 -0
- package/vendor/fusion/Instances/Children.luau +148 -0
- package/vendor/fusion/Instances/Hydrate.luau +33 -0
- package/vendor/fusion/Instances/New.luau +53 -0
- package/vendor/fusion/Instances/OnChange.luau +50 -0
- package/vendor/fusion/Instances/OnEvent.luau +54 -0
- package/vendor/fusion/Instances/Out.luau +69 -0
- package/vendor/fusion/Instances/applyInstanceProps.luau +149 -0
- package/vendor/fusion/Instances/defaultProps.luau +194 -0
- package/vendor/fusion/LICENSE +21 -0
- package/vendor/fusion/Logging/formatError.luau +49 -0
- package/vendor/fusion/Logging/messages.luau +52 -0
- package/vendor/fusion/Logging/parseError.luau +25 -0
- package/vendor/fusion/Memory/checkLifetime.luau +134 -0
- package/vendor/fusion/Memory/deriveScope.luau +24 -0
- package/vendor/fusion/Memory/deriveScopeImpl.luau +45 -0
- package/vendor/fusion/Memory/doCleanup.luau +79 -0
- package/vendor/fusion/Memory/innerScope.luau +34 -0
- package/vendor/fusion/Memory/legacyCleanup.luau +18 -0
- package/vendor/fusion/Memory/needsDestruction.luau +17 -0
- package/vendor/fusion/Memory/poisonScope.luau +34 -0
- package/vendor/fusion/Memory/scopePool.luau +55 -0
- package/vendor/fusion/Memory/scoped.luau +27 -0
- package/vendor/fusion/Memory/whichLivesLonger.luau +75 -0
- package/vendor/fusion/RobloxExternal.luau +98 -0
- package/vendor/fusion/State/Computed.luau +139 -0
- package/vendor/fusion/State/For/Disassembly.luau +211 -0
- package/vendor/fusion/State/For/ForTypes.luau +30 -0
- package/vendor/fusion/State/For/init.luau +110 -0
- package/vendor/fusion/State/ForKeys.luau +94 -0
- package/vendor/fusion/State/ForPairs.luau +97 -0
- package/vendor/fusion/State/ForValues.luau +94 -0
- package/vendor/fusion/State/Value.luau +88 -0
- package/vendor/fusion/State/castToState.luau +26 -0
- package/vendor/fusion/State/peek.luau +31 -0
- package/vendor/fusion/State/updateAll.luau +1 -0
- package/vendor/fusion/Types.luau +314 -0
- package/vendor/fusion/Utility/Contextual.luau +91 -0
- package/vendor/fusion/Utility/Safe.luau +23 -0
- package/vendor/fusion/Utility/isSimilar.luau +29 -0
- package/vendor/fusion/Utility/merge.luau +35 -0
- package/vendor/fusion/Utility/nameOf.luau +35 -0
- package/vendor/fusion/Utility/never.luau +14 -0
- package/vendor/fusion/Utility/nicknames.luau +11 -0
- package/vendor/fusion/Utility/xtypeof.luau +27 -0
- package/vendor/fusion/init.luau +82 -0
- package/vendor/profilestore/init.luau +2243 -0
- package/vendor/promise/init.luau +1982 -0
- package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -0
- package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -0
- package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -0
- package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -0
- package/vendor/rbxutil/buffer-util/Types.luau +60 -0
- package/vendor/rbxutil/buffer-util/index.d.ts +153 -0
- package/vendor/rbxutil/buffer-util/init.luau +41 -0
- package/vendor/rbxutil/buffer-util/package.json +16 -0
- package/vendor/rbxutil/buffer-util/wally.toml +9 -0
- package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -0
- package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -0
- package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -0
- package/vendor/rbxutil/comm/Client/init.luau +135 -0
- package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -0
- package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -0
- package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -0
- package/vendor/rbxutil/comm/Server/init.luau +140 -0
- package/vendor/rbxutil/comm/Types.luau +18 -0
- package/vendor/rbxutil/comm/Util.luau +27 -0
- package/vendor/rbxutil/comm/init.luau +35 -0
- package/vendor/rbxutil/comm/wally.toml +13 -0
- package/vendor/rbxutil/component/init.luau +759 -0
- package/vendor/rbxutil/component/init.test.luau +311 -0
- package/vendor/rbxutil/component/wally.toml +14 -0
- package/vendor/rbxutil/concur/init.luau +542 -0
- package/vendor/rbxutil/concur/init.test.luau +364 -0
- package/vendor/rbxutil/concur/wally.toml +8 -0
- package/vendor/rbxutil/enum-list/init.luau +101 -0
- package/vendor/rbxutil/enum-list/init.test.luau +91 -0
- package/vendor/rbxutil/enum-list/wally.toml +8 -0
- package/vendor/rbxutil/find/index.d.ts +20 -0
- package/vendor/rbxutil/find/init.luau +44 -0
- package/vendor/rbxutil/find/package.json +17 -0
- package/vendor/rbxutil/find/wally.toml +8 -0
- package/vendor/rbxutil/input/Gamepad.luau +559 -0
- package/vendor/rbxutil/input/Keyboard.luau +124 -0
- package/vendor/rbxutil/input/Mouse.luau +278 -0
- package/vendor/rbxutil/input/PreferredInput.luau +91 -0
- package/vendor/rbxutil/input/Touch.luau +120 -0
- package/vendor/rbxutil/input/init.luau +33 -0
- package/vendor/rbxutil/input/wally.toml +12 -0
- package/vendor/rbxutil/loader/index.d.ts +15 -0
- package/vendor/rbxutil/loader/init.luau +137 -0
- package/vendor/rbxutil/loader/wally.toml +8 -0
- package/vendor/rbxutil/log/index.d.ts +38 -0
- package/vendor/rbxutil/log/init.luau +746 -0
- package/vendor/rbxutil/log/wally.toml +8 -0
- package/vendor/rbxutil/net/init.luau +190 -0
- package/vendor/rbxutil/net/wally.toml +8 -0
- package/vendor/rbxutil/option/index.d.ts +44 -0
- package/vendor/rbxutil/option/init.luau +489 -0
- package/vendor/rbxutil/option/init.test.luau +342 -0
- package/vendor/rbxutil/option/wally.toml +8 -0
- package/vendor/rbxutil/pid/index.d.ts +53 -0
- package/vendor/rbxutil/pid/init.luau +195 -0
- package/vendor/rbxutil/pid/package.json +16 -0
- package/vendor/rbxutil/pid/wally.toml +9 -0
- package/vendor/rbxutil/quaternion/index.d.ts +117 -0
- package/vendor/rbxutil/quaternion/init.luau +570 -0
- package/vendor/rbxutil/quaternion/package.json +16 -0
- package/vendor/rbxutil/quaternion/wally.toml +9 -0
- package/vendor/rbxutil/query/index.d.ts +43 -0
- package/vendor/rbxutil/query/init.luau +117 -0
- package/vendor/rbxutil/query/package.json +18 -0
- package/vendor/rbxutil/query/wally.toml +9 -0
- package/vendor/rbxutil/sequent/index.d.ts +28 -0
- package/vendor/rbxutil/sequent/init.luau +340 -0
- package/vendor/rbxutil/sequent/package.json +16 -0
- package/vendor/rbxutil/sequent/wally.toml +9 -0
- package/vendor/rbxutil/ser/init.luau +175 -0
- package/vendor/rbxutil/ser/init.test.luau +50 -0
- package/vendor/rbxutil/ser/wally.toml +11 -0
- package/vendor/rbxutil/shake/index.d.ts +36 -0
- package/vendor/rbxutil/shake/init.luau +532 -0
- package/vendor/rbxutil/shake/init.test.luau +267 -0
- package/vendor/rbxutil/shake/package.json +16 -0
- package/vendor/rbxutil/shake/wally.toml +9 -0
- package/vendor/rbxutil/signal/index.d.ts +100 -0
- package/vendor/rbxutil/signal/init.luau +432 -0
- package/vendor/rbxutil/signal/init.test.luau +190 -0
- package/vendor/rbxutil/signal/package.json +17 -0
- package/vendor/rbxutil/signal/wally.toml +9 -0
- package/vendor/rbxutil/silo/TableWatcher.luau +65 -0
- package/vendor/rbxutil/silo/Util.luau +55 -0
- package/vendor/rbxutil/silo/init.luau +338 -0
- package/vendor/rbxutil/silo/init.test.luau +215 -0
- package/vendor/rbxutil/silo/wally.toml +8 -0
- package/vendor/rbxutil/spring/index.d.ts +40 -0
- package/vendor/rbxutil/spring/init.luau +97 -0
- package/vendor/rbxutil/spring/package.json +17 -0
- package/vendor/rbxutil/spring/wally.toml +8 -0
- package/vendor/rbxutil/stream/index.d.ts +88 -0
- package/vendor/rbxutil/stream/init.luau +597 -0
- package/vendor/rbxutil/stream/package.json +18 -0
- package/vendor/rbxutil/stream/wally.toml +9 -0
- package/vendor/rbxutil/streamable/Streamable.luau +202 -0
- package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -0
- package/vendor/rbxutil/streamable/init.luau +8 -0
- package/vendor/rbxutil/streamable/wally.toml +12 -0
- package/vendor/rbxutil/symbol/init.luau +56 -0
- package/vendor/rbxutil/symbol/init.test.luau +37 -0
- package/vendor/rbxutil/symbol/wally.toml +8 -0
- package/vendor/rbxutil/table-util/init.luau +938 -0
- package/vendor/rbxutil/table-util/init.test.luau +439 -0
- package/vendor/rbxutil/table-util/wally.toml +8 -0
- package/vendor/rbxutil/task-queue/index.d.ts +27 -0
- package/vendor/rbxutil/task-queue/init.luau +97 -0
- package/vendor/rbxutil/task-queue/wally.toml +8 -0
- package/vendor/rbxutil/timer/index.d.ts +81 -0
- package/vendor/rbxutil/timer/init.luau +249 -0
- package/vendor/rbxutil/timer/init.test.luau +73 -0
- package/vendor/rbxutil/timer/wally.toml +11 -0
- package/vendor/rbxutil/tree/index.d.ts +15 -0
- package/vendor/rbxutil/tree/init.luau +137 -0
- package/vendor/rbxutil/tree/wally.toml +8 -0
- package/vendor/rbxutil/trove/index.d.ts +46 -0
- package/vendor/rbxutil/trove/init.luau +787 -0
- package/vendor/rbxutil/trove/init.test.luau +203 -0
- package/vendor/rbxutil/trove/wally.toml +8 -0
- package/vendor/rbxutil/typed-remote/init.luau +196 -0
- package/vendor/rbxutil/typed-remote/wally.toml +8 -0
- package/vendor/rbxutil/wait-for/index.d.ts +17 -0
- package/vendor/rbxutil/wait-for/init.luau +257 -0
- package/vendor/rbxutil/wait-for/init.test.luau +182 -0
- package/vendor/rbxutil/wait-for/wally.toml +11 -0
- package/vendor/t/t.lua +1350 -0
- package/vendor/testez/Context.lua +26 -0
- package/vendor/testez/Expectation.lua +311 -0
- package/vendor/testez/ExpectationContext.lua +38 -0
- package/vendor/testez/LifecycleHooks.lua +89 -0
- package/vendor/testez/Reporters/TeamCityReporter.lua +102 -0
- package/vendor/testez/Reporters/TextReporter.lua +106 -0
- package/vendor/testez/Reporters/TextReporterQuiet.lua +97 -0
- package/vendor/testez/TestBootstrap.lua +147 -0
- package/vendor/testez/TestEnum.lua +28 -0
- package/vendor/testez/TestPlan.lua +304 -0
- package/vendor/testez/TestPlanner.lua +40 -0
- package/vendor/testez/TestResults.lua +112 -0
- package/vendor/testez/TestRunner.lua +188 -0
- package/vendor/testez/TestSession.lua +243 -0
- package/vendor/testez/init.lua +40 -0
|
@@ -0,0 +1,1103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roblox-gui
|
|
3
|
+
description: GUI systems, layout, responsiveness, cross-platform UI. ScreenGuis, UIListLayout, constraint-based design.
|
|
4
|
+
last_reviewed: 2026-05-22
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- Source: brockmartin/roblox-game-skill (MIT) -->
|
|
8
|
+
|
|
9
|
+
# Roblox GUI/UI Systems Reference
|
|
10
|
+
|
|
11
|
+
## 1. Overview
|
|
12
|
+
|
|
13
|
+
Load this reference when working on any UI-related task in Roblox:
|
|
14
|
+
|
|
15
|
+
- Building menus (main menu, pause menu, settings)
|
|
16
|
+
- HUDs (health bars, minimaps, ammo counters, score displays)
|
|
17
|
+
- Shops and inventory screens
|
|
18
|
+
- Notification and toast systems
|
|
19
|
+
- Dialog and popup windows
|
|
20
|
+
- Any 2D or 3D-attached interface elements
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick Reference
|
|
27
|
+
|
|
28
|
+
**Load Full Reference below only when you need specific layout examples or implementation patterns.**
|
|
29
|
+
|
|
30
|
+
Key rules:
|
|
31
|
+
- Mobile-first: design for phone, scale up. Touch targets minimum 48x48px.
|
|
32
|
+
- Scale (0-1 proportional) for position/size. Offset only for fixed padding/icons.
|
|
33
|
+
- Container Frame Rule: every logical group gets a Frame with layout modifier inside.
|
|
34
|
+
- UIListLayout/UIGridLayout: set on parent Frame, children auto-arrange. AutomaticSize on parent.
|
|
35
|
+
- ScreenGui.ResetOnSpawn = false for persistent UI. IgnoreGuiInset = true for fullscreen.
|
|
36
|
+
- ZIndex for layering within same ScreenGui. DisplayOrder for ScreenGui priority.
|
|
37
|
+
- Never use absolute pixel sizes for main containers. UISizeConstraint for min/max bounds.
|
|
38
|
+
- ScrollingFrame: set CanvasSize or AutomaticCanvasSize. UIListLayout inside for content.
|
|
39
|
+
- Common AI mistake: forgetting to set LayoutOrder on children when using layout modifiers.
|
|
40
|
+
- For complex stateful UI (shops, inventories, settings), see the `roblox-gui-fusion` skill.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Full Reference
|
|
45
|
+
|
|
46
|
+
## Design Guidelines
|
|
47
|
+
|
|
48
|
+
<!-- Guidelines sourced from Roblox DevForum, official docs, and community standards -->
|
|
49
|
+
|
|
50
|
+
### Container Frame Rule
|
|
51
|
+
<!-- Source: epochzx, Roblox DevForum -->
|
|
52
|
+
|
|
53
|
+
**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.
|
|
54
|
+
|
|
55
|
+
```luau
|
|
56
|
+
local container = Instance.new("Frame")
|
|
57
|
+
container.Name = "Container"
|
|
58
|
+
container.Size = UDim2.new(1, 0, 1, 0)
|
|
59
|
+
container.BackgroundTransparency = 1
|
|
60
|
+
container.BorderSizePixel = 0
|
|
61
|
+
container.Parent = screenGui
|
|
62
|
+
-- All UI children go under container, not directly under screenGui
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Scale vs Offset
|
|
66
|
+
<!-- Source: uiuxartist (Roblox Staff), DevForum -->
|
|
67
|
+
|
|
68
|
+
- **Scale** = percentage of parent (responsive). Use for Size and Position.
|
|
69
|
+
- **Offset** = fixed pixels. Use for pixel-perfect icons, small graphics, UIStroke.
|
|
70
|
+
- **UIStroke** does NOT support Scale - only Offset.
|
|
71
|
+
- **UICorner** DOES support Scale (`UDim.new(0.5, 0)` = 50% radius).
|
|
72
|
+
- **Hybrid pattern**: start pure Scale, add Offset for minimum size, reduce Scale.
|
|
73
|
+
|
|
74
|
+
```luau
|
|
75
|
+
-- Scale for responsive sizing
|
|
76
|
+
frame.Size = UDim2.new(0.5, 0, 0, 40) -- 50% width, 40px height
|
|
77
|
+
|
|
78
|
+
-- Offset for UIStroke (Scale not supported)
|
|
79
|
+
stroke.Thickness = 2 -- always Offset
|
|
80
|
+
|
|
81
|
+
-- UICorner supports Scale
|
|
82
|
+
corner.CornerRadius = UDim.new(0, 8) -- Offset
|
|
83
|
+
corner.CornerRadius = UDim.new(0.5, 0) -- Scale (50% of smallest axis)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Mobile-First Principles
|
|
87
|
+
<!-- Source: Roblox official docs - Adaptive Design Guidelines -->
|
|
88
|
+
<!-- https://create.roblox.com/docs/production/publishing/adaptive-design -->
|
|
89
|
+
|
|
90
|
+
- **50%+ of Roblox players are on mobile.** Design touch-first.
|
|
91
|
+
- Minimum touch target: **~0.15 width scale** (≈44-48px).
|
|
92
|
+
- Account for notches via `ScreenGui.ScreenInsets`.
|
|
93
|
+
- Don't place UI in the top 58px (Roblox top bar) or bottom virtual controls.
|
|
94
|
+
- **Test with Device Emulator** before publishing (View → Device Emulator).
|
|
95
|
+
|
|
96
|
+
### Typography
|
|
97
|
+
<!-- Source: PictureFolder, Roblox DevForum (119 likes) -->
|
|
98
|
+
<!-- "Designing UI - Tips and Best Practices" -->
|
|
99
|
+
|
|
100
|
+
- **Two fonts max**: Display (headers/buttons) + Body (descriptions).
|
|
101
|
+
- **Gotham** is the de facto modern Roblox font.
|
|
102
|
+
- **NEVER** pure white (`#FFFFFF`) on pure black (`#000000`). Use off-white (`#F0F0F0`) on dark gray (`#1E1E1E`).
|
|
103
|
+
- **Size hierarchy**: bigger/bolder = more important.
|
|
104
|
+
|
|
105
|
+
### Color
|
|
106
|
+
<!-- Source: DevForum "Modern UI Colour Schemes" -->
|
|
107
|
+
|
|
108
|
+
- Dark palette: `20,20,20` (Modern Black) to `35,35,35` (Very Light). Don't go lighter than 35.
|
|
109
|
+
- Grey base + accent colors for interactive elements.
|
|
110
|
+
- **Pick a palette and stick to it.** Consistency > variety.
|
|
111
|
+
|
|
112
|
+
### Common AI UI Mistakes
|
|
113
|
+
|
|
114
|
+
| Mistake | Fix |
|
|
115
|
+
|---------|-----|
|
|
116
|
+
| Elements directly under ScreenGui | Use a Container Frame child |
|
|
117
|
+
| Pure Scale only | Too small on mobile - add Offset minimums |
|
|
118
|
+
| Pure Offset only | Breaks on different resolutions |
|
|
119
|
+
| Pure white on pure black text | Use off-white on dark gray |
|
|
120
|
+
| Ignoring mobile players | 50%+ are mobile; design touch-first |
|
|
121
|
+
| Text-heavy UI for young audiences | Use icons, images, minimal text |
|
|
122
|
+
| UI overlapping top bar / chat / leaderboard | Respect safe areas (top 58px, bottom 100px) |
|
|
123
|
+
| Not testing with Device Emulator | Always test before publishing |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 2. GUI Hierarchy
|
|
128
|
+
|
|
129
|
+
### ScreenGui (2D Overlay)
|
|
130
|
+
|
|
131
|
+
The primary container for all 2D UI. Placed in `StarterGui`; Roblox copies it into each player's `PlayerGui` on spawn.
|
|
132
|
+
|
|
133
|
+
```luau
|
|
134
|
+
local Players = game:GetService("Players")
|
|
135
|
+
local player = Players.LocalPlayer
|
|
136
|
+
local playerGui = player:WaitForChild("PlayerGui")
|
|
137
|
+
|
|
138
|
+
local screenGui = Instance.new("ScreenGui")
|
|
139
|
+
screenGui.Name = "MainHUD"
|
|
140
|
+
screenGui.ResetOnSpawn = false -- survives respawn
|
|
141
|
+
screenGui.DisplayOrder = 10 -- higher renders on top
|
|
142
|
+
screenGui.IgnoreGuiInset = true -- extends behind top bar
|
|
143
|
+
screenGui.Parent = playerGui
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Key Properties:**
|
|
147
|
+
|
|
148
|
+
| Property | Purpose |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `DisplayOrder` | Controls layering. Higher values render on top of lower values. |
|
|
151
|
+
| `ResetOnSpawn` | `true` (default): destroyed and re-cloned on respawn. Set to `false` for persistent UI (shops, settings). |
|
|
152
|
+
| `IgnoreGuiInset` | `true`: UI extends behind the top bar (CoreGui area). Use for fullscreen overlays. |
|
|
153
|
+
| `Enabled` | Toggle visibility without destroying. |
|
|
154
|
+
|
|
155
|
+
### SurfaceGui (On Part Surfaces)
|
|
156
|
+
|
|
157
|
+
Renders UI on a Part's surface. Used for in-world signs, screens, control panels.
|
|
158
|
+
|
|
159
|
+
```luau
|
|
160
|
+
local surfaceGui = Instance.new("SurfaceGui")
|
|
161
|
+
surfaceGui.Face = Enum.NormalId.Front
|
|
162
|
+
surfaceGui.SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud
|
|
163
|
+
surfaceGui.PixelsPerStud = 50
|
|
164
|
+
surfaceGui.Parent = workspace.SignPart
|
|
165
|
+
-- Also set surfaceGui.Adornee = workspace.SignPart if parented elsewhere
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### BillboardGui (Floating in 3D)
|
|
169
|
+
|
|
170
|
+
Always faces the camera. Used for nametags, damage numbers, quest markers.
|
|
171
|
+
|
|
172
|
+
```luau
|
|
173
|
+
local billboardGui = Instance.new("BillboardGui")
|
|
174
|
+
billboardGui.Size = UDim2.new(0, 200, 0, 50)
|
|
175
|
+
billboardGui.StudsOffset = Vector3.new(0, 3, 0) -- above the part
|
|
176
|
+
billboardGui.AlwaysOnTop = false -- occluded by 3D geometry
|
|
177
|
+
billboardGui.MaxDistance = 100 -- hides beyond this range
|
|
178
|
+
billboardGui.Adornee = workspace.NPC.Head
|
|
179
|
+
billboardGui.Parent = workspace.NPC.Head
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Display Order Hierarchy
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
DisplayOrder 100 -- Modal dialogs (on top of everything)
|
|
186
|
+
DisplayOrder 50 -- Notifications / toasts
|
|
187
|
+
DisplayOrder 10 -- HUD elements
|
|
188
|
+
DisplayOrder 1 -- Background UI
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 3. Core UI Elements
|
|
194
|
+
|
|
195
|
+
### Frame
|
|
196
|
+
|
|
197
|
+
Container for grouping and styling. No text or image by default.
|
|
198
|
+
|
|
199
|
+
```luau
|
|
200
|
+
local frame = Instance.new("Frame")
|
|
201
|
+
frame.Size = UDim2.new(0.3, 0, 0.4, 0) -- 30% width, 40% height
|
|
202
|
+
frame.Position = UDim2.new(0.5, 0, 0.5, 0) -- centered (with AnchorPoint)
|
|
203
|
+
frame.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
204
|
+
frame.BackgroundColor3 = Color3.fromRGB(30, 30, 40)
|
|
205
|
+
frame.BackgroundTransparency = 0.1
|
|
206
|
+
frame.BorderSizePixel = 0
|
|
207
|
+
frame.Parent = screenGui
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### TextLabel / TextButton
|
|
211
|
+
|
|
212
|
+
```luau
|
|
213
|
+
local label = Instance.new("TextLabel")
|
|
214
|
+
label.Size = UDim2.new(1, 0, 0, 40)
|
|
215
|
+
label.Text = "Score: 0"
|
|
216
|
+
label.TextColor3 = Color3.fromRGB(255, 255, 255)
|
|
217
|
+
label.TextScaled = true
|
|
218
|
+
label.Font = Enum.Font.GothamBold
|
|
219
|
+
label.BackgroundTransparency = 1
|
|
220
|
+
label.Parent = frame
|
|
221
|
+
|
|
222
|
+
local button = Instance.new("TextButton")
|
|
223
|
+
button.Size = UDim2.new(0.5, 0, 0, 50)
|
|
224
|
+
button.Text = "Purchase"
|
|
225
|
+
button.TextColor3 = Color3.fromRGB(255, 255, 255)
|
|
226
|
+
button.BackgroundColor3 = Color3.fromRGB(0, 170, 80)
|
|
227
|
+
button.Font = Enum.Font.GothamBold
|
|
228
|
+
button.TextSize = 18
|
|
229
|
+
button.Parent = frame
|
|
230
|
+
|
|
231
|
+
button.Activated:Connect(function()
|
|
232
|
+
-- Activated works for mouse click, touch tap, and gamepad
|
|
233
|
+
end)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
> Use `Activated` instead of `MouseButton1Click` for cross-platform support.
|
|
237
|
+
|
|
238
|
+
### ImageLabel / ImageButton
|
|
239
|
+
|
|
240
|
+
```luau
|
|
241
|
+
local icon = Instance.new("ImageLabel")
|
|
242
|
+
icon.Size = UDim2.new(0, 64, 0, 64)
|
|
243
|
+
icon.Image = "rbxassetid://123456789"
|
|
244
|
+
icon.ScaleType = Enum.ScaleType.Fit
|
|
245
|
+
icon.BackgroundTransparency = 1
|
|
246
|
+
icon.Parent = frame
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### ScrollingFrame
|
|
250
|
+
|
|
251
|
+
See **section 14 (ScrollingFrame Patterns)** for full coverage including AutomaticCanvasSize, UIListLayout integration, and elastic overscroll.
|
|
252
|
+
|
|
253
|
+
### ViewportFrame
|
|
254
|
+
|
|
255
|
+
Renders 3D content inside a 2D GUI (item previews, character displays).
|
|
256
|
+
|
|
257
|
+
```luau
|
|
258
|
+
local viewport = Instance.new("ViewportFrame")
|
|
259
|
+
viewport.Size = UDim2.new(0, 200, 0, 200)
|
|
260
|
+
viewport.BackgroundColor3 = Color3.fromRGB(20, 20, 20)
|
|
261
|
+
viewport.Parent = frame
|
|
262
|
+
|
|
263
|
+
-- Clone a model into the viewport
|
|
264
|
+
local previewModel = workspace.SwordModel:Clone()
|
|
265
|
+
previewModel.Parent = viewport
|
|
266
|
+
|
|
267
|
+
-- Add a camera
|
|
268
|
+
local camera = Instance.new("Camera")
|
|
269
|
+
camera.CFrame = CFrame.new(Vector3.new(0, 2, 5), Vector3.new(0, 1, 0))
|
|
270
|
+
camera.Parent = viewport
|
|
271
|
+
viewport.CurrentCamera = camera
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Layout Modifiers
|
|
275
|
+
|
|
276
|
+
| Modifier | Purpose |
|
|
277
|
+
|---|---|
|
|
278
|
+
| `UIListLayout` | Arranges children in a vertical or horizontal list |
|
|
279
|
+
| `UIGridLayout` | Arranges children in a grid |
|
|
280
|
+
| `UIPageLayout` | Swipeable pages (one child visible at a time) |
|
|
281
|
+
| `UIPadding` | Inner padding on a container |
|
|
282
|
+
| `UICorner` | Rounded corners |
|
|
283
|
+
| `UIStroke` | Outline/border effect |
|
|
284
|
+
| `UIGradient` | Color gradient on an element |
|
|
285
|
+
| `UISizeConstraint` | Min/max pixel size |
|
|
286
|
+
| `UIAspectRatioConstraint` | Locks width/height ratio |
|
|
287
|
+
|
|
288
|
+
```luau
|
|
289
|
+
-- Rounded corners
|
|
290
|
+
local corner = Instance.new("UICorner")
|
|
291
|
+
corner.CornerRadius = UDim.new(0, 8)
|
|
292
|
+
corner.Parent = frame
|
|
293
|
+
|
|
294
|
+
-- Stroke/border
|
|
295
|
+
local stroke = Instance.new("UIStroke")
|
|
296
|
+
stroke.Color = Color3.fromRGB(255, 255, 255)
|
|
297
|
+
stroke.Thickness = 2
|
|
298
|
+
stroke.Transparency = 0.5
|
|
299
|
+
stroke.ApplyStrokeMode = Enum.ApplyStrokeMode.Border
|
|
300
|
+
stroke.Parent = frame
|
|
301
|
+
|
|
302
|
+
-- Gradient
|
|
303
|
+
local gradient = Instance.new("UIGradient")
|
|
304
|
+
gradient.Color = ColorSequence.new(
|
|
305
|
+
Color3.fromRGB(50, 50, 80),
|
|
306
|
+
Color3.fromRGB(20, 20, 40)
|
|
307
|
+
)
|
|
308
|
+
gradient.Rotation = 90
|
|
309
|
+
gradient.Parent = frame
|
|
310
|
+
|
|
311
|
+
-- Padding
|
|
312
|
+
local padding = Instance.new("UIPadding")
|
|
313
|
+
padding.PaddingLeft = UDim.new(0, 12)
|
|
314
|
+
padding.PaddingRight = UDim.new(0, 12)
|
|
315
|
+
padding.PaddingTop = UDim.new(0, 12)
|
|
316
|
+
padding.PaddingBottom = UDim.new(0, 12)
|
|
317
|
+
padding.Parent = frame
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 4. Layout Systems
|
|
323
|
+
|
|
324
|
+
### UIListLayout
|
|
325
|
+
|
|
326
|
+
Arranges children sequentially. Best for menus, sidebars, chat messages, vertical/horizontal lists.
|
|
327
|
+
|
|
328
|
+
```luau
|
|
329
|
+
local listLayout = Instance.new("UIListLayout")
|
|
330
|
+
listLayout.FillDirection = Enum.FillDirection.Vertical
|
|
331
|
+
listLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
|
|
332
|
+
listLayout.VerticalAlignment = Enum.VerticalAlignment.Top
|
|
333
|
+
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
334
|
+
listLayout.Padding = UDim.new(0, 8) -- gap between items
|
|
335
|
+
listLayout.Parent = frame
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Children are sorted by their `LayoutOrder` property (lower values first).
|
|
339
|
+
|
|
340
|
+
### UIGridLayout
|
|
341
|
+
|
|
342
|
+
Arranges children in rows and columns. Best for inventories, shops, icon grids.
|
|
343
|
+
|
|
344
|
+
```luau
|
|
345
|
+
local gridLayout = Instance.new("UIGridLayout")
|
|
346
|
+
gridLayout.CellSize = UDim2.new(0, 80, 0, 80)
|
|
347
|
+
gridLayout.CellPadding = UDim2.new(0, 8, 0, 8)
|
|
348
|
+
gridLayout.FillDirection = Enum.FillDirection.Horizontal
|
|
349
|
+
gridLayout.FillDirectionMaxCells = 4 -- 4 columns, then wrap
|
|
350
|
+
gridLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
351
|
+
gridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
|
|
352
|
+
gridLayout.Parent = scrollFrame
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### UIPageLayout
|
|
356
|
+
|
|
357
|
+
Shows one child at a time with animated page transitions. Best for tabbed menus, tutorials, onboarding flows.
|
|
358
|
+
|
|
359
|
+
```luau
|
|
360
|
+
local pageLayout = Instance.new("UIPageLayout")
|
|
361
|
+
pageLayout.Animated = true
|
|
362
|
+
pageLayout.EasingStyle = Enum.EasingStyle.Quad
|
|
363
|
+
pageLayout.EasingDirection = Enum.EasingDirection.InOut
|
|
364
|
+
pageLayout.TweenTime = 0.3
|
|
365
|
+
pageLayout.Circular = false -- cannot loop from last to first
|
|
366
|
+
pageLayout.Padding = UDim.new(0, 0)
|
|
367
|
+
pageLayout.Parent = frame
|
|
368
|
+
|
|
369
|
+
-- Navigate pages
|
|
370
|
+
pageLayout:JumpToIndex(0)
|
|
371
|
+
pageLayout:Next()
|
|
372
|
+
pageLayout:Previous()
|
|
373
|
+
pageLayout:JumpTo(someChildFrame)
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### When to Use Each
|
|
377
|
+
|
|
378
|
+
| Scenario | Layout |
|
|
379
|
+
|---|---|
|
|
380
|
+
| Vertical menu, chat log, leaderboard | `UIListLayout` |
|
|
381
|
+
| Inventory grid, shop items, emoji picker | `UIGridLayout` |
|
|
382
|
+
| Settings tabs, tutorial slides, shop categories | `UIPageLayout` |
|
|
383
|
+
| HUD element pinned to a corner | Absolute positioning (no layout) |
|
|
384
|
+
| Overlapping elements (health bar segments) | Absolute positioning |
|
|
385
|
+
|
|
386
|
+
### Absolute vs Layout-Driven
|
|
387
|
+
|
|
388
|
+
- **Absolute positioning**: Set `Position` and `Size` directly. Use for HUD elements pinned to specific screen locations.
|
|
389
|
+
- **Layout-driven**: Add a layout object as a child. The layout overrides children's `Position`. Use for dynamic lists where content count changes.
|
|
390
|
+
|
|
391
|
+
You can nest both: a frame with absolute position containing children managed by a UIListLayout.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## 5. Responsive Design
|
|
396
|
+
|
|
397
|
+
Scale vs Offset rules are in **Design Guidelines** above. This section covers constraints and adaptive patterns.
|
|
398
|
+
|
|
399
|
+
### UISizeConstraint
|
|
400
|
+
|
|
401
|
+
Prevents elements from becoming too small on phones or too large on ultrawide monitors.
|
|
402
|
+
|
|
403
|
+
```luau
|
|
404
|
+
local sizeConstraint = Instance.new("UISizeConstraint")
|
|
405
|
+
sizeConstraint.MinSize = Vector2.new(200, 150)
|
|
406
|
+
sizeConstraint.MaxSize = Vector2.new(600, 450)
|
|
407
|
+
sizeConstraint.Parent = frame
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### UIAspectRatioConstraint
|
|
411
|
+
|
|
412
|
+
Locks an element's aspect ratio so it does not stretch.
|
|
413
|
+
|
|
414
|
+
```luau
|
|
415
|
+
local aspect = Instance.new("UIAspectRatioConstraint")
|
|
416
|
+
aspect.AspectRatio = 16 / 9
|
|
417
|
+
aspect.AspectType = Enum.AspectType.FitWithinMaxSize
|
|
418
|
+
aspect.DominantAxis = Enum.DominantAxis.Width
|
|
419
|
+
aspect.Parent = frame
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Adapting to Screen Size
|
|
423
|
+
|
|
424
|
+
```luau
|
|
425
|
+
local camera = workspace.CurrentCamera
|
|
426
|
+
|
|
427
|
+
local function adaptUI()
|
|
428
|
+
local viewportSize = camera.ViewportSize
|
|
429
|
+
local isPortrait = viewportSize.Y > viewportSize.X
|
|
430
|
+
local isSmallScreen = viewportSize.X < 600
|
|
431
|
+
|
|
432
|
+
if isSmallScreen then
|
|
433
|
+
frame.Size = UDim2.new(0.95, 0, 0.8, 0) -- nearly fullscreen on mobile
|
|
434
|
+
elseif isPortrait then
|
|
435
|
+
frame.Size = UDim2.new(0.7, 0, 0.5, 0)
|
|
436
|
+
else
|
|
437
|
+
frame.Size = UDim2.new(0.3, 0, 0.4, 0) -- standard desktop
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
camera:GetPropertyChangedSignal("ViewportSize"):Connect(adaptUI)
|
|
442
|
+
adaptUI()
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## 6. Animation with TweenService
|
|
448
|
+
|
|
449
|
+
### TweenInfo
|
|
450
|
+
|
|
451
|
+
```luau
|
|
452
|
+
local TweenService = game:GetService("TweenService")
|
|
453
|
+
|
|
454
|
+
-- TweenInfo.new(time, easingStyle, easingDirection, repeatCount, reverses, delayTime)
|
|
455
|
+
local tweenInfo = TweenInfo.new(
|
|
456
|
+
0.5, -- duration in seconds
|
|
457
|
+
Enum.EasingStyle.Quad, -- easing curve
|
|
458
|
+
Enum.EasingDirection.Out, -- direction
|
|
459
|
+
0, -- repeat count (0 = no repeat, -1 = infinite)
|
|
460
|
+
false, -- reverses
|
|
461
|
+
0 -- delay before starting
|
|
462
|
+
)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**Common Easing Styles:**
|
|
466
|
+
|
|
467
|
+
| Style | Use Case |
|
|
468
|
+
|---|---|
|
|
469
|
+
| `Quad` | General-purpose, smooth |
|
|
470
|
+
| `Back` | Slight overshoot, bouncy buttons |
|
|
471
|
+
| `Elastic` | Springy, attention-grabbing |
|
|
472
|
+
| `Bounce` | Bouncing effect at the end |
|
|
473
|
+
| `Linear` | Constant speed, progress bars |
|
|
474
|
+
| `Sine` | Gentle, subtle motion |
|
|
475
|
+
| `Exponential` | Dramatic acceleration/deceleration |
|
|
476
|
+
|
|
477
|
+
### Tweening UI Properties
|
|
478
|
+
|
|
479
|
+
```luau
|
|
480
|
+
-- Slide in from the right
|
|
481
|
+
frame.Position = UDim2.new(1.5, 0, 0.5, 0)
|
|
482
|
+
|
|
483
|
+
local slideIn = TweenService:Create(frame, TweenInfo.new(0.4, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {
|
|
484
|
+
Position = UDim2.new(0.5, 0, 0.5, 0),
|
|
485
|
+
})
|
|
486
|
+
slideIn:Play()
|
|
487
|
+
|
|
488
|
+
-- Fade in
|
|
489
|
+
frame.BackgroundTransparency = 1
|
|
490
|
+
local fadeIn = TweenService:Create(frame, TweenInfo.new(0.3), {
|
|
491
|
+
BackgroundTransparency = 0,
|
|
492
|
+
})
|
|
493
|
+
fadeIn:Play()
|
|
494
|
+
|
|
495
|
+
-- Color transition
|
|
496
|
+
local colorShift = TweenService:Create(frame, TweenInfo.new(0.5), {
|
|
497
|
+
BackgroundColor3 = Color3.fromRGB(255, 50, 50),
|
|
498
|
+
})
|
|
499
|
+
colorShift:Play()
|
|
500
|
+
|
|
501
|
+
-- Size pulse
|
|
502
|
+
local pulse = TweenService:Create(button, TweenInfo.new(0.6, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true), {
|
|
503
|
+
Size = UDim2.new(0.55, 0, 0, 55),
|
|
504
|
+
})
|
|
505
|
+
pulse:Play()
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Chaining Tweens
|
|
509
|
+
|
|
510
|
+
```luau
|
|
511
|
+
local step1 = TweenService:Create(frame, TweenInfo.new(0.3), {
|
|
512
|
+
Position = UDim2.new(0.5, 0, 0.5, 0),
|
|
513
|
+
})
|
|
514
|
+
local step2 = TweenService:Create(frame, TweenInfo.new(0.2), {
|
|
515
|
+
Size = UDim2.new(0.4, 0, 0.5, 0),
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
step1.Completed:Connect(function()
|
|
519
|
+
step2:Play()
|
|
520
|
+
end)
|
|
521
|
+
step1:Play()
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Common UI Animation Recipes
|
|
525
|
+
|
|
526
|
+
```luau
|
|
527
|
+
-- Bounce entrance
|
|
528
|
+
local function bounceIn(element: GuiObject)
|
|
529
|
+
element.Size = UDim2.new(0, 0, 0, 0)
|
|
530
|
+
element.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
531
|
+
local tween = TweenService:Create(element, TweenInfo.new(0.5, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {
|
|
532
|
+
Size = UDim2.new(0.3, 0, 0.4, 0),
|
|
533
|
+
})
|
|
534
|
+
tween:Play()
|
|
535
|
+
return tween
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
-- Fade + scale dismiss
|
|
539
|
+
local function dismiss(element: GuiObject): RBXScriptSignal
|
|
540
|
+
local tween = TweenService:Create(element, TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.In), {
|
|
541
|
+
Size = UDim2.new(0, 0, 0, 0),
|
|
542
|
+
BackgroundTransparency = 1,
|
|
543
|
+
})
|
|
544
|
+
tween:Play()
|
|
545
|
+
return tween.Completed
|
|
546
|
+
end
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## 7. Input Handling
|
|
552
|
+
|
|
553
|
+
### UserInputService
|
|
554
|
+
|
|
555
|
+
Low-level input detection. Best for keyboard shortcuts, mouse tracking, detecting input type.
|
|
556
|
+
|
|
557
|
+
```luau
|
|
558
|
+
local UserInputService = game:GetService("UserInputService")
|
|
559
|
+
|
|
560
|
+
-- Keyboard input
|
|
561
|
+
UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessed: boolean)
|
|
562
|
+
if gameProcessed then return end -- ignore if typing in a TextBox, etc.
|
|
563
|
+
|
|
564
|
+
if input.KeyCode == Enum.KeyCode.E then
|
|
565
|
+
toggleInventory()
|
|
566
|
+
elseif input.KeyCode == Enum.KeyCode.Escape then
|
|
567
|
+
togglePauseMenu()
|
|
568
|
+
end
|
|
569
|
+
end)
|
|
570
|
+
|
|
571
|
+
-- Mouse position
|
|
572
|
+
local mousePos = UserInputService:GetMouseLocation() -- Vector2
|
|
573
|
+
|
|
574
|
+
-- Detect platform
|
|
575
|
+
local isMobile = UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled
|
|
576
|
+
local isConsole = UserInputService.GamepadEnabled
|
|
577
|
+
|
|
578
|
+
-- Hide mouse cursor
|
|
579
|
+
UserInputService.MouseIconEnabled = false
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### ContextActionService
|
|
583
|
+
|
|
584
|
+
Higher-level action binding. Automatically generates mobile buttons. Best for game actions (interact, reload, sprint).
|
|
585
|
+
|
|
586
|
+
```luau
|
|
587
|
+
local ContextActionService = game:GetService("ContextActionService")
|
|
588
|
+
|
|
589
|
+
local function onInteract(actionName: string, inputState: Enum.UserInputState, inputObject: InputObject)
|
|
590
|
+
if inputState == Enum.UserInputState.Begin then
|
|
591
|
+
interactWithNearestObject()
|
|
592
|
+
end
|
|
593
|
+
return Enum.ContextActionResult.Sink -- consume the input
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
-- Bind to E key, touch button auto-created on mobile
|
|
597
|
+
ContextActionService:BindAction("Interact", onInteract, true, Enum.KeyCode.E)
|
|
598
|
+
|
|
599
|
+
-- Customize the mobile button
|
|
600
|
+
ContextActionService:SetPosition("Interact", UDim2.new(0.8, 0, 0.5, 0))
|
|
601
|
+
ContextActionService:SetTitle("Interact", "E")
|
|
602
|
+
ContextActionService:SetImage("Interact", "rbxassetid://123456789")
|
|
603
|
+
|
|
604
|
+
-- Unbind when no longer needed
|
|
605
|
+
ContextActionService:UnbindAction("Interact")
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### When to Use Each
|
|
609
|
+
|
|
610
|
+
| Service | Best For |
|
|
611
|
+
|---|---|
|
|
612
|
+
| `UserInputService` | Global hotkeys, mouse tracking, detecting input device type, custom cursor |
|
|
613
|
+
| `ContextActionService` | In-game actions that need mobile buttons, context-sensitive controls (e.g., "interact" only near objects) |
|
|
614
|
+
| `GuiButton.Activated` | UI button clicks (already cross-platform) |
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## 8. Common UI Patterns (Skeletons)
|
|
619
|
+
|
|
620
|
+
### Shop Interface
|
|
621
|
+
|
|
622
|
+
**Problem:** Display purchasable items in a grid with hover feedback and server-validated purchase.
|
|
623
|
+
|
|
624
|
+
**Structure:**
|
|
625
|
+
- ScrollingFrame + UIGridLayout (container)
|
|
626
|
+
- Frame per item (card)
|
|
627
|
+
- ImageLabel (icon)
|
|
628
|
+
- TextLabel (name + price)
|
|
629
|
+
- TextButton (buy) with hover tween
|
|
630
|
+
|
|
631
|
+
**Key properties:** UIGridLayout.CellSize for responsive cards, UICorner for rounded edges, TweenService for hover color shift. Purchase via RemoteFunction (server validates, returns success/fail).
|
|
632
|
+
|
|
633
|
+
### Health Bar
|
|
634
|
+
|
|
635
|
+
**Problem:** Show player health with color thresholds and damage trail effect.
|
|
636
|
+
|
|
637
|
+
**Structure:**
|
|
638
|
+
- Frame (container, dark background)
|
|
639
|
+
- Frame (damage trail, red, tweens to match fill with delay)
|
|
640
|
+
- Frame (fill, green→yellow→red based on %)
|
|
641
|
+
|
|
642
|
+
**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.
|
|
643
|
+
|
|
644
|
+
### Notification Toast
|
|
645
|
+
|
|
646
|
+
**Problem:** Temporary message that slides in, stays briefly, slides out.
|
|
647
|
+
|
|
648
|
+
**Structure:**
|
|
649
|
+
- Frame (anchored off-screen right)
|
|
650
|
+
- TextLabel (message)
|
|
651
|
+
- UICorner + UIStroke
|
|
652
|
+
|
|
653
|
+
**Key logic:** Tween Position from off-screen to visible, task.delay(3), tween back out, Destroy. Queue multiple toasts with vertical offset.
|
|
654
|
+
|
|
655
|
+
### Dialog / Popup System
|
|
656
|
+
|
|
657
|
+
**Problem:** Modal dialog with backdrop, title, body, confirm/cancel buttons.
|
|
658
|
+
|
|
659
|
+
**Structure:**
|
|
660
|
+
- Frame (backdrop, full-screen, semi-transparent black)
|
|
661
|
+
- Frame (dialog card, centered, 0.4x0.3 scale)
|
|
662
|
+
- TextLabel (title)
|
|
663
|
+
- TextLabel (body)
|
|
664
|
+
- Frame (button row)
|
|
665
|
+
- TextButton (confirm)
|
|
666
|
+
- TextButton (cancel)
|
|
667
|
+
|
|
668
|
+
**Key logic:** Show/hide by toggling Visible + tween BackgroundTransparency on backdrop. Return result via Promise or callback. Destroy on close.
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
## 9. Best Practices
|
|
672
|
+
|
|
673
|
+
### Positioning and Sizing
|
|
674
|
+
|
|
675
|
+
- **Always use Scale** for `Position` and `Size` to support all screen resolutions.
|
|
676
|
+
- **Use Offset** only for small fixed-pixel elements (icon sizes, padding, border thickness).
|
|
677
|
+
- **Always set AnchorPoint** when centering elements: `AnchorPoint = Vector2.new(0.5, 0.5)`.
|
|
678
|
+
- **Use UISizeConstraint** to set minimum and maximum sizes so UI does not become unusable on small screens or absurdly large on ultrawide monitors.
|
|
679
|
+
|
|
680
|
+
### Style and Theme Consistency
|
|
681
|
+
|
|
682
|
+
- Define colors, fonts, and sizes as constants at the top of your module.
|
|
683
|
+
- Create a UI style/theme module that all scripts reference.
|
|
684
|
+
|
|
685
|
+
```luau
|
|
686
|
+
-- UITheme.luau (ModuleScript in ReplicatedStorage)
|
|
687
|
+
local Theme = {
|
|
688
|
+
Colors = {
|
|
689
|
+
Background = Color3.fromRGB(25, 25, 35),
|
|
690
|
+
Surface = Color3.fromRGB(40, 40, 55),
|
|
691
|
+
Primary = Color3.fromRGB(0, 150, 70),
|
|
692
|
+
Danger = Color3.fromRGB(200, 40, 40),
|
|
693
|
+
TextPrimary = Color3.fromRGB(255, 255, 255),
|
|
694
|
+
TextSecondary = Color3.fromRGB(180, 180, 190),
|
|
695
|
+
Accent = Color3.fromRGB(255, 215, 0),
|
|
696
|
+
},
|
|
697
|
+
Fonts = {
|
|
698
|
+
Title = Enum.Font.GothamBold,
|
|
699
|
+
Body = Enum.Font.Gotham,
|
|
700
|
+
Mono = Enum.Font.RobotoMono,
|
|
701
|
+
},
|
|
702
|
+
TextSizes = {
|
|
703
|
+
Title = 24,
|
|
704
|
+
Subtitle = 18,
|
|
705
|
+
Body = 14,
|
|
706
|
+
Small = 12,
|
|
707
|
+
},
|
|
708
|
+
CornerRadius = UDim.new(0, 8),
|
|
709
|
+
Padding = UDim.new(0, 12),
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return Theme
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### Accessibility
|
|
716
|
+
|
|
717
|
+
- Minimum text size of **14pt** for body text; 12pt only for tertiary labels.
|
|
718
|
+
- Maintain **high contrast** between text and background (white on dark, dark on light).
|
|
719
|
+
- Use `TextScaled = true` with `TextWrapped = true` sparingly; prefer explicit `TextSize` for control.
|
|
720
|
+
- Provide visual feedback on all interactive elements (hover color change, press scale).
|
|
721
|
+
- Support gamepad navigation with `GuiService:Select()` for console players.
|
|
722
|
+
|
|
723
|
+
### Performance: GUI Pooling
|
|
724
|
+
|
|
725
|
+
For scrolling lists with many items (inventory, leaderboard), reuse GUI elements instead of creating/destroying them.
|
|
726
|
+
|
|
727
|
+
```luau
|
|
728
|
+
local pool: { Frame } = {}
|
|
729
|
+
|
|
730
|
+
local function getCard(): Frame
|
|
731
|
+
local card = table.remove(pool)
|
|
732
|
+
if not card then
|
|
733
|
+
card = createNewCard() -- only create if pool is empty
|
|
734
|
+
end
|
|
735
|
+
card.Visible = true
|
|
736
|
+
return card
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
local function returnCard(card: Frame)
|
|
740
|
+
card.Visible = false
|
|
741
|
+
card.Parent = nil
|
|
742
|
+
table.insert(pool, card)
|
|
743
|
+
end
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### General
|
|
747
|
+
|
|
748
|
+
- Set `ScreenGui.Enabled = false` when a UI is not visible rather than destroying and recreating it.
|
|
749
|
+
- Use `Activated` on buttons instead of `MouseButton1Click` for cross-platform support.
|
|
750
|
+
- Disconnect event connections when UI is destroyed to prevent memory leaks.
|
|
751
|
+
- Keep UI logic in ModuleScripts; keep LocalScripts thin (just wiring).
|
|
752
|
+
- Always validate purchases on the server. The client UI is only for display.
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
## 10. Anti-Patterns
|
|
757
|
+
|
|
758
|
+
### Hardcoded pixel positions
|
|
759
|
+
|
|
760
|
+
```luau
|
|
761
|
+
-- BAD: breaks on different resolutions
|
|
762
|
+
frame.Position = UDim2.new(0, 500, 0, 300)
|
|
763
|
+
frame.Size = UDim2.new(0, 400, 0, 200)
|
|
764
|
+
|
|
765
|
+
-- GOOD: responsive
|
|
766
|
+
frame.Position = UDim2.new(0.5, 0, 0.5, 0)
|
|
767
|
+
frame.Size = UDim2.new(0.3, 0, 0.25, 0)
|
|
768
|
+
frame.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### Creating new GUIs every frame
|
|
772
|
+
|
|
773
|
+
```luau
|
|
774
|
+
-- BAD: creates garbage every frame, causes lag
|
|
775
|
+
RunService.RenderStepped:Connect(function()
|
|
776
|
+
local label = Instance.new("TextLabel")
|
|
777
|
+
label.Text = `Score: ${score}`
|
|
778
|
+
label.Parent = screenGui
|
|
779
|
+
end)
|
|
780
|
+
|
|
781
|
+
-- GOOD: update existing element
|
|
782
|
+
RunService.RenderStepped:Connect(function()
|
|
783
|
+
scoreLabel.Text = `Score: ${score}`
|
|
784
|
+
end)
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Not cleaning up event connections
|
|
788
|
+
|
|
789
|
+
```luau
|
|
790
|
+
-- BAD: connection persists after UI is removed, leaks memory
|
|
791
|
+
button.Activated:Connect(function()
|
|
792
|
+
doSomething()
|
|
793
|
+
end)
|
|
794
|
+
|
|
795
|
+
-- GOOD: store and disconnect
|
|
796
|
+
local connection = button.Activated:Connect(function()
|
|
797
|
+
doSomething()
|
|
798
|
+
end)
|
|
799
|
+
|
|
800
|
+
-- When done:
|
|
801
|
+
connection:Disconnect()
|
|
802
|
+
|
|
803
|
+
-- OR use :Once() for single-fire events
|
|
804
|
+
button.Activated:Once(function()
|
|
805
|
+
doSomething()
|
|
806
|
+
end)
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### Blocking the UI thread with yields
|
|
810
|
+
|
|
811
|
+
```luau
|
|
812
|
+
-- BAD: freezes the entire UI
|
|
813
|
+
button.Activated:Connect(function()
|
|
814
|
+
task.wait(5) -- nothing else can happen for 5 seconds
|
|
815
|
+
label.Text = "Done"
|
|
816
|
+
end)
|
|
817
|
+
|
|
818
|
+
-- GOOD: use task.delay or task.spawn for async work
|
|
819
|
+
button.Activated:Connect(function()
|
|
820
|
+
button.Active = false
|
|
821
|
+
task.spawn(function()
|
|
822
|
+
-- do async work
|
|
823
|
+
task.wait(5)
|
|
824
|
+
label.Text = "Done"
|
|
825
|
+
button.Active = true
|
|
826
|
+
end)
|
|
827
|
+
end)
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
### Trusting client UI for game logic
|
|
831
|
+
|
|
832
|
+
```luau
|
|
833
|
+
-- BAD: client decides if purchase succeeds
|
|
834
|
+
button.Activated:Connect(function()
|
|
835
|
+
coins -= item.price -- client-side deduction, exploitable
|
|
836
|
+
end)
|
|
837
|
+
|
|
838
|
+
-- GOOD: server validates everything
|
|
839
|
+
button.Activated:Connect(function()
|
|
840
|
+
local success = purchaseRemote:InvokeServer(itemId)
|
|
841
|
+
if success then
|
|
842
|
+
updateCoinsDisplay()
|
|
843
|
+
end
|
|
844
|
+
end)
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
### Ignoring mobile and gamepad
|
|
848
|
+
|
|
849
|
+
```luau
|
|
850
|
+
-- BAD: only works with mouse
|
|
851
|
+
button.MouseButton1Click:Connect(handler)
|
|
852
|
+
|
|
853
|
+
-- GOOD: works on mouse, touch, and gamepad
|
|
854
|
+
button.Activated:Connect(handler)
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
---
|
|
858
|
+
|
|
859
|
+
## 11. Mobile-First Design
|
|
860
|
+
|
|
861
|
+
78% of Roblox traffic is mobile. Design for touch first, adapt for desktop/gamepad.
|
|
862
|
+
|
|
863
|
+
### Touch Targets
|
|
864
|
+
|
|
865
|
+
- Minimum touch target: **44×44 px** (Apple guideline) or **48×48 dp** (Material Design)
|
|
866
|
+
- Buttons smaller than 44px are frustrating on phones
|
|
867
|
+
- Scale buttons 1.4× on touch devices:
|
|
868
|
+
|
|
869
|
+
```luau
|
|
870
|
+
local UserInputService = game:GetService("UserInputService")
|
|
871
|
+
|
|
872
|
+
local function isTouchDevice(): boolean
|
|
873
|
+
return UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
-- Apply at UI creation time
|
|
877
|
+
if isTouchDevice() then
|
|
878
|
+
button.Size = UDim2.new(button.Size.X.Scale * 1.4, 0, button.Size.Y.Scale * 1.4, 0)
|
|
879
|
+
end
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### PreferredInput API (2025+)
|
|
883
|
+
|
|
884
|
+
Modern approach to input detection. Use instead of checking individual booleans:
|
|
885
|
+
|
|
886
|
+
```luau
|
|
887
|
+
local preferred = UserInputService.PreferredInput
|
|
888
|
+
-- Enum.PreferredInput values:
|
|
889
|
+
-- Touch, MouseAndKeyboard, Gamepad, None
|
|
890
|
+
|
|
891
|
+
if preferred == Enum.PreferredInput.Touch then
|
|
892
|
+
-- enlarge buttons, simplify layout
|
|
893
|
+
elseif preferred == Enum.PreferredInput.Gamepad then
|
|
894
|
+
-- highlight focused element, add navigation hints
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
UserInputService:GetPropertyChangedSignal("PreferredInput"):Connect(function()
|
|
898
|
+
-- re-adapt UI when input method changes
|
|
899
|
+
end)
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
### Safe Areas and Screen Real Estate
|
|
903
|
+
|
|
904
|
+
- Top bar takes **58px**. Use `ScreenGui.IgnoreGuiInset = true` for fullscreen, but don't put critical content behind it.
|
|
905
|
+
- Bottom of screen has virtual controls on mobile. Keep interactive elements above the bottom 100px.
|
|
906
|
+
- Use the **Device Emulator** in Studio (View → Device Emulator) to test phone/tablet views before shipping.
|
|
907
|
+
|
|
908
|
+
### Mobile Layout Rules
|
|
909
|
+
|
|
910
|
+
- Prefer vertical layouts over horizontal on mobile (scrolling down is natural)
|
|
911
|
+
- Avoid sidebars - use bottom sheets or full-screen overlays instead
|
|
912
|
+
- Use `UIScale` to zoom entire UI proportionally on small screens:
|
|
913
|
+
|
|
914
|
+
```luau
|
|
915
|
+
local uiScale = Instance.new("UIScale")
|
|
916
|
+
uiScale.Scale = math.min(1, camera.ViewportSize.X / 1080) -- reference width
|
|
917
|
+
uiScale.Parent = screenGui
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
### Gamepad Navigation
|
|
921
|
+
|
|
922
|
+
For console players:
|
|
923
|
+
|
|
924
|
+
```luau
|
|
925
|
+
local GuiService = game:GetService("GuiService")
|
|
926
|
+
|
|
927
|
+
-- Set the initially selected object when opening a menu
|
|
928
|
+
GuiService.SelectedObject = myButton
|
|
929
|
+
|
|
930
|
+
-- On menu close, clear selection
|
|
931
|
+
GuiService.SelectedObject = nil
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
Source: Roblox Adaptive Design Guidelines (create.roblox.com/docs/production/publishing/adaptive-design)
|
|
935
|
+
|
|
936
|
+
---
|
|
937
|
+
|
|
938
|
+
## 12. Text Handling
|
|
939
|
+
|
|
940
|
+
### AutomaticSize vs TextScaled
|
|
941
|
+
|
|
942
|
+
**Do NOT use `TextScaled = true`.** It makes text unreadably small on mobile and truncates text that doesn't fit.
|
|
943
|
+
|
|
944
|
+
Use `AutomaticSize` instead. It resizes the UI element to fit the text at a consistent, readable font size:
|
|
945
|
+
|
|
946
|
+
```luau
|
|
947
|
+
-- BAD: text scales to fit, becomes unreadable on small screens
|
|
948
|
+
label.TextScaled = true
|
|
949
|
+
|
|
950
|
+
-- GOOD: label grows/shrinks to fit text at fixed readable size
|
|
951
|
+
label.TextSize = 16
|
|
952
|
+
label.TextWrapped = true
|
|
953
|
+
label.AutomaticSize = Enum.AutomaticSize.Y -- height adjusts to content
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
### UITextSizeConstraint
|
|
957
|
+
|
|
958
|
+
If you must use `TextScaled`, always add a constraint:
|
|
959
|
+
|
|
960
|
+
```luau
|
|
961
|
+
local textSizeConstraint = Instance.new("UITextSizeConstraint")
|
|
962
|
+
textSizeConstraint.MaxTextSize = 24
|
|
963
|
+
textSizeConstraint.MinTextSize = 12 -- NEVER below 9 (official hard rule)
|
|
964
|
+
textSizeConstraint.Parent = label
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
### Text Truncation
|
|
968
|
+
|
|
969
|
+
```luau
|
|
970
|
+
label.TextTruncate = Enum.TextTruncate.AtEnd -- shows "..." when text overflows
|
|
971
|
+
label.TextWrapped = true -- wrap instead of truncate for multi-line content
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
### Calculating Text Size Programmatically
|
|
975
|
+
|
|
976
|
+
```luau
|
|
977
|
+
local TextService = game:GetService("TextService")
|
|
978
|
+
|
|
979
|
+
local bounds = TextService:GetTextSize(
|
|
980
|
+
"Hello World",
|
|
981
|
+
16, -- TextSize
|
|
982
|
+
Enum.Font.Gotham,
|
|
983
|
+
Vector2.new(200, math.huge) -- max width, unlimited height
|
|
984
|
+
)
|
|
985
|
+
-- bounds.X = actual width, bounds.Y = actual height
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
Source: Roblox Size Modifiers & Constraints docs (create.roblox.com/docs/ui/size-modifiers)
|
|
989
|
+
|
|
990
|
+
---
|
|
991
|
+
|
|
992
|
+
## 13. UI Styling (StyleEditor)
|
|
993
|
+
|
|
994
|
+
Roblox ships a CSS-like styling system (2025+). Use it for consistent theming.
|
|
995
|
+
|
|
996
|
+
### Style Tokens
|
|
997
|
+
|
|
998
|
+
Named values (like CSS variables). Define once, reference everywhere.
|
|
999
|
+
|
|
1000
|
+
### Style Rules with Selectors
|
|
1001
|
+
|
|
1002
|
+
```luau
|
|
1003
|
+
-- Style rules can target by class, tag, name, state, modifier, query
|
|
1004
|
+
-- Similar to CSS selectors
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
### When to Use StyleEditor vs Code Themes
|
|
1008
|
+
|
|
1009
|
+
| Approach | Best For |
|
|
1010
|
+
|----------|----------|
|
|
1011
|
+
| Studio StyleEditor | Static UI built visually, team collaboration on design |
|
|
1012
|
+
| Code-based UITheme module | Dynamic UI built in code, programmatic theming |
|
|
1013
|
+
|
|
1014
|
+
Both can coexist. StyleEditor is the "build in Studio" answer; code themes are the "build in code" answer.
|
|
1015
|
+
|
|
1016
|
+
Source: Roblox UI Styling docs (create.roblox.com/docs/ui/styling)
|
|
1017
|
+
|
|
1018
|
+
---
|
|
1019
|
+
|
|
1020
|
+
## 14. ScrollingFrame Patterns
|
|
1021
|
+
|
|
1022
|
+
### AutomaticCanvasSize (Use This)
|
|
1023
|
+
|
|
1024
|
+
```luau
|
|
1025
|
+
-- BAD: manually calculating canvas size
|
|
1026
|
+
scrollFrame.CanvasSize = UDim2.new(0, 0, 0, listLayout.AbsoluteContentSize.Y)
|
|
1027
|
+
|
|
1028
|
+
-- GOOD: engine handles it automatically
|
|
1029
|
+
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
|
|
1030
|
+
scrollFrame.ScrollingDirection = Enum.ScrollingDirection.Y
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
### ScrollingFrame + UIListLayout
|
|
1034
|
+
|
|
1035
|
+
The most common pattern - a scrollable list:
|
|
1036
|
+
|
|
1037
|
+
```luau
|
|
1038
|
+
local scrollFrame = Instance.new("ScrollingFrame")
|
|
1039
|
+
scrollFrame.Size = UDim2.new(1, 0, 1, 0)
|
|
1040
|
+
scrollFrame.BackgroundTransparency = 1
|
|
1041
|
+
scrollFrame.ScrollBarThickness = 6
|
|
1042
|
+
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
|
|
1043
|
+
scrollFrame.ScrollingDirection = Enum.ScrollingDirection.Y
|
|
1044
|
+
scrollFrame.Parent = container
|
|
1045
|
+
|
|
1046
|
+
local listLayout = Instance.new("UIListLayout")
|
|
1047
|
+
listLayout.FillDirection = Enum.FillDirection.Vertical
|
|
1048
|
+
listLayout.Padding = UDim.new(0, 8)
|
|
1049
|
+
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
1050
|
+
listLayout.Parent = scrollFrame
|
|
1051
|
+
|
|
1052
|
+
-- Add items as children of scrollFrame
|
|
1053
|
+
-- They auto-arrange vertically, scrollFrame auto-sizes
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
### ScrollingFrame + UIGridLayout
|
|
1057
|
+
|
|
1058
|
+
For scrollable grids (inventory, shop):
|
|
1059
|
+
|
|
1060
|
+
```luau
|
|
1061
|
+
local scrollFrame = Instance.new("ScrollingFrame")
|
|
1062
|
+
scrollFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
|
|
1063
|
+
scrollFrame.ScrollingDirection = Enum.ScrollingDirection.Y
|
|
1064
|
+
scrollFrame.Parent = container
|
|
1065
|
+
|
|
1066
|
+
local gridLayout = Instance.new("UIGridLayout")
|
|
1067
|
+
gridLayout.CellSize = UDim2.new(0, 80, 0, 80)
|
|
1068
|
+
gridLayout.CellPadding = UDim2.new(0, 8, 0, 8)
|
|
1069
|
+
gridLayout.FillDirectionMaxCells = 4 -- 4 columns
|
|
1070
|
+
gridLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
1071
|
+
gridLayout.Parent = scrollFrame
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
### Elastic Overscroll
|
|
1075
|
+
|
|
1076
|
+
The bouncy effect on iOS/Android. Enabled by default. Disable if unwanted:
|
|
1077
|
+
|
|
1078
|
+
```luau
|
|
1079
|
+
scrollFrame.ElasticBehavior = Enum.ElasticBehavior.Never
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
Source: Roblox Scrolling Frames docs (create.roblox.com/docs/ui/scrolling-frames)
|
|
1083
|
+
|
|
1084
|
+
---
|
|
1085
|
+
|
|
1086
|
+
## Sources
|
|
1087
|
+
|
|
1088
|
+
- Roblox Adaptive Design Guidelines: create.roblox.com/docs/production/publishing/adaptive-design
|
|
1089
|
+
- Roblox Size Modifiers & Constraints: create.roblox.com/docs/ui/size-modifiers
|
|
1090
|
+
- Roblox UI Styling: create.roblox.com/docs/ui/styling
|
|
1091
|
+
- Roblox Scrolling Frames: create.roblox.com/docs/ui/scrolling-frames
|
|
1092
|
+
- Roblox List & Flex Layouts: create.roblox.com/docs/ui/list-flex-layouts
|
|
1093
|
+
- Roblox Grid & Table Layouts: create.roblox.com/docs/ui/grid-table-layouts
|
|
1094
|
+
- DevForum: Designing UI - Tips and Best Practices (Roblox Staff)
|
|
1095
|
+
- DevForum: Design Mobile First
|
|
1096
|
+
- DevForum: GUI Optimization Tips
|
|
1097
|
+
- DevForum: epochzx - Container Frame Rule for ScreenGui
|
|
1098
|
+
- DevForum: uiuxartist (Roblox Staff) - Scale vs Offset guidelines
|
|
1099
|
+
- DevForum: PictureFolder - UI Design Tips and Best Practices (119 likes)
|
|
1100
|
+
- DevForum: Modern UI Colour Schemes - dark palette guidelines
|
|
1101
|
+
- Fusion: github.com/dphfox/Fusion (MIT)
|
|
1102
|
+
- Vide: github.com/centau/vide (MIT)
|
|
1103
|
+
- brockmartin/roblox-game-skill (MIT) - base content
|