Como fazer uma recompensa diária no Roblox?
Recompensa diária é uma data salva, não um cronômetro. Guarde os.time() no instante em que o jogador resgata e, em toda entrada, compare com agora: se passaram mais de 24 horas, o botão volta a valer. A sequência é um segundo número que você zera quando o intervalo passa de 48 horas.
Em resumo
- Guarde a hora do resgate, nunca uma contagem. Contagem morre junto com o servidor.
- 24 horas desde o último resgate liberam o botão; 48 horas quebram a sequência.
- Quem decide a recompensa é o servidor. O cliente só pede.
Uma recompensa diária faz uma pergunta só: quanto tempo desde o último resgate deste jogador? Todo o resto — a sequência, o prêmio que cresce, o botão brilhando — sai desse único número. O jogador fecha o jogo, o servidor desliga, passa uma semana: uma data salva sobrevive a tudo isso e um cronômetro rodando não sobrevive a nada.
Passo a passo
1. Uma entrada de DataStore por jogador
Guarde a hora do resgate e a sequência juntas, numa tabela. Um UpdateAsync escreve as duas, então o jogador nunca termina com hora nova e sequência velha.
local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("DailyReward_v1")
local DIA = 24 * 60 * 60
local function carregar(userId: number)
local ok, dados = pcall(function()
return store:GetAsync("u_" .. userId)
end)
if not ok or type(dados) ~= "table" then
return { lastClaim = 0, streak = 0 }
end
return dados
end
2. Pergunte se o botão está valendo
A resposta é uma subtração. Escreva ela uma vez, no servidor, e deixe o botão e o resgate lerem a mesma função — duas cópias dessa regra é como o jogador ganha um botão que não faz nada.
local function situacao(dados)
local desde = os.time() - (dados.lastClaim or 0)
return {
canClaim = desde >= DIA,
-- Intervalo maior que dois dias significa que ele perdeu um dia inteiro:
-- a sequência acabou.
streakAlive = desde < 2 * DIA,
secondsLeft = math.max(0, DIA - desde),
}
end
3. Resgate no servidor, nunca no cliente
O cliente aperta um botão; o servidor decide quanto aquilo vale. Recompensa calculada no cliente é recompensa que qualquer um reescreve.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local evento = Instance.new("RemoteEvent")
evento.Name = "ClaimDaily"
evento.Parent = ReplicatedStorage
local PREMIOS = { 50, 75, 100, 150, 250, 400, 1000 }
evento.OnServerEvent:Connect(function(player: Player)
local dados = carregar(player.UserId)
local s = situacao(dados)
if not s.canClaim then return end
local sequencia = s.streakAlive and (dados.streak or 0) + 1 or 1
local dia = math.min(sequencia, #PREMIOS)
local valor = PREMIOS[dia]
local ok = pcall(function()
store:UpdateAsync("u_" .. player.UserId, function()
return { lastClaim = os.time(), streak = sequencia }
end)
end)
-- Nada é pago antes de a gravação dar certo: prêmio dado num save que falhou
-- é prêmio que o jogador recebe de novo na próxima entrada.
if not ok then return end
local stats = player:FindFirstChild("leaderstats")
local moedas = stats and stats:FindFirstChild("Coins")
if moedas then
moedas.Value = moedas.Value + valor
end
evento:FireClient(player, { claimed = true, day = dia, amount = valor })
end)
4. Diga ao cliente o que desenhar
Mande a situação na entrada e deixe a interface desenhar. O cliente nunca calcula a liberação; ele é informado.
Players.PlayerAdded:Connect(function(player: Player)
local dados = carregar(player.UserId)
task.defer(function()
evento:FireClient(player, situacao(dados))
end)
end)
Qual relógio comparar
| O que você compara | Sobrevive a um restart | Sobrevive ao fuso | Use quando |
|---|---|---|---|
Diferença de os.time() | sim | sim | Quase sempre: "24 horas desde o seu resgate" |
Dia do calendário (os.date("*t")) | sim | não — é o dia em UTC do servidor, não o do jogador | Você quer "um resgate por dia do calendário" e aceita UTC |
tick() / os.clock() | não | — | Nunca para isto: os dois zeram quando o servidor zera |
Um while rodando | não | — | Nunca. O jogador fecha o jogo e o seu cronômetro morre |
Perguntas
Por que a minha recompensa zera a cada restart do servidor?
Porque você está medindo com algo que também zera. tick(), os.clock() e um contador while true do task.wait(1) end começam do zero junto com o servidor. os.time() é o relógio de verdade, em segundos desde 1970, e é o mesmo número em todos os servidores da sua experiência.
Como impedir que alguém resgate duas vezes entrando de novo?
A checagem é no servidor e a data gravada é a única autoridade. Entrar de novo relê o mesmo lastClaim, então desde continua abaixo de um dia e o resgate sai na primeira linha. Nunca guarde canClaim numa variável do cliente — essa, sim, zera quando ele entra de novo.
UpdateAsync ou SetAsync?
UpdateAsync aqui. Dois servidores podem segurar o mesmo jogador por um instante num teleporte, e com SetAsync o segundo sobrescreve o primeiro. UpdateAsync lê e escreve como uma só operação, e é isso que impede a sequência de andar para trás.
Onde colocar a sequência na tela?
Do lado do botão, como o número em si — "Dia 4" — e o prêmio do dia 5 apagado ao lado. Sequência que o jogador não vê é sequência que não muda o que ele faz amanhã.
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
