Roblox · Приключение · урок 5

Магазин

Торговец открывает окно магазина, игрок покупает зелья за монеты. Цены проверяет сервер.

Структура

Синим — что добавляем на этом уроке.

ReplicatedStorage
└─ Events (Folder)
   ├─ ShopEvent (RemoteEvent)
   └─ BuyFunction (RemoteFunction)
ServerStorage
└─ Shop (Folder)
   └─ HealingPotion, InvisiblePotion (Tool) — внутри IntValue Price
ServerScriptService
└─ BuyScript (Script)
Workspace
└─ Trader (Model) — Head → ProximityPrompt «Торговать»
   └─ TraderScript (Script)
StarterGui
└─ ShopGui (ScreenGui) — ResetOnSpawn выкл.
   ├─ ShopFrame (Frame) — Visible выкл.
   │  ├─ Items (Frame) — UIListLayout + кнопки «HealingPotion», «InvisiblePotion»
   │  ├─ Message (TextLabel)
   │  └─ CloseButton (TextButton)
   └─ ShopLocal (LocalScript)

Шаги

  1. В каждый товар в ServerStorage.Shop — IntValue «Price»: 10 у лечения, 15 у невидимости.
  2. Events → RemoteEvent «ShopEvent» и RemoteFunction «BuyFunction». RemoteFunction возвращает ответ — клиент покажет «Куплено» или «Не хватает монет».
  3. Торговец «Trader»: ProximityPrompt в Head (ActionText «Торговать»), Script «TraderScript».
  4. ShopGui → ShopFrame: Frame «Items» с UIListLayout и кнопками, названными как товары (текст — «Зелье лечения — 10»), TextLabel «Message», TextButton «CloseButton».
  5. LocalScript «ShopLocal»: все кнопки подписываются один раз при входе (в методичке «Закрыть» подписывалась при каждом открытии, и клики множились).
  6. ServerScriptService → Script «BuyScript»: проверяет, что товар есть и монет хватает. Товары в ServerStorage — клиент не может их подменить.

Скрипты

TraderScript

Где: Workspace → Trader · Script
Зачем: Открывает магазин.

-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- открыть магазин
local shopEvent = RS:WaitForChild("Events"):WaitForChild("ShopEvent")
-- «Торговать»
local prompt = script.Parent:WaitForChild("Head"):WaitForChild("ProximityPrompt")

-- игрок нажал E
prompt.Triggered:Connect(function(player)
    -- только у него откроется окно
    shopEvent:FireClient(player)
-- конец функции
end)

ShopLocal

Где: StarterGui → ShopGui · LocalScript
Зачем: Окно магазина.

-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- события
local events = RS:WaitForChild("Events")
-- открыть
local shopEvent = events:WaitForChild("ShopEvent")
-- купить и получить ответ
local buyFunction = events:WaitForChild("BuyFunction")
-- окно
local frame = script.Parent:WaitForChild("ShopFrame")
-- ответ магазина
local message = frame:WaitForChild("Message")

-- торговец позвал
shopEvent.OnClientEvent:Connect(function()
    -- без старых сообщений
    message.Text = ""
    -- открываем
    frame.Visible = true
-- конец функции
end)
-- «Закрыть» — подписываемся один раз
frame:WaitForChild("CloseButton").MouseButton1Click:Connect(function()
    -- закрываем
    frame.Visible = false
-- конец функции
end)
-- кнопки товаров
for _, button in ipairs(frame:WaitForChild("Items"):GetChildren()) do
    -- только кнопки
    if button:IsA("TextButton") then
        -- нажатие
        button.MouseButton1Click:Connect(function()
            -- имя кнопки = имя товара; ждём ответ
            local _, text = buyFunction:InvokeServer(button.Name)
            -- «Куплено» или «Не хватает монет»
            message.Text = text
        -- конец функции
        end)
    -- конец проверки
    end
-- конец перебора
end

BuyScript

Где: ServerScriptService · Script
Зачем: Покупка с проверкой.

-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- товары лежат здесь — клиент их не видит и не подменит
local SS = game:GetService("ServerStorage")
-- покупка
local buyFunction = RS:WaitForChild("Events"):WaitForChild("BuyFunction")
-- товары
local shop = SS:WaitForChild("Shop")

-- клиент хочет купить
buyFunction.OnServerInvoke = function(player, itemName)
    -- пришло не имя
    if typeof(itemName) ~= "string" then return false, "Ошибка" end
    -- товар
    local item = shop:FindFirstChild(itemName)
    -- его цена
    local price = item and item:FindFirstChild("Price")
    -- монеты игрока
    local stats = player:FindFirstChild("leaderstats")
    -- рюкзак
    local backpack = player:FindFirstChild("Backpack")
    -- что-то не так
    if not price or not stats or not backpack then return false, "Такого товара нет" end
    -- денег мало
    if stats.Coins.Value < price.Value then
        -- ответ
        return false, "Не хватает монет: нужно " .. price.Value
    -- конец проверки
    end
    -- списываем
    stats.Coins.Value -= price.Value
    -- товар в рюкзак
    item:Clone().Parent = backpack
    -- ответ
    return true, "Куплено!"
-- конец функции
end

Проверка

Запусти Play (или Run) и убедись: