Crea una tienda pequeña que vende una linterna por 40 monedas. El botón envía únicamente el identificador del artículo; el servidor decide su precio, comprueba el saldo y entrega la herramienta. Así puedes conectar una interfaz a una acción de juego sin confiar en números enviados por el cliente.
Última revisión: 2 de octubre de 2026. Referencia: Roblox Studio y APIs actuales del Creator Hub. Código revisado y analizado sintácticamente; no ejecutado en Roblox Studio durante esta revisión. Studio se actualiza automáticamente: aquí no se atribuye un número de compilación que no se haya comprobado.
Construye los objetos en Studio
Instala Studio desde Roblox Creator Hub y abre una Baseplate. Activa Explorer y Properties. No necesitas plugins ni modelos de terceros.
En ReplicatedStorage crea una Folder llamada ShopRemotes y, dentro, dos RemoteEvent: BuyItem y PurchaseResult. En ServerStorage crea una Folder ShopTools. Dentro añade un Tool Linterna que contenga un Part llamado exactamente Handle: tamaño aproximado 1, 1, 1, Anchored desactivado y CanCollide desactivado. Añade un PointLight dentro de Handle, con Brightness 2 y Range 16. Conserva Archivable activado en el Tool.
La herramienta está en ServerStorage para que el cliente no pueda obtenerla directamente desde el catálogo. Handle es el nombre que Tool utiliza para sostenerla. Al equiparla, su luz acompañará al personaje.
En StarterGui crea un ScreenGui ShopGui, con ResetOnSpawn desactivado. Dentro añade un TextButton BuyButton, visible, con tamaño 240 por 50 píxeles y texto «Comprar linterna · 40». Usa por ejemplo posición X 20, Y 80 para no colocarlo bajo la barra superior.
El saldo vive en el servidor
Añade un Script ShopServer en ServerScriptService y pega esta preparación:
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local remotes = ReplicatedStorage:WaitForChild("ShopRemotes")
local buy = remotes:WaitForChild("BuyItem")
local result = remotes:WaitForChild("PurchaseResult")
local tools = ServerStorage:WaitForChild("ShopTools")
local catalog = { lantern = { price = 40, tool = "Linterna" } }
local sessions = {}
local function setup(player)
if sessions[player] then return end
local stats = Instance.new("Folder")
stats.Name = "leaderstats"
stats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 100
coins.Parent = stats
sessions[player] = { balance = 100, coins = coins, owned = {}, last = -math.huge }
end
Players.PlayerAdded:Connect(setup)
for _, player in Players:GetPlayers() do setup(player) end
Players.PlayerRemoving:Connect(function(player)
sessions[player] = nil
end)Este ejemplo es para una experiencia vacía: si ya tienes leaderstats o economía, intégralo con ese sistema en lugar de crear otro. Coins muestra el saldo, pero la regla utiliza balance en la tabla del servidor. Los jugadores empiezan con 100 por sesión; no se utiliza DataStore ni se guarda progreso al salir.
Añade a continuación, en el mismo Script, el procesamiento de la compra:
buy.OnServerEvent:Connect(function(player, itemId)
local state = sessions[player]
if not state then return end
local now = os.clock()
if now - state.last < 0.75 then return end
state.last = now
if typeof(itemId) ~= "string" or #itemId > 32 then return end
local item = catalog[itemId]
if not item then
result:FireClient(player, "Artículo no disponible")
return
end
if state.owned[itemId] then
result:FireClient(player, "Ya tienes esta linterna")
return
end
if state.balance < item.price then
result:FireClient(player, "No tienes suficientes monedas")
return
end
local template = tools:FindFirstChild(item.tool)
local backpack = player:FindFirstChildOfClass("Backpack")
local starterGear = player:FindFirstChild("StarterGear")
if not template or not template:IsA("Tool") or not template.Archivable
or not backpack or not starterGear then
result:FireClient(player, "La tienda no está lista; inténtalo al reaparecer")
return
end
local current = template:Clone()
local respawnCopy = template:Clone()
state.balance = state.balance - item.price
state.coins.Value = state.balance
state.owned[itemId] = true
current.Parent = backpack
respawnCopy.Parent = starterGear
result:FireClient(player, "Linterna comprada · saldo: " .. state.balance)
end)OnServerEvent añade automáticamente el Player que disparó el evento. No pedimos al cliente un jugador, precio, saldo ni objeto para clonarlo. El catálogo permite solo lantern. El límite por jugador frena solicitudes repetidas antes de hacer trabajo; un debounce exclusivamente en la interfaz sería fácil de ignorar.
La comprobación y la entrega no contienen operaciones que cedan la ejecución, como una llamada a DataStore. StarterGear conserva la herramienta en los siguientes respawns de esta sesión. Al abandonar y volver, se reinicia tanto el saldo como la propiedad. Añadir persistencia requiere diseñar carga, guardado y recuperación de errores por separado.

Conecta el botón
Dentro de BuyButton crea un LocalScript:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remotes = ReplicatedStorage:WaitForChild("ShopRemotes")
local buy = remotes:WaitForChild("BuyItem")
local result = remotes:WaitForChild("PurchaseResult")
local button = script.Parent
local nextClick = 0
button.Activated:Connect(function()
if os.clock() < nextClick then return end
nextClick = os.clock() + 0.8
buy:FireServer("lantern")
end)
result.OnClientEvent:Connect(function(message)
button.Text = message
end)Activated funciona con ratón y entrada táctil. El enfriamiento del cliente reduce clics accidentales; el del servidor aplica la regla. La confirmación viene del servidor mediante el segundo RemoteEvent. Cambiar el texto del botón o Coins desde el cliente no modifica state.balance.
Prueba con dos jugadores
Selecciona Server & Clients, elige dos clientes e inicia la prueba. Cada uno debe comenzar con 100 monedas. Compra: el saldo pasa a 60, aparece la linterna en Backpack y al equiparla ilumina. Intenta comprar de nuevo: el saldo sigue en 60. Reaparece y comprueba que conserve la herramienta; detén la sesión y verifica que una prueba nueva vuelve a 100.
Desde la Command Bar de un cliente de prueba, solicita un ID desconocido:
game:GetService("ReplicatedStorage").ShopRemotes.BuyItem:FireServer("objeto-inexistente")No debe descontar ni entregar nada. También prueba enviar una tabla: debe rechazarse sin error. Si ves «Infinite yield possible», revisa nombres y ubicación de los objetos. Si el botón no actúa, confirma que sea un LocalScript dentro de la interfaz. Si no se equipa, revisa el Part Handle y que no esté anclado.
Fuentes y documentación
Proyecto y desarrollador: Roblox Studio, Roblox Corporation. Página y descarga oficial: Creator Hub. Condiciones: términos de Roblox; Studio no se presenta como software libre ni se redistribuye. Código de la tienda y gráficos originales de The Silencers.






Comentarios 0
Únete a la conversación con tu cuenta.
Inicia sesiónCargando comentarios…