Como fazer um sistema de chocar ovos no Roblox?
Um ovo é três coisas: um preço, uma tabela de pets com peso e uma animação. O servidor cobra as moedas, sorteia na tabela, grava o pet no inventário do jogador e responde qual pet saiu. O cliente toca a rachadura e revela aquele pet. Nada é decidido no cliente.
Em resumo
- Cobre, sorteie e salve nesta ordem, tudo no servidor.
- Uma tabela com peso por ovo, para um ovo novo ser dado e não código.
- A animação de revelação lê o resultado; ela nunca o produz.
Chocar é uma compra com uma surpresa em cima. A surpresa é a parte divertida e a compra é a parte que precisa estar certa: as moedas saem da conta uma vez, o pet chega uma vez, e quem fecha o jogo no meio da animação continua com o pet quando voltar.
Passo a passo
1. Descreva o ovo como dado
Uma tabela por ovo, com preço e pets. Acrescentar o Ovo de Vulcão tem de ser oito linhas de dado, não um script novo.
local OVOS = {
basico = {
preco = 250,
pets = {
{ id = "dog", peso = 500 },
{ id = "cat", peso = 300 },
{ id = "bunny", peso = 150 },
{ id = "dragon", peso = 49 },
{ id = "void", peso = 1 },
},
},
vulcao = {
preco = 5000,
pets = {
{ id = "magma", peso = 600 },
{ id = "obsidian", peso = 300 },
{ id = "phoenix", peso = 95 },
{ id = "void", peso = 5 },
},
},
}
2. Sorteie na tabela
O mesmo sorteio com peso que a roleta usa. Deixe num lugar só: um ovo e uma roleta discordando sobre as chances é um bug que ninguém encontra.
local rng = Random.new()
local function sortearPet(pets): string
local total = 0
for _, p in pets do
total += p.peso
end
local bilhete = rng:NextInteger(1, total)
local soma = 0
for _, p in pets do
soma += p.peso
if bilhete <= soma then
return p.id
end
end
return pets[#pets].id
end
3. Cobre, salve e só então responda
A ordem é o projeto inteiro. Tire as moedas antes de sortear, para ninguém chocar de graça disparando o remote duas vezes; salve antes de responder, para o pet existir antes de o jogador ver.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStoreService = game:GetService("DataStoreService")
local pets = DataStoreService:GetDataStore("Pets_v1")
local chocar = Instance.new("RemoteEvent")
chocar.Name = "HatchEgg"
chocar.Parent = ReplicatedStorage
local ocupado: { [number]: boolean } = {}
chocar.OnServerEvent:Connect(function(player: Player, ovoId: unknown)
-- Nunca confie no id do ovo: ele veio do cliente.
local ovo = type(ovoId) == "string" and OVOS[ovoId] or nil
if not ovo then return end
-- Um choque por vez por jogador: dois remotes no mesmo tick leriam o mesmo
-- saldo de moedas e os dois passariam.
if ocupado[player.UserId] then return end
ocupado[player.UserId] = true
local ok = pcall(function()
local stats = player:FindFirstChild("leaderstats")
local moedas = stats and stats:FindFirstChild("Coins")
if not moedas or moedas.Value < ovo.preco then
error("sem moedas")
end
moedas.Value = moedas.Value - ovo.preco
local petId = sortearPet(ovo.pets)
pets:UpdateAsync("u_" .. player.UserId, function(antigo)
local tem = type(antigo) == "table" and antigo or {}
tem[petId] = (tem[petId] or 0) + 1
return tem
end)
chocar:FireClient(player, ovoId, petId)
end)
ocupado[player.UserId] = nil
if not ok then
chocar:FireClient(player, ovoId, nil)
end
end)
Players.PlayerRemoving:Connect(function(player)
ocupado[player.UserId] = nil
end)
4. Abra o ovo no cliente
A animação é só show. Ela recebe o id do pet e revela aquele modelo — se o jogador sair no meio, o pet já é dele.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local chocar = ReplicatedStorage:WaitForChild("HatchEgg")
local modelos = ReplicatedStorage:WaitForChild("PetModels")
chocar.OnClientEvent:Connect(function(_ovoId: string, petId: string?)
if not petId then return end -- recusado: faltavam moedas
local modelo = modelos:FindFirstChild(petId)
if not modelo then return end
local mostrado = modelo:Clone()
mostrado.Parent = workspace
mostrado:PivotTo(workspace.CurrentCamera.CFrame * CFrame.new(0, 0, -6))
for _, parte in mostrado:GetDescendants() do
if parte:IsA("BasePart") then
parte.Anchored = true
parte.CanCollide = false
parte.Transparency = 1
TweenService:Create(parte, TweenInfo.new(0.6), { Transparency = 0 }):Play()
end
end
task.delay(3, function()
mostrado:Destroy()
end)
end)
Onde guardar o pet que o jogador tem
| Onde | Sobrevive a reentrar | A salvo do cliente | Use quando |
|---|---|---|---|
| DataStore, contado por id de pet | sim | sim | O padrão: uma tabela { petId = quantos } |
| Uma pasta de valores sob o jogador | não | sim | Só como espelho ao vivo da tabela salva |
| Uma tabela no cliente | não | não | Nunca |
| Um DataStore ordenado | sim | sim | Só para um ranking de quantos foram chocados |
Perguntas
O pet é sorteado ao chocar ou ao comprar o ovo?
Ao chocar, que neste desenho é o mesmo instante. O que não pode é sortear quando o ovo aparece na loja: um resultado que existe antes de o jogador apertar o botão é um resultado que algo no cliente consegue ler.
Por que às vezes o jogador é cobrado e não recebe o pet?
Porque a cobrança e o save são duas gravações separadas e uma delas falhou. O pcall acima põe as duas num bloco e recusa o choque inteiro, então um save que falha devolve as moedas por nunca ter confirmado o sorteio. Se você separar, o bug é seu.
Como mostrar as chances?
Imprima a partir da mesma tabela que o sorteio usa — peso / total em porcentagem, com uma casa. Digitar as porcentagens na interface na mão é como um pet de 1% vira silenciosamente um pet de 0,2% depois de alguém acrescentar um sexto pet.
Dá para usar um script de ovo para dez ovos?
É exatamente o motivo do passo 1. O script recebe um id de ovo, procura na tabela e sorteia. Dez ovos são dez entradas em OVOS, e um ovo novo não consegue trazer um bug novo porque não há código novo.
Atualizado: Testado no Studio: Roblox Studio, outubro de 2026Autor: GustavoLicença: O código deste guia pode ser usado em qualquer experiência do Roblox, inclusive paga
Pack relacionado
