Как сделать магазин в Роблокс Студио: монеты, GUI и защита
Как сделать магазин в Роблокс Студио, который нельзя обмануть: монеты в таблице лидеров, кнопка покупки в интерфейсе и проверка каждой покупки на сервере. Три готовых скрипта на Luau с комментариями.
- Монеты в leaderstats и доход по таймеру
- GUI-кнопка и RemoteEvent
- Сервер проверяет всё — клиенту не верим
Пошагово
Подготовьте товар
В ServerStorage создайте папку (Folder) ShopItems. Положите в неё инструмент (Tool) — например, меч из Toolbox — и переименуйте в Sword. Предметы в ServerStorage игроки не видят, поэтому забрать их без покупки нельзя.
Создайте RemoteEvent и настройки
В ReplicatedStorage добавьте RemoteEvent с именем BuyItem — через него клиент будет просить сервер о покупке. Там же создайте ModuleScript с именем ShopConfig и вставьте код из первого блока.
Добавьте серверный скрипт
В ServerScriptService добавьте Script с именем ShopServer и вставьте код из второго блока. Он создаёт монеты в таблице лидеров, начисляет доход и проверяет каждую покупку.
Соберите интерфейс
В StarterGui добавьте ScreenGui с именем ShopGui, внутри него Frame с именем ShopFrame. В ShopFrame положите TextButton с именем Sword — как в ShopConfig — и TextLabel с именем Message для сообщений.
Добавьте LocalScript
Внутрь ShopGui добавьте LocalScript и вставьте код из третьего блока. Он подпишет кнопку ценой, отправит просьбу о покупке на сервер и покажет ответ зелёным или красным текстом.
Проверьте покупку
Нажмите Play. Справа вверху появятся монеты: 100. Нажмите кнопку — монет станет 50, меч появится на панели предметов. Нажмите ещё раз — увидите «Уже куплено». Каждые 10 секунд добавляется 10 монет.
Добавьте свои товары
Для нового товара нужны три вещи с одинаковым именем: Tool в ShopItems, строка в ShopConfig.Items с ценой и названием и TextButton в ShopFrame. Код менять не придётся.
Скрипт
-- ModuleScript «ShopConfig» в ReplicatedStorage
-- Список товаров. Его читают и сервер (чтобы проверить цену), и клиент (чтобы показать цену на кнопке)
local ShopConfig = {}
ShopConfig.Items = {
-- Ключ Sword должен совпадать с именем Tool в ServerStorage > ShopItems
-- и с именем кнопки в ShopFrame
Sword = {
Price = 50, -- цена в монетах
DisplayName = "Меч", -- название для кнопки
},
}
return ShopConfig
-- Script «ShopServer» в ServerScriptService
-- Сервер — главный: он хранит монеты, проверяет покупку и выдаёт предмет
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local ShopConfig = require(ReplicatedStorage:WaitForChild("ShopConfig"))
local buyEvent = ReplicatedStorage:WaitForChild("BuyItem") -- RemoteEvent
local shopItems = ServerStorage:WaitForChild("ShopItems") -- папка с товарами (Tool)
local START_COINS = 100 -- монеты при входе в игру
local INCOME = 10 -- сколько монет начисляем...
local INCOME_INTERVAL = 10 -- ...каждые столько секунд
local BUY_COOLDOWN = 0.5 -- не чаще одной попытки покупки в полсекунды
local lastBuyTime = {} -- игрок -> время последней попытки покупки
-- Монеты в таблице лидеров. Папка обязательно называется leaderstats (маленькими буквами)
local function onPlayerAdded(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = START_COINS
coins.Parent = leaderstats
-- Простой «доход», чтобы было что тратить
task.spawn(function()
while player.Parent do
task.wait(INCOME_INTERVAL)
coins.Value += INCOME
end
end)
end
Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in ipairs(Players:GetPlayers()) do
onPlayerAdded(player) -- на случай, если игрок зашёл раньше, чем запустился скрипт
end
Players.PlayerRemoving:Connect(function(player)
lastBuyTime[player] = nil -- чистим память
end)
-- Есть ли уже этот предмет у игрока: в рюкзаке, в руках или в StarterGear
local function playerOwns(player, itemName)
local backpack = player:FindFirstChildOfClass("Backpack")
local starterGear = player:FindFirstChild("StarterGear")
local character = player.Character
if backpack and backpack:FindFirstChild(itemName) then
return true
end
if starterGear and starterGear:FindFirstChild(itemName) then
return true
end
if character and character:FindFirstChild(itemName) then
return true
end
return false
end
-- Клиент просит купить товар. Первым аргументом Roblox САМ передаёт игрока — подделать его нельзя
buyEvent.OnServerEvent:Connect(function(player, itemName)
-- 1. Проверяем тип: от клиента может прийти что угодно
if typeof(itemName) ~= "string" then
return
end
-- 2. Защита от спама кнопкой
local now = os.clock()
if lastBuyTime[player] and now - lastBuyTime[player] < BUY_COOLDOWN then
return
end
lastBuyTime[player] = now
-- 3. Товар существует? Цену берём ТОЛЬКО на сервере, клиенту не верим
local item = ShopConfig.Items[itemName]
local tool = shopItems:FindFirstChild(itemName)
if not item or not tool then
buyEvent:FireClient(player, false, "Такого товара нет")
return
end
-- 4. Не куплен ли уже
if playerOwns(player, itemName) then
buyEvent:FireClient(player, false, "Уже куплено")
return
end
-- 5. Хватает ли монет
local leaderstats = player:FindFirstChild("leaderstats")
local coins = leaderstats and leaderstats:FindFirstChild("Coins")
if not coins or coins.Value < item.Price then
buyEvent:FireClient(player, false, "Не хватает монет")
return
end
local backpack = player:FindFirstChildOfClass("Backpack")
if not backpack then
return -- персонаж ещё не появился
end
-- 6. Всё в порядке: списываем монеты и выдаём предмет
coins.Value -= item.Price
tool:Clone().Parent = backpack -- предмет сразу в руки (в рюкзак)
local starterGear = player:FindFirstChild("StarterGear")
if starterGear then
tool:Clone().Parent = starterGear -- копия останется и после возрождения
end
buyEvent:FireClient(player, true, "Куплено: " .. item.DisplayName)
end)
-- LocalScript внутри ScreenGui «ShopGui» (в StarterGui)
-- Клиент только показывает магазин и ПРОСИТ сервер о покупке. Решает всегда сервер
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ShopConfig = require(ReplicatedStorage:WaitForChild("ShopConfig"))
local buyEvent = ReplicatedStorage:WaitForChild("BuyItem")
local shopGui = script.Parent
local shopFrame = shopGui:WaitForChild("ShopFrame")
local messageLabel = shopFrame:WaitForChild("Message")
-- Для каждого товара ищем кнопку с таким же именем и подключаем её
for itemName, item in pairs(ShopConfig.Items) do
local button = shopFrame:FindFirstChild(itemName)
if button and button:IsA("TextButton") then
button.Text = item.DisplayName .. " — " .. item.Price .. " монет"
-- Activated срабатывает и от мыши, и от касания на телефоне, и от геймпада
button.Activated:Connect(function()
buyEvent:FireServer(itemName) -- отправляем только ИМЯ товара, без цены
end)
end
end
-- Сервер отвечает: получилось или нет, и текст сообщения
buyEvent.OnClientEvent:Connect(function(success, message)
messageLabel.Text = message
if success then
messageLabel.TextColor3 = Color3.fromRGB(60, 180, 90) -- зелёный
else
messageLabel.TextColor3 = Color3.fromRGB(220, 60, 60) -- красный
end
end)
Не работает? Частые ошибки
Папка должна называться ровно leaderstats — маленькими буквами. Также проверьте, что ShopServer — это Script в ServerScriptService, а не LocalScript.
Монеты меняются в LocalScript. Изменения на клиенте не попадают на сервер, и сервер считает старое значение. Меняйте монеты только в серверном скрипте, как в ShopServer.
RemoteEvent называется иначе или лежит не в ReplicatedStorage. Переименуйте его в BuyItem (с большой B и I) и перетащите в ReplicatedStorage.
Имя кнопки должно точно совпадать с ключом в ShopConfig.Items (Sword), а сама кнопка должна быть TextButton, а не TextLabel или ImageButton. Ещё проверьте, что Tool с таким именем лежит в ServerStorage > ShopItems.
Как сделать магазин в Роблокс Студио безопасно
Когда разбираешься, как сделать магазин в Роблокс Студио, главное правило одно: клиенту не верим. Клиент — это компьютер игрока, и читер может изменить на нём что угодно: цену на кнопке, количество монет на экране, даже отправить на сервер свои данные. Поэтому в нашем магазине клиент только просит: «хочу купить Sword». Всё остальное решает сервер.
Сервер по очереди проверяет:
- что пришла строка, а не что-то другое;
- что игрок не нажимает кнопку чаще, чем раз в полсекунды;
- что такой товар существует — цену берём из серверной копии ShopConfig, а не от клиента;
- что предмета у игрока ещё нет;
- что монет хватает.
Только после этого сервер списывает монеты и выдаёт предмет. Если бы клиент сам отправлял цену, читер прислал бы 0 и забрал всё бесплатно. Если бы клиент сам менял монеты, изменения просто не дошли бы до сервера.
Как устроен код: разбор по частям
- ShopConfig — ModuleScript со списком товаров. Он лежит в ReplicatedStorage, поэтому его читают и сервер, и клиент. Клиент берёт оттуда цену для кнопки, сервер — для проверки. Если читер изменит свою копию, серверная останется прежней.
- leaderstats — папка с таким именем внутри игрока. Всё, что в ней лежит (у нас IntValue с именем Coins), Roblox автоматически показывает в таблице справа вверху.
- RemoteEvent BuyItem работает в обе стороны. Клиент вызывает FireServer, а на сервере срабатывает OnServerEvent, где первым аргументом Roblox сам передаёт игрока — подделать его нельзя. Ответ сервер отправляет через FireClient.
- Backpack и StarterGear. Предмет кладём в рюкзак, чтобы он появился сразу, и в StarterGear, чтобы он остался после возрождения.
Кнопка подключена через Activated, а не MouseButton1Click: это событие срабатывает и от мыши, и от касания на телефоне.
Что добавить дальше
- Сохранение монет. Сейчас монеты обнуляются при выходе из игры. Для сохранения нужен DataStoreService. Чтобы он работал при тестах в Studio, включите доступ к API в настройках игры (Game Settings → Security).
- Монеты за действия. Вместо дохода по таймеру давайте монеты за сбор предметов, прохождение уровня или победу в раунде.
- Прилавок. Скройте ShopFrame и открывайте его кнопкой-переключателем или когда игрок подходит к прилавку.
- Покупки за Robux. Это уже не монеты игры, а настоящие деньги — они проходят через MarketplaceService (Developer Products и Game Passes), и к ним свои правила безопасности.
Когда понятно, как сделать магазин в Роблокс Студио, монеты можно использовать и в других механиках: например, сделать дверь или телепорт, которые пропускают только тех, кто накопил достаточно.
Заметка для родителей
Понять, как сделать магазин в Роблокс Студио, сложнее, чем дверь или телепорт: здесь три скрипта, которые работают вместе, и главное понятие — разделение на клиент и сервер. Это та же архитектура, что у мобильных приложений, онлайн-игр и банковских сайтов: интерфейс у пользователя, а важные решения — на сервере.
Если ребёнок 12–16 лет собрал магазин и может объяснить, почему цену нельзя отправлять с клиента, — он понял одну из самых важных идей в разработке. Если застрял — это нормально: на этом месте многие начинающие разработчики копируют код, не понимая, как он работает. Именно такие темы удобнее всего разбирать с наставником, по шагам и на примере собственной игры.
Хотите, чтобы ребёнок делал целые игры?
Магазин — это уже часть настоящей игровой экономики. Дальше идут сохранение прогресса, раунды, интерфейс и баланс. На уроках в Excklusive-IT ребёнок собирает такую игру вместе с наставником 1-на-1: 60 минут в Zoom, на русском, украинском или английском, в удобное время в вашем часовом поясе.
Подросткам 14–17 лет подходит программа Roblox + AI: 4 урока в месяц по 120 минут, на первом из них за одну сессию собирается полноценный шутер, а дальше — 3D-модели, анимации и эффекты с помощью AI-инструментов. Хотите, чтобы ребёнок делал целые игры вроде этой? Первый урок 1-на-1 — бесплатно. Подробнее — на странице курса Roblox.
Вопросы, которые задают родители
Почему нельзя просто списать монеты в LocalScript?
LocalScript работает на компьютере игрока, и его изменения не попадают на сервер. Монеты уменьшатся только на экране игрока, а сервер будет считать старое значение. К тому же читер может изменить клиентский код как угодно, поэтому все покупки проверяет и проводит только сервер.
Сохранятся ли монеты после выхода из игры?
В этом туториале — нет, монеты обнуляются при каждом входе. Чтобы их сохранять, нужен DataStoreService: при выходе игрока записываем значение, при входе — читаем. Это отдельная тема, и её лучше делать после того, как магазин уже работает.
Можно ли продавать предметы за Robux?
Да, но это другой механизм — MarketplaceService с Developer Products или Game Passes. Покупку за Robux проводит сам Roblox, а ваш сервер выдаёт предмет после подтверждения. Начинать лучше с игровых монет, как в этом туториале.
Где взять меч или другой предмет для магазина?
Подойдёт любой Tool: из Toolbox или собранный вами из части Handle. Бесплатные модели из Toolbox проверяйте — внутри могут быть лишние скрипты. Главное, чтобы имя Tool в папке ShopItems совпадало с ключом в ShopConfig.
Как открывать магазин только у прилавка?
Скройте ShopFrame (Visible = false). Добавьте к прилавку ProximityPrompt, а в серверном скрипте по событию Triggered отправляйте игроку команду через отдельный RemoteEvent. В LocalScript по этой команде показывайте ShopFrame. Проверка покупки при этом остаётся на сервере.
Разберём магазин и вашу игру на бесплатном уроке
Клиент, сервер, монеты, сохранение — проще понять с наставником. 60 минут 1-на-1 в Roblox Studio, онлайн на русском, первый урок бесплатно.
Записаться на бесплатный урокЗапишитесь на бесплатный урок
Без обязательств. Без оплаты. Просто первый урок для вашего ребёнка.
Или напишите нам напрямую
Нажмите на мессенджер — чат откроется с готовым приветствием.
- +1 (289) 44-28-698
- excklusiveit@gmail.com
-
Часы работы — Гамильтон, Онтарио (ET) Пн–Пт: 9:00–18:00 Сб–Вс: 8:00–16:00
- Онлайн по всему миру · класс в Гамильтоне: 170 Parkdale Ave N, Hamilton, ON L8H 5X2