Como fazer uma roleta no Roblox?
Sorteie o prêmio primeiro e anime depois. O servidor tira um número com peso, decide qual fatia ganhou e manda esse índice para o cliente; o cliente gira a roda para parar nela. Animar primeiro e ler o ângulo depois é como uma roleta vira algo pagável pelo cliente.
Em resumo
- Pesos, não fatias iguais: um prêmio de 1 em 200 é peso 1 contra 199.
- Quem sorteia é o servidor. O giro é a animação de uma decisão já tomada.
- Faça a roda parar sempre na fatia que o servidor escolheu, nunca o contrário.
Toda roleta que já foi explorada foi feita na ordem errada: gira a roda, vê onde parou, paga aquilo. Faça ao contrário e não há o que explorar — o cliente está só reproduzindo um resultado que ele não consegue mudar.
Passo a passo
1. Escreva os prêmios como pesos
Peso é "quantos bilhetes este prêmio tem no saco". Lê melhor que porcentagem porque acrescentar um prêmio não obriga você a recalcular todos os outros números.
local PREMIOS = {
{ nome = "50 moedas", peso = 400, moedas = 50 },
{ nome = "150 moedas", peso = 250, moedas = 150 },
{ nome = "500 moedas", peso = 100, moedas = 500 },
{ nome = "2.000 moedas", peso = 40, moedas = 2000 },
{ nome = "Pet raro", peso = 9, pet = "Rare" },
{ nome = "Pet mítico", peso = 1, pet = "Mythic" },
}
local TOTAL = 0
for _, p in PREMIOS do
TOTAL += p.peso
end
2. Sorteie no servidor
Um objeto Random para o servidor inteiro, não um novo por giro: um Random.new() criado dentro de um laço, com semente do relógio, pode dar o mesmo prêmio a dois jogadores no mesmo tick.
local rng = Random.new()
local function sortear(): number
local bilhete = rng:NextInteger(1, TOTAL)
local soma = 0
for i, p in PREMIOS do
soma += p.peso
if bilhete <= soma then
return i
end
end
return #PREMIOS
end
3. Pague e só então diga ao cliente onde parar
O que viaja é o índice, não o prêmio: o cliente já tem a lista, e mandar o pagamento é convidar alguém a mudá-lo no caminho de volta.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local giro = Instance.new("RemoteEvent")
giro.Name = "SpinWheel"
giro.Parent = ReplicatedStorage
local espera: { [number]: number } = {}
giro.OnServerEvent:Connect(function(player: Player)
-- O único limite que importa: o cliente dispara um RemoteEvent na
-- velocidade que quiser, então recusar um giro tem de ser barato.
local agora = os.clock()
if espera[player.UserId] and agora - espera[player.UserId] < 3 then return end
espera[player.UserId] = agora
local indice = sortear()
local premio = PREMIOS[indice]
if premio.moedas then
local stats = player:FindFirstChild("leaderstats")
local moedas = stats and stats:FindFirstChild("Coins")
if moedas then
moedas.Value = moedas.Value + premio.moedas
end
end
giro:FireClient(player, indice)
end)
Players.PlayerRemoving:Connect(function(player)
espera[player.UserId] = nil
end)
4. Gire a roda até aquela fatia
O ângulo é aritmética: as voltas inteiras para o show, mais o deslocamento da fatia vencedora. O TweenService com um ease-out longo faz o resto.
local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local roda = script.Parent -- um ImageLabel com as fatias desenhadas
local FATIAS = 6
local giro = ReplicatedStorage:WaitForChild("SpinWheel")
giro.OnClientEvent:Connect(function(indice: number)
local porFatia = 360 / FATIAS
-- Quatro voltas inteiras e, no fim, a fatia vencedora no topo.
local destino = 360 * 4 + (360 - (indice - 1) * porFatia)
local tween = TweenService:Create(
roda,
TweenInfo.new(4, Enum.EasingStyle.Quint, Enum.EasingDirection.Out),
{ Rotation = destino }
)
tween:Play()
tween.Completed:Wait()
roda.Rotation = destino % 360 -- deixa o número pequeno para o próximo giro
end)
Onde decidir o prêmio
| Onde o sorteio acontece | O jogador consegue mudar | Parece instantâneo | Veredito |
|---|---|---|---|
| Servidor, antes da animação | não | sim, o giro cobre a espera | É o que fazer |
| Cliente, servidor só paga | sim — inteiramente | sim | Nunca: o pagamento é uma variável do cliente |
| Servidor, lendo o ângulo final | não, mas frágil | não | Evite: um erro de arredondamento paga a fatia errada |
| Servidor com espera por jogador | não | sim | Igual à primeira linha, com um piso contra spam |
Perguntas
Como fazer um prêmio de exatamente 0,5%?
Dê peso 1 a ele e faça os outros somarem 199. Porcentagem é o que você imprime na interface; peso é o que o código sorteia. Manter os dois separados deixa um designer acrescentar um prêmio sem refazer seis números.
A roda precisa ter fatias iguais?
Visualmente não, e é melhor que não tenha — uma fatia desenhada como um risco fino já comunica raridade. O que importa é que a fatia no topo quando o tween para seja a que o servidor escolheu, então a geometria desenhada e a ordem dos índices têm de bater.
Dois jogadores podem tirar o mítico no mesmo instante?
Podem, e está certo: cada giro é um sorteio próprio. Se você quer um prêmio que exista uma vez só na experiência inteira, isso não é problema de roleta — é um contador de estoque num DataStore, reduzido com UpdateAsync antes de o prêmio ser anunciado.
Por que a minha roda dá um salto antes de girar?
Porque Rotation ficou num número tipo 1.448 do giro anterior e o tween novo começa dali. Normalize com % 360 quando o tween terminar, como acima.
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
