§ How to · with Picoo

How to make a main menu and loading screen in Roblox

By Sametcan Tasgiran, Founder & Developer·Published ·Updated

Two UI bugs account for most of the time people lose on Roblox menus: the menu that disappears when you respawn, and the loading screen that never replaces Roblox's own.

Both are one property and one location — and once they are right, the rest is layout.

Persistent menu

ResetOnSpawn handled so the menu survives death and respawn.

Real loading screen

Roblox's default screen removed from ReplicatedFirst and replaced with your own.

Asset preloading

ContentProvider:PreloadAsync with genuine progress, not a fake timer.

Phone-safe scaling

UIScale and aspect-ratio constraints so a 6-inch screen is not a wall of text.

Camera framing

A scripted menu camera looking at the map instead of a spawned character.

Settings and credits

Sub-panels with a single navigation stack rather than a pile of visibility toggles.

Files Picoo ships for this prompt

2 files · 190 lines · ~30s · 1 credit

MenuController

Panel stack, camera framing, button wiring, play transition.

130 lines

LoadingScreen

ReplicatedFirst loading screen with real preload progress.

60 lines

Sample output: ReplicatedFirst.LoadingScreen

-- A custom loading screen ONLY works from ReplicatedFirst. That container is
-- replicated before anything else, which is the entire point: by the time a
-- LocalScript in StarterPlayerScripts runs, the default screen has already gone.
local ReplicatedFirst = game:GetService("ReplicatedFirst")
ReplicatedFirst:RemoveDefaultLoadingScreen()

local gui = script:WaitForChild("LoadingGui"):Clone()
gui.Parent = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui")

-- Real progress beats a fake timer: PreloadAsync yields per asset, so the bar
-- tracks actual work and the screen closes when the game is genuinely ready.
local assets = collectAssets()
local done = 0
for _, asset in assets do
	pcall(function() ContentProvider:PreloadAsync({ asset }) end)
	done += 1
	bar.Size = UDim2.fromScale(done / #assets, 1)
end

--------------------------------------------------------------------
-- And the menu itself, in StarterGui:
-- ScreenGui.ResetOnSpawn defaults to TRUE. Every death destroys and recreates
-- the GUI, so state resets and any connection you made points at a dead
-- instance. This is the "my menu disappeared" bug, every time.
menuGui.ResetOnSpawn = false
menuGui.IgnoreGuiInset = true     -- draw under the top bar, not below it

Building a main menu and loading screen in Roblox

A main menu is the first thing a player sees and the first thing that breaks in a way nobody explains properly.

Start with the disappearing menu. ScreenGui has a property called ResetOnSpawn, and it defaults to true. That means every time the player dies, the GUI is destroyed and re-cloned from StarterGui. Your carefully built panel stack is reset, and worse, every connection you made now points at an instance that no longer exists — so the buttons stop responding even though the menu looks fine. One property fixes it, and it is the single most common Roblox UI bug there is.

The loading screen has a similar shape: right code, wrong place. Roblox will let you remove its default loading screen, but only from ReplicatedFirst, which is delivered ahead of everything else. A LocalScript in StarterPlayerScripts runs after the default screen has already come and gone, so the call succeeds and appears to do nothing. Put the script and its GUI in ReplicatedFirst and it works immediately.

While you are there, make the progress real. PreloadAsync yields per asset, so stepping a bar through your asset list reflects genuine loading. A bar tweened over three seconds is a decoration that lies on slow connections — it completes while the game is still fetching, which is exactly when the player needed it to be honest.

The last piece is scaling, and it is worth caring about because most Roblox players are on phones. Scale-based sizing keeps layout proportional; Offset keeps small details from vanishing. Mixing them deliberately — Scale for containers, Offset for padding and strokes — plus a UIScale you can tune per device gives you a menu that reads on a 6-inch screen without looking sparse on a monitor.

See more on the Luau generator, the game builder, or browse the full blog.

Frequently asked

Why does my GUI disappear when I respawn?+

ScreenGui.ResetOnSpawn is true by default. On every respawn the GUI is destroyed and a fresh copy is cloned from StarterGui, so your state is lost and every connection you made is now attached to a destroyed instance. Set it to false for anything that should persist across deaths.

Where does a custom loading screen go?+

In ReplicatedFirst, as a LocalScript, calling ReplicatedFirst:RemoveDefaultLoadingScreen(). ReplicatedFirst is delivered before the rest of the game, which is why it is the only place that can replace the default screen. The same code in StarterPlayerScripts runs too late to remove anything.

Should I use Scale or Offset for sizing?+

Scale for layout, Offset for things that must not shrink — a 4-pixel border stays 4 pixels. Sizing everything in Offset produces a menu that fits your monitor and overflows a phone; sizing everything in Scale produces text too small to read on that same phone. UIScale plus a UIAspectRatioConstraint gives you one dial to tune per device class.

How do I show the map behind the menu?+

Set the camera to Scriptable and place its CFrame somewhere flattering before the character exists. Loading into a spawn point and covering the character with UI works, but the character is simulated, other players can see it standing there, and it drops into the world during the menu.

How do I know preloading is real?+

PreloadAsync yields until each asset resolves, so advancing your bar one asset at a time reflects actual work. A bar animated over three fixed seconds says nothing about readiness — and on a slow connection it finishes while the game is still loading, which is the worst of both.

Related Picoo prompts