In short
Keep each egg's price and its pets with their weights in a ModuleScript named EggConfig in ReplicatedStorage. Tag each egg model Egg and give it an EggName attribute. A Script adds a ProximityPrompt to every egg; on Triggered it checks the player's coins, subtracts the price, rolls a pet with weighted random, adds it to the player's Pets folder and fires a RemoteEvent so the client plays the hatch animation.
- The server rolls. If the client chose the pet, a cheater would hatch legendaries every time.
- Take the coins and give the pet in the same step, with no wait in between.
- The client may read EggConfig to show the odds; changing its copy changes nothing on the server.
Get the script
Egg hatching server script with weighted odds
A Script that adds a hatch prompt to every egg, takes the price in coins, rolls a pet by weight on the server and adds it to the player's Pets folder.
-- Script in ServerScriptService named EggHatching.
-- Every Model tagged "Egg" gets a ProximityPrompt. The server checks the price,
-- takes the coins, rolls the pet and adds it to the player's Pets folder.
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
type PetChance = { pet: string, weight: number, rarity: string }
type Egg = { price: number, pets: { PetChance } }
local EggConfig = require(ReplicatedStorage:WaitForChild("EggConfig")) :: { [string]: Egg }
local HATCH_COOLDOWN = 3.5 -- seconds between hatches, the length of the animation
local hatchedEvent = Instance.new("RemoteEvent")
hatchedEvent.Name = "EggHatched"
hatchedEvent.Parent = ReplicatedStorage
local random = Random.new()
local lastHatch: { [Player]: number } = {}
local function roll(egg: Egg): PetChance
local total = 0
for _, chance in egg.pets do
total += chance.weight
end
local pick = random:NextNumber(0, total)
for _, chance in egg.pets do
pick -= chance.weight
if pick <= 0 then
return chance
end
end
return egg.pets[#egg.pets]
end
local function hatch(player: Player, eggName: string)
local egg = EggConfig[eggName]
local leaderstats = player:FindFirstChild("leaderstats")
local coins = leaderstats and leaderstats:FindFirstChild("Coins")
local pets = player:FindFirstChild("Pets")
if not (egg and coins and coins:IsA("IntValue") and pets) then
return
end
local now = os.clock()
if now - (lastHatch[player] or -math.huge) < HATCH_COOLDOWN then
return
end
if coins.Value < egg.price then
hatchedEvent:FireClient(player, eggName, nil)
return
end
lastHatch[player] = now
coins.Value -= egg.price -- take the price before rolling, in the same step
local result = roll(egg)
local owned = pets:FindFirstChild(result.pet)
if owned and owned:IsA("IntValue") then
owned.Value += 1
else
local newPet = Instance.new("IntValue")
newPet.Name = result.pet
newPet.Value = 1
newPet.Parent = pets
end
hatchedEvent:FireClient(player, eggName, result.pet, result.rarity)
end
local function setUpEgg(eggModel: Instance)
local eggName = eggModel:GetAttribute("EggName")
if typeof(eggName) ~= "string" or not EggConfig[eggName] then
warn("EggHatching: give", eggModel:GetFullName(), "an EggName attribute that matches EggConfig")
return
end
local prompt = Instance.new("ProximityPrompt")
prompt.ActionText = string.format("Hatch (%d coins)", EggConfig[eggName].price)
prompt.ObjectText = eggName
prompt.HoldDuration = 0.3
prompt.MaxActivationDistance = 10
prompt.Parent = if eggModel:IsA("Model") then eggModel.PrimaryPart or eggModel else eggModel
prompt.Triggered:Connect(function(player: Player)
hatch(player, eggName)
end)
end
for _, egg in CollectionService:GetTagged("Egg") do
setUpEgg(egg)
end
CollectionService:GetInstanceAddedSignal("Egg"):Connect(setUpEgg)
Players.PlayerAdded:Connect(function(player: Player)
local pets = Instance.new("Folder")
pets.Name = "Pets"
pets.Parent = player
end)
Players.PlayerRemoving:Connect(function(player: Player)
lastHatch[player] = nil
end)
RoCode builds it in the place you have open in Roblox Studio, on Windows or macOS with a desktop browser; on a phone the button sends the prompt and the link to your computer. Free to start, with free credits every day. More Roblox Studio scripts
Where to put it
ReplicatedStorage
EggConfig ModuleScript new
ServerScriptService
EggHatching Script new
StarterPlayer
StarterPlayerScripts
HatchAnimation LocalScript new
Workspace
BasicEgg Model (tag: Egg, EggName = BasicEgg)
Settings you can change
HATCH_COOLDOWNIn the script- Seconds between hatches for one player, about the length of the animation. Default:
3.5. EggNameAttribute- On each egg model: which egg in EggConfig it is, such as
BasicEgg. Default: none.
Players need a Coins value in leaderstats, as in the coin collecting guide. Prices and odds live in the EggConfig module from Step 1.
What a cheater can and cannot doThe server checks the egg exists, the player can pay and the cooldown, then takes the coins and rolls the pet itself. A cheater can trigger the prompt from anywhere their character can reach, but cannot hatch for free or pick the pet.
Type-checked with luau-lsp strict against Roblox's API definitions on 23 October 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.
Prompt for RoCode
Add egg hatching: an EggConfig ModuleScript in ReplicatedStorage with egg prices and pets with weights and rarities, a server Script that puts a ProximityPrompt on every model tagged Egg, takes the price from the Coins leaderstat, rolls a pet by weight and adds it to a Pets folder on the player, and a LocalScript that shakes an egg on screen and reveals the pet in its rarity colour.
On this page
How egg hatching works
- The player walks up to an egg and holds E (or taps the prompt on a phone).
- The ProximityPrompt's
Triggeredevent fires on the server with the player. - The server checks the player has enough Coins and is off cooldown, takes the price, rolls a pet and adds it to their Pets folder.
- The server fires
EggHatchedto that player with the pet's name and rarity, and their screen plays the animation.
A weighted roll adds up all the weights, picks a random number between 0 and that total, then walks down the list subtracting each pet's weight until it drops to 0 or below. With weights 50, 35, 12 and 3, the Dragon comes up 3 times in 100 on average.
Step 1: Prices and odds in one module
Insert a ModuleScript in ReplicatedStorage named EggConfig:
-- ModuleScript in ReplicatedStorage named EggConfig.
-- Prices and odds for every egg. The server rolls with these numbers; the client
-- only reads them to show the odds.
export type PetChance = { pet: string, weight: number, rarity: string }
export type Egg = { price: number, pets: { PetChance } }
local EggConfig: { [string]: Egg } = {
BasicEgg = {
price = 100,
pets = {
{ pet = "Dog", weight = 50, rarity = "Common" },
{ pet = "Cat", weight = 35, rarity = "Common" },
{ pet = "Bunny", weight = 12, rarity = "Rare" },
{ pet = "Dragon", weight = 3, rarity = "Legendary" },
},
},
GoldEgg = {
price = 1000,
pets = {
{ pet = "Fox", weight = 60, rarity = "Rare" },
{ pet = "Unicorn", weight = 35, rarity = "Epic" },
{ pet = "Phoenix", weight = 5, rarity = "Legendary" },
},
},
}
return EggConfig
Each egg has a price and a list of pets with a weight and a rarity. The weights in one egg do not have to add to 100. If you show the odds in a menu, divide each weight by the egg's total.
Step 2: The hatching server
- Build an egg: a Model with an egg-shaped part as its PrimaryPart. Tag the model
Eggand add a String attributeEggNameset toBasicEgg. - Insert a Script in ServerScriptService named
EggHatching:
-- Script in ServerScriptService named EggHatching.
-- Every Model tagged "Egg" gets a ProximityPrompt. The server checks the price,
-- takes the coins, rolls the pet and adds it to the player's Pets folder.
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
type PetChance = { pet: string, weight: number, rarity: string }
type Egg = { price: number, pets: { PetChance } }
local EggConfig = require(ReplicatedStorage:WaitForChild("EggConfig")) :: { [string]: Egg }
local HATCH_COOLDOWN = 3.5 -- seconds between hatches, the length of the animation
local hatchedEvent = Instance.new("RemoteEvent")
hatchedEvent.Name = "EggHatched"
hatchedEvent.Parent = ReplicatedStorage
local random = Random.new()
local lastHatch: { [Player]: number } = {}
local function roll(egg: Egg): PetChance
local total = 0
for _, chance in egg.pets do
total += chance.weight
end
local pick = random:NextNumber(0, total)
for _, chance in egg.pets do
pick -= chance.weight
if pick <= 0 then
return chance
end
end
return egg.pets[#egg.pets]
end
local function hatch(player: Player, eggName: string)
local egg = EggConfig[eggName]
local leaderstats = player:FindFirstChild("leaderstats")
local coins = leaderstats and leaderstats:FindFirstChild("Coins")
local pets = player:FindFirstChild("Pets")
if not (egg and coins and coins:IsA("IntValue") and pets) then
return
end
local now = os.clock()
if now - (lastHatch[player] or -math.huge) < HATCH_COOLDOWN then
return
end
if coins.Value < egg.price then
hatchedEvent:FireClient(player, eggName, nil)
return
end
lastHatch[player] = now
coins.Value -= egg.price -- take the price before rolling, in the same step
local result = roll(egg)
local owned = pets:FindFirstChild(result.pet)
if owned and owned:IsA("IntValue") then
owned.Value += 1
else
local newPet = Instance.new("IntValue")
newPet.Name = result.pet
newPet.Value = 1
newPet.Parent = pets
end
hatchedEvent:FireClient(player, eggName, result.pet, result.rarity)
end
local function setUpEgg(eggModel: Instance)
local eggName = eggModel:GetAttribute("EggName")
if typeof(eggName) ~= "string" or not EggConfig[eggName] then
warn("EggHatching: give", eggModel:GetFullName(), "an EggName attribute that matches EggConfig")
return
end
local prompt = Instance.new("ProximityPrompt")
prompt.ActionText = string.format("Hatch (%d coins)", EggConfig[eggName].price)
prompt.ObjectText = eggName
prompt.HoldDuration = 0.3
prompt.MaxActivationDistance = 10
prompt.Parent = if eggModel:IsA("Model") then eggModel.PrimaryPart or eggModel else eggModel
prompt.Triggered:Connect(function(player: Player)
hatch(player, eggName)
end)
end
for _, egg in CollectionService:GetTagged("Egg") do
setUpEgg(egg)
end
CollectionService:GetInstanceAddedSignal("Egg"):Connect(setUpEgg)
Players.PlayerAdded:Connect(function(player: Player)
local pets = Instance.new("Folder")
pets.Name = "Pets"
pets.Parent = player
end)
Players.PlayerRemoving:Connect(function(player: Player)
lastHatch[player] = nil
end)
The prompt shows the egg's name and price, and needs a short 0.3 second hold so players do not buy by accident. hatch checks everything first, then takes the price, rolls and adds the pet, all without yielding. Owned pets are IntValues in a Pets folder, one per kind, whose value counts how many the player has.
Step 3: The hatch animation
Insert a LocalScript in StarterPlayer > StarterPlayerScripts named HatchAnimation:
-- LocalScript in StarterPlayerScripts named HatchAnimation.
-- Shows the egg shaking, then the pet's name in its rarity colour.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local hatchedEvent = ReplicatedStorage:WaitForChild("EggHatched") :: RemoteEvent
local RARITY_COLORS: { [string]: Color3 } = {
Common = Color3.fromRGB(220, 220, 220),
Rare = Color3.fromRGB(80, 160, 255),
Epic = Color3.fromRGB(180, 90, 255),
Legendary = Color3.fromRGB(255, 190, 40),
}
local gui = Instance.new("ScreenGui")
gui.Name = "HatchGui"
gui.ResetOnSpawn = false
gui.Enabled = false
gui.Parent = Players.LocalPlayer:WaitForChild("PlayerGui")
local egg = Instance.new("TextLabel")
egg.AnchorPoint = Vector2.new(0.5, 0.5)
egg.Position = UDim2.fromScale(0.5, 0.45)
egg.Size = UDim2.fromOffset(160, 200)
egg.BackgroundColor3 = Color3.fromRGB(250, 240, 220)
egg.Text = ""
egg.Parent = gui
local eggCorner = Instance.new("UICorner")
eggCorner.CornerRadius = UDim.new(0.5, 0)
eggCorner.Parent = egg
local result = Instance.new("TextLabel")
result.AnchorPoint = Vector2.new(0.5, 0)
result.Position = UDim2.fromScale(0.5, 0.65)
result.Size = UDim2.fromOffset(400, 60)
result.BackgroundTransparency = 1
result.Font = Enum.Font.GothamBlack
result.TextSize = 34
result.TextStrokeTransparency = 0.4
result.Parent = gui
local busy = false
hatchedEvent.OnClientEvent:Connect(function(eggName: string, petName: string?, rarity: string?)
if not petName then
-- The server said no: not enough coins
result.Text = "Not enough coins for " .. eggName
result.TextColor3 = Color3.fromRGB(255, 110, 110)
gui.Enabled = true
egg.Visible = false
task.wait(1.5)
gui.Enabled = false
egg.Visible = true
return
end
if busy then
return
end
busy = true
gui.Enabled = true
egg.Visible = true
egg.Size = UDim2.fromOffset(160, 200)
result.Text = ""
-- Shake: rotate left and right a few times, faster each time
for i = 1, 6 do
local angle = if i % 2 == 0 then 12 else -12
local shake = TweenService:Create(egg, TweenInfo.new(0.25 - i * 0.025), { Rotation = angle })
shake:Play()
shake.Completed:Wait()
end
-- Pop: the egg grows and disappears, the pet's name appears
local pop = TweenService:Create(egg, TweenInfo.new(0.2), { Size = UDim2.fromOffset(240, 300), Rotation = 0 })
pop:Play()
pop.Completed:Wait()
egg.Visible = false
result.Text = string.format("%s! (%s)", petName, rarity or "")
result.TextColor3 = RARITY_COLORS[rarity or ""] or Color3.new(1, 1, 1)
task.wait(2)
gui.Enabled = false
busy = false
end)
The egg is a rounded TextLabel that tweens its Rotation left and right six times, each shake shorter, then grows and disappears as the pet's name appears in its rarity colour. If the server refused the hatch, the same screen says the player cannot afford it.
Test it
- Give yourself coins for the test: in the server view during Play, set your Coins in leaderstats to 1000.
- Walk to the egg and hold E: 100 coins are taken, the egg shakes and a pet appears.
- Look in your Player in the Explorer: the Pets folder has the new pet with a count.
- Spend down to below 100 and try again: "Not enough coins".
Common errors and fixes
Watch the Output window for warnings from EggHatching.
EggHatching: give ... an EggName attribute that matches EggConfig
The egg model needs a String attribute EggName spelled exactly like a key in EggConfig, such as BasicEgg.
No prompt appears on the egg
Check the model is tagged Egg and has a PrimaryPart. Prompts show when you are within 10 studs.
It always says not enough coins
The player needs a Coins IntValue inside a leaderstats folder, set by the server. Coins you change in a LocalScript only exist on your screen.
Hatching twice quickly shows only one animation
That is the cooldown and the animation guard. Raise or lower HATCH_COOLDOWN to match your animation.
Build it with RoCode
If you would rather not set this up by hand, RoCode can build it in the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the eggs and scripts in your Explorer.
Add three eggs (100, 1000 and 10000 coins) with 4 pets each from common to legendary, hatched on the server, with an egg shake animation, and make the pets follow the player.
- Builds the egg models, the odds module and the server script, and wires them to your existing Coins
- Can insert a ready-made egg hatching screen from its premium UI kits in one step, animated, in one of five themes (candy, neon, gold, blocky, pastel), with working server scripts
- Is told to compile-check each script with Check Script, and sets undo points around each batch
After a build, RoCode can run its test robot: it plays your game in Studio, walks to the egg, presses keys and clicks buttons, and reports PASS or FAIL. It checks only what it chose to test. RoCode cannot create developer products for Robux eggs: make those in Creator Hub. How RoCode connects to Studio.
Questions
How do I make an egg hatching system in Roblox?
Put egg prices and pet weights in a module, roll the pet on the server when a ProximityPrompt is triggered, take the price in the same step, and tell the client to play an animation. Steps 1 to 3 have the scripts.
How do egg odds work?
Each pet has a weight. Its chance is its weight divided by the total of all weights in that egg.
Why must the hatch roll be on the server?
Anything on the client can be changed by a cheater. If the client rolled or named the pet, they could hatch whatever they wanted.
How do I make the hatched pet follow me?
Clone a pet model with the same name from ServerStorage and attach it to the player with the pet follow guide's script.
Sources and further reading
- Roblox Creator Docs: Proximity prompts. adding prompts and the Triggered event
- Roblox Creator Docs: ProximityPrompt. ActionText, HoldDuration and MaxActivationDistance
- Roblox Creator Docs: Random. NextNumber for the roll
- Roblox Creator Docs: TweenService. the shake and pop
- Roblox Creator Docs: Paid random items policy. eggs bought with Robux or Robux-bought currency, and odds disclosure
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 23 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.