Roblox · RPG · урок 11
Торговая площадь: у каждого класса свой торговец. Зелье лечения, свиток телепорта домой, нимб скорости. Покупки проверяет сервер и кладёт в рюкзак.
Синим — что добавляем на этом уроке.
Workspace ├─ MapFolder → Market (Model) │ └─ Торговец (Part) — невидимый; CanInteract (вкл.), CanBuy = Swordsman / Archer / Mage └─ SafePart (Part) — куда переносит свиток ServerStorage └─ Shop (Folder) └─ Swordsman, Archer, Mage (Folder) — товары класса ├─ Potion (Tool) — Handle, Drink (Animation), PotionScript, Price ├─ Scroll (Tool) — Handle, ScrollScript, Price └─ Halo (Accessory) — Handle → HatAttachment, HaloScript, Price ReplicatedStorage └─ BuyFunction (RemoteFunction) ServerScriptService └─ Buy (Script) StarterGui └─ ShopGui (ScreenGui) — ResetOnSpawn выкл. └─ Swordsman, Archer, Mage (Frame) — Title, Items (кнопки = имена товаров), Message, CloseButton StarterPlayerScripts → InteractSystem (LocalScript) — дополнили
Где: ServerStorage → Shop → … → Potion · Script
Зачем: Зелье лечения.
-- зелье
local tool = script.Parent
-- анимация глотка
local anim = tool:WaitForChild("Drink")
-- сколько лечит
local HEAL = 50
-- уже выпито
local used = false
-- клик — выпить
tool.Activated:Connect(function()
-- кто пьёт
local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
-- уже, нет персонажа или погиб
if used or not humanoid or humanoid.Health <= 0 then return end
-- выпито
used = true
-- глоток
if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
-- +50, не выше максимума
humanoid.Health = math.min(humanoid.Health + HEAL, humanoid.MaxHealth)
-- зелья больше нет
tool:Destroy()
-- конец функции
end)Где: ServerStorage → Shop → … → Scroll · Script
Зачем: Свиток телепорта.
-- свиток
local tool = script.Parent
-- клик — прочитать свиток
tool.Activated:Connect(function()
-- кто читает
local character = tool.Parent
-- безопасное место
local safe = workspace:FindFirstChild("SafePart")
-- места нет или нет персонажа
if not safe or not character:FindFirstChild("HumanoidRootPart") then return end
-- переносим персонажа целиком
character:PivotTo(safe.CFrame + Vector3.new(0, 4, 0))
-- свиток сгорел
tool:Destroy()
-- конец функции
end)Где: ServerStorage → Shop → … → Halo · Script
Зачем: Нимб скорости.
-- нимб (Accessory) — скрипт запускается, когда нимб надели
local halo = script.Parent
-- прибавка к скорости
local BONUS = 4
-- на ком нимб
local humanoid = halo.Parent and halo.Parent:FindFirstChildOfClass("Humanoid")
-- быстрее — до конца жизни персонажа
if humanoid then humanoid.WalkSpeed += BONUS endГде: ServerScriptService · Script
Зачем: Покупка.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- товары — клиент их не видит и не подменит
local SS = game:GetService("ServerStorage")
-- покупка
local buyFunction = RS:WaitForChild("BuyFunction")
-- папки товаров по классам
local shop = SS:WaitForChild("Shop")
-- игрок нажал «купить»
buyFunction.OnServerInvoke = function(player, className, itemName)
-- пришёл мусор
if typeof(className) ~= "string" or typeof(itemName) ~= "string" then return false, "Ошибка" end
-- чужой торговец
if player:GetAttribute("Class") ~= className then return false, "Этот торговец не для твоего класса" end
-- товары класса
local folder = shop:FindFirstChild(className)
-- товар
local item = folder and folder:FindFirstChild(itemName)
-- цена
local price = item and item:FindFirstChild("Price")
-- нет
if not price then return false, "Нет такого товара" end
-- кошелёк
local coins = player.leaderstats["Монеты"]
-- денег мало
if coins.Value < price.Value then return false, "Не хватает монет: нужно " .. price.Value end
-- копия товара
local copy = item:Clone()
-- нимб — надеваем
if copy:IsA("Accessory") then
-- персонаж
local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
-- нет персонажа — денег не берём
if not humanoid then return false, "Персонаж не найден" end
-- надели
humanoid:AddAccessory(copy)
-- инструмент
else
-- в рюкзак (а не на землю, как в методичке)
copy.Parent = player.Backpack
-- конец проверки
end
-- списываем
coins.Value -= price.Value
-- ответ
return true, "Куплено: " .. itemName
-- конец функции
endГде: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: Взаимодействие и магазин.
-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- каждый кадр
local RunService = game:GetService("RunService")
-- этот игрок
local player = Players.LocalPlayer
-- мышь: mouse.Target — деталь под курсором
local mouse = player:GetMouse()
-- интерфейс игрока
local playerGui = player:WaitForChild("PlayerGui")
-- надпись над объектом (BillboardGui)
local interactGui = playerGui:WaitForChild("InteractGui")
-- окно записки
local noteFrame = playerGui:WaitForChild("NoteGui"):WaitForChild("NoteFrame")
-- просим сервер: «взять монету/сундук»
local interactEvent = RS:WaitForChild("InteractEvent")
-- дальше 30 studs не взаимодействуем
local MAX_DISTANCE = 30
-- окна торговцев
local shopGui = playerGui:WaitForChild("ShopGui")
-- покупка с ответом сервера
local buyFunction = RS:WaitForChild("BuyFunction")
-- объект с CanInteract под курсором (деталь или её модель)
local function Target()
-- деталь под курсором
local obj = mouse.Target
-- поднимаемся к родителям
while obj and obj ~= workspace do
-- метка «с этим можно взаимодействовать»
local can = obj:FindFirstChild("CanInteract")
-- нашли, и он сейчас доступен
if can and can.Value then return obj end
-- выше
obj = obj.Parent
-- конец цикла
end
-- ничего нет
return nil
-- конец функции
end
-- где объект
local function PositionOf(item)
-- у модели — центр
if item:IsA("Model") then return item:GetPivot().Position end
-- у детали — её позиция
return item.Position
-- конец функции
end
-- что написать под именем
local function ActionText(item)
-- записка
if item:FindFirstChild("Text") then return "ЛКМ — прочитать" end
-- торговец
if item:FindFirstChild("CanBuy") then return "ЛКМ — торговать" end
-- монета или сундук
return "ЛКМ — взять"
-- конец функции
end
-- каждый кадр
RunService.RenderStepped:Connect(function()
-- что под курсором
local item = Target()
-- есть и не слишком далеко
if item and player:DistanceFromCharacter(PositionOf(item)) < MAX_DISTANCE * 2 then
-- надпись над объектом
interactGui.Adornee = item:IsA("Model") and (item.PrimaryPart or item:FindFirstChildWhichIsA("BasePart")) or item
-- имя объекта
interactGui.Object.Text = item.Name
-- что можно сделать
interactGui.Interact.Text = ActionText(item)
-- показываем
interactGui.Enabled = true
-- ничего нет
else
-- отвязываем
interactGui.Adornee = nil
-- прячем
interactGui.Enabled = false
-- конец проверки
end
-- конец функции
end)
-- окно торговца
local function OpenShop(className)
-- окно для этого класса
local frame = shopGui:FindFirstChild(className)
-- такого окна нет
if not frame then return end
-- торговец не для нашего класса
if player:GetAttribute("Class") ~= className then
-- его ответ
noteFrame.NoteText.Text = "Торговец: я работаю только с классом " .. className
-- показываем
noteFrame.Visible = true
-- окно не открываем
return
-- конец проверки
end
-- без старых сообщений
frame.Message.Text = ""
-- открываем
frame.Visible = true
-- конец функции
end
-- каждое окно торговца — подписываемся один раз
for _, frame in ipairs(shopGui:GetChildren()) do
-- только окна
if frame:IsA("Frame") then
-- «Закрыть»
frame.CloseButton.MouseButton1Click:Connect(function() frame.Visible = false end)
-- кнопки товаров
for _, button in ipairs(frame.Items:GetChildren()) do
-- только кнопки
if button:IsA("TextButton") then
-- нажали
button.MouseButton1Click:Connect(function()
-- класс и товар — серверу, ждём ответ
local _, text = buyFunction:InvokeServer(frame.Name, button.Name)
-- «Куплено» или «Не хватает монет»
frame.Message.Text = text
-- конец функции
end)
-- конец проверки
end
-- конец перебора
end
-- конец проверки
end
-- конец перебора
end
-- клик левой кнопкой
mouse.Button1Down:Connect(function()
-- что под курсором
local item = Target()
-- ничего или далеко
if not item or player:DistanceFromCharacter(PositionOf(item)) > MAX_DISTANCE then return end
-- это записка?
local text = item:FindFirstChild("Text")
-- да
if text then
-- текст записки
noteFrame.NoteText.Text = text.Value
-- показываем окно
noteFrame.Visible = true
-- шелест бумаги
local sound = item:FindFirstChild("Sound")
-- играем у себя
if sound then sound:Play() end
-- это торговец
elseif item:FindFirstChild("CanBuy") then
-- окно его класса
OpenShop(item.CanBuy.Value)
-- монета или сундук
else
-- деньги начисляет только сервер
interactEvent:FireServer(item)
-- конец проверки
end
-- конец функции
end)
-- «Закрыть» записку — подписка одна
noteFrame:WaitForChild("CloseButton").MouseButton1Click:Connect(function()
-- прячем
noteFrame.Visible = false
-- конец функции
end)Запусти Play (или Run) и убедись: