Una caja que te da el objeto al instante es matemáticamente idéntica a una que se abre con suspense. Y sin embargo la segunda engancha diez veces más. Toda la diferencia está en unos segundos de espera bien puestos.
Este artículo va de esos segundos: cuántos, en qué orden, y —lo más importante— quién decide el premio y cuándo.
En la demo puedes mover los tiempos de cada fase y abrir la caja las veces que quieras hasta que te guste cómo se siente.
Respuesta rápida
El servidor tira los dados primero y le manda al cliente el resultado. La animación solo enseña algo que ya está decidido. Tres fases: anticipación (la caja tiembla), apertura (la tapa salta) y revelado (aparece el objeto), con un total de entre 1,5 y 3 segundos.
Lo que necesitas antes de empezar
- La tabla de pesos: rarezas con probabilidades reales.
- Saber mover cosas con TweenService: cómo mover objetos con TweenService.
- Unos 40 minutos.
Paso 1: el orden correcto de las cosas
Esto es lo que más se hace al revés:
| Orden malo | Orden bueno |
|---|---|
| El cliente anima, y al final pide el premio | El servidor decide el premio |
| El servidor contesta lo que salga | Se lo manda al cliente |
| La animación ya ha terminado y hay que improvisar | El cliente anima sabiendo qué va a salir |
Con el orden bueno, la animación puede adaptarse: si va a salir un legendario, alarga un poco el temblor y cambia el color de la luz. Con el orden malo, la sorpresa te pilla a ti también.
Y hay un motivo de seguridad: si el premio se decide al final, el cliente puede cortar la comunicación cuando no le guste el resultado. Decidido y guardado antes de animar, no hay nada que cancelar.

