Cómo crear una caja de botín con animación de apertura

Claim Your Loot

·

Portada del artículo «Una caja de botín que engancha», de la sección Sistemas de botín

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

Paso 1: el orden correcto de las cosas

Esto es lo que más se hace al revés:

Orden maloOrden bueno
El cliente anima, y al final pide el premioEl servidor decide el premio
El servidor contesta lo que salgaSe lo manda al cliente
La animación ya ha terminado y hay que improvisarEl 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.

Las tres fases de la apertura y el momento en que el servidor decide el premio

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 Back la 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

ParteDónde vivePor qué
La tiradaServidorSi la hace el cliente, se da legendarios
La entrega del objetoServidorAntes de animar, para que no se pierda
La animaciónClienteVa suave y no gasta red
El sonidoClienteCada uno oye el suyo
El aviso a los demás («¡Fulano sacó un legendario!»)ServidorEs 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


 
Total: 2,05 s

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

Claim Your Loot

Explora otras secciones

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *