Туториал Roblox Studio · Luau

Как сделать магазин в Роблокс Студио: монеты, GUI и защита

Как сделать магазин в Роблокс Студио, который нельзя обмануть: монеты в таблице лидеров, кнопка покупки в интерфейсе и проверка каждой покупки на сервере. Три готовых скрипта на Luau с комментариями.

  • Монеты в leaderstats и доход по таймеру
  • GUI-кнопка и RemoteEvent
  • Сервер проверяет всё — клиенту не верим

Пошагово

  1. Подготовьте товар

    В ServerStorage создайте папку (Folder) ShopItems. Положите в неё инструмент (Tool) — например, меч из Toolbox — и переименуйте в Sword. Предметы в ServerStorage игроки не видят, поэтому забрать их без покупки нельзя.

  2. Создайте RemoteEvent и настройки

    В ReplicatedStorage добавьте RemoteEvent с именем BuyItem — через него клиент будет просить сервер о покупке. Там же создайте ModuleScript с именем ShopConfig и вставьте код из первого блока.

  3. Добавьте серверный скрипт

    В ServerScriptService добавьте Script с именем ShopServer и вставьте код из второго блока. Он создаёт монеты в таблице лидеров, начисляет доход и проверяет каждую покупку.

  4. Соберите интерфейс

    В StarterGui добавьте ScreenGui с именем ShopGui, внутри него Frame с именем ShopFrame. В ShopFrame положите TextButton с именем Sword — как в ShopConfig — и TextLabel с именем Message для сообщений.

  5. Добавьте LocalScript

    Внутрь ShopGui добавьте LocalScript и вставьте код из третьего блока. Он подпишет кнопку ценой, отправит просьбу о покупке на сервер и покажет ответ зелёным или красным текстом.

  6. Проверьте покупку

    Нажмите Play. Справа вверху появятся монеты: 100. Нажмите кнопку — монет станет 50, меч появится на панели предметов. Нажмите ещё раз — увидите «Уже куплено». Каждые 10 секунд добавляется 10 монет.

  7. Добавьте свои товары

    Для нового товара нужны три вещи с одинаковым именем: Tool в ShopItems, строка в ShopConfig.Items с ценой и названием и TextButton в ShopFrame. Код менять не придётся.

Скрипт

ModuleScript «ShopConfig» — в ReplicatedStorage
-- ModuleScript «ShopConfig» в ReplicatedStorage
-- Список товаров. Его читают и сервер (чтобы проверить цену), и клиент (чтобы показать цену на кнопке)
local ShopConfig = {}

ShopConfig.Items = {
	-- Ключ Sword должен совпадать с именем Tool в ServerStorage > ShopItems
	-- и с именем кнопки в ShopFrame
	Sword = {
		Price = 50, -- цена в монетах
		DisplayName = "Меч", -- название для кнопки
	},
}

return ShopConfig
Script «ShopServer» — в ServerScriptService
-- 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
-- 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.

В Output ошибка Infinite yield possible on ReplicatedStorage:WaitForChild("BuyItem")

RemoteEvent называется иначе или лежит не в ReplicatedStorage. Переименуйте его в BuyItem (с большой B и I) и перетащите в ReplicatedStorage.

Кнопка ничего не делает

Имя кнопки должно точно совпадать с ключом в ShopConfig.Items (Sword), а сама кнопка должна быть TextButton, а не TextLabel или ImageButton. Ещё проверьте, что Tool с таким именем лежит в ServerStorage > ShopItems.

Как сделать магазин в Роблокс Студио безопасно

Когда разбираешься, как сделать магазин в Роблокс Студио, главное правило одно: клиенту не верим. Клиент — это компьютер игрока, и читер может изменить на нём что угодно: цену на кнопке, количество монет на экране, даже отправить на сервер свои данные. Поэтому в нашем магазине клиент только просит: «хочу купить Sword». Всё остальное решает сервер.

Сервер по очереди проверяет:

  1. что пришла строка, а не что-то другое;
  2. что игрок не нажимает кнопку чаще, чем раз в полсекунды;
  3. что такой товар существует — цену берём из серверной копии ShopConfig, а не от клиента;
  4. что предмета у игрока ещё нет;
  5. что монет хватает.

Только после этого сервер списывает монеты и выдаёт предмет. Если бы клиент сам отправлял цену, читер прислал бы 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, онлайн на русском, первый урок бесплатно.

Записаться на бесплатный урок

Запишитесь на бесплатный урок

Гарантия возврата денег Не подошло? Вернём деньги за неиспользованные уроки в течение 14 дней.
CA$70 за индивидуальный урок 60 мин ≈ US$50. Крупные школы США берут US$60–75 за 50–60 минут.
Один наставник, вживую, 1-на-1 На русском, украинском или английском. Проверка Vulnerable Sector Check. Родители могут присутствовать.