Paso 2: el servidor decide
local evento = game.ReplicatedStorage:WaitForChild("AbrirCaja")
local BOTIN = {
{id = "comun", peso = 600, color = Color3.fromRGB(150, 150, 160)},
{id = "raro", peso = 250, color = Color3.fromRGB(80, 150, 220)},
{id = "epico", peso = 120, color = Color3.fromRGB(160, 100, 230)},
{id = "legendario",peso = 30, color = Color3.fromRGB(240, 170, 60)},
}
local function tirar()
local total = 0
for _, o in ipairs(BOTIN) do total = total + o.peso end
local tirada = math.random() * total
local acumulado = 0
for _, o in ipairs(BOTIN) do
acumulado = acumulado + o.peso
if tirada <= acumulado then return o end
end
return BOTIN[1]
end
evento.OnServerEvent:Connect(function(jugador)
if not tieneLlave(jugador) then return end
gastarLlave(jugador)
local premio = tirar()
darObjeto(jugador, premio.id) -- ya es suyo, antes de la animación
evento:FireClient(jugador, premio.id, premio.color)
end)
El objeto se entrega antes de animar. Si al jugador se le va internet en mitad de la animación, al volver lo tiene. Es lo contrario de lo que hace mucha gente, y es lo correcto.
Paso 3: los tres tiempos
En el cliente, las tres fases con TweenService:
local TweenService = game:GetService("TweenService")
local ANTICIPACION = 0.9 -- la caja tiembla y crece un poco
local APERTURA = 0.35 -- la tapa salta
local REVELADO = 0.8 -- el objeto sube y gira
local function abrir(caja, color)
-- 1. Anticipación: la caja se hincha despacio
local crecer = TweenService:Create(caja,
TweenInfo.new(ANTICIPACION, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut),
{Size = caja.Size * 1.12})
crecer:Play()
crecer.Completed:Wait()
-- 2. Apertura: la tapa sale disparada
local tapa = caja.Tapa
local salto = TweenService:Create(tapa,
TweenInfo.new(APERTURA, Enum.EasingStyle.Back, Enum.EasingDirection.Out),
{CFrame = tapa.CFrame * CFrame.new(0, 3, 0) * CFrame.Angles(0.6, 0, 0.3)})
salto:Play()
-- 3. Revelado: el objeto sube desde dentro
local objeto = Instance.new("Part")
objeto.Size = Vector3.new(1.2, 1.2, 1.2)
objeto.Color = color
objeto.Material = Enum.Material.Neon
objeto.Anchored = true
objeto.CanCollide = false
objeto.CFrame = caja.CFrame
objeto.Parent = workspace
TweenService:Create(objeto,
TweenInfo.new(REVELADO, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
{CFrame = caja.CFrame * CFrame.new(0, 3.5, 0) * CFrame.Angles(0, math.pi, 0)}):Play()
end
Los números de arriba no son sagrados, pero el reparto sí importa:
- La anticipación es la fase larga. Es donde está la tensión. Menos de medio segundo y no da tiempo a sentir nada.
- La apertura es un golpe seco. Cuanto más corta, más fuerte se siente. Con
Backla tapa se pasa de rosca y vuelve: es el detalle que hace que parezca real. - El revelado tiene que dejarte mirar. Si el objeto aparece y desaparece en 0,2 s, el jugador no llega a verlo.
Total: menos de tres segundos. Por encima de eso, cansa a la décima caja, y tus jugadores van a abrir cientos.
Paso 4: que la rareza se note antes de verla
El truco que usan los juegos que lo bordan: si el premio es bueno, la anticipación se alarga y cambia de color.
local EXTRA = {
comun = 0, raro = 0.2, epico = 0.5, legendario = 1.1,
}
local function abrirConDrama(caja, id, color)
local extra = EXTRA[id] or 0
local luz = caja:FindFirstChildOfClass("PointLight")
if luz then
luz.Color = color
TweenService:Create(luz, TweenInfo.new(ANTICIPACION + extra), {Brightness = 8}):Play()
end
-- ...y la anticipación dura ANTICIPACION + extra
end
Cuando el temblor se alarga y la luz se vuelve dorada, el jugador ya sabe que le ha tocado algo, y ese medio segundo vale más que el objeto. Es honesto: el resultado ya estaba decidido, solo se está contando mejor.
Paso 5: dejar saltarse la animación
Obligatorio si el jugador va a abrir cajas a puñados:
local saltando = false
boton.Activated:Connect(function() saltando = true end)
-- dentro de cada fase
if saltando then
tween:Cancel()
ponerEstadoFinal()
end
Y para diez cajas de golpe, una pantalla con los diez resultados a la vez. Lo que no puedes es hacerle ver treinta segundos de animaciones.
Paso 6: dónde va cada cosa
| Parte | Dónde vive | Por qué |
|---|---|---|
| La tirada | Servidor | Si la hace el cliente, se da legendarios |
| La entrega del objeto | Servidor | Antes de animar, para que no se pierda |
| La animación | Cliente | Va suave y no gasta red |
| El sonido | Cliente | Cada uno oye el suyo |
| El aviso a los demás («¡Fulano sacó un legendario!») | Servidor | Es información pública |
Ese último es el más fácil de olvidar y el que más juego da: un anuncio en el chat cuando alguien saca algo muy raro hace que el resto quiera abrir cajas.
Ajústalo aquí
Mueve los tres tiempos hasta que te guste
Anticipación, apertura y revelado. Ábrela las veces que quieras: el premio ya está decidido antes de que empiece la animación.
Tus tiempos, en código
Errores habituales
La animación va a tirones
La estás haciendo en el servidor. Las animaciones de interfaz y de efectos van en el cliente, siempre.
Se ve el premio antes de que se abra la tapa
El objeto está creado desde el principio con transparencia 0. Créalo dentro de la caja o con Transparency = 1 y anímalo.
Si el jugador se desconecta a mitad, pierde el objeto
Estás entregando al final. Entrega en el servidor antes de mandar el aviso al cliente.
Abrir diez cajas tarda medio minuto
Falta el botón de saltar y la apertura múltiple.
La tapa se queda flotando
Cancel() deja el tween a medias, pero no coloca el objeto. Después de cancelar, pon el estado final a mano.
La caja se queda hinchada
La animación de anticipación cambia Size y nadie lo devuelve. Guarda el tamaño original antes de empezar y restáuralo al final.
Preguntas frecuentes
¿Cuánto debería durar la animación entera?
Entre 1,5 y 3 segundos. Más corta se siente barata; más larga se siente lenta a partir de la quinta.
¿Puedo usar una animación de verdad en vez de tweens?
Sí, si tienes un modelo con huesos. Para una caja normal, los tweens son más fáciles y quedan igual.
¿Y si quiero una ruleta de las que pasan objetos de lado?
Es el mismo principio: el servidor decide el premio, y la ruleta se frena justo en el premio que ya salió. Nunca al revés.
¿Vale la pena poner partículas?
Solo para las rarezas altas. Si todas las cajas echan confeti, el confeti deja de significar nada.
Última revisión: 1 de septiembre de 2026





Deja una respuesta