Roblox · Pet Simulator · урок 6

Новая область и публикация

Стена делит карту на две области. Подходишь — «Открыть область за 100 Coins?», «Да» — стена исчезает навсегда (и после перезахода). Публикуем игру.

Структура

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

Workspace
└─ Doors (Folder)
   └─ SecondArea (Part) — стена; имя = название области
      ├─ Price (NumberValue) — цена
      ├─ PriceGui (SurfaceGui)
      │  └─ PriceText (TextLabel)
      └─ Prompt (Part) — перед стеной: прозрачный, CanCollide выкл.
         └─ ProximityPrompt (ProximityPrompt)
ReplicatedStorage → Events (Folder)
├─ BuyAreaEvent (RemoteEvent)
└─ BuyAreaFunction (RemoteFunction)
ServerScriptService → MainScript → PlayerInfo (Folder)
└─ Areas (Folder) — купленные области
StarterGui
└─ BuyAreaGui (ScreenGui) — ResetOnSpawn выкл.
   └─ BuyFrame (Frame) — Visible выкл.
      ├─ Question (TextLabel)
      └─ YesButton, NoButton (TextButton)
StarterPlayer → StarterPlayerScripts
└─ MainLocal (LocalScript)

Шаги

  1. Стена между областями — деталь с именем области «SecondArea». Внутрь: NumberValue «Price», SurfaceGui «PriceGui» (на сторону, видную из первой области) → TextLabel «PriceText».
  2. Деталь «Prompt» перед стеной (прозрачная, CanCollide выкл., Anchored) — внутрь ProximityPrompt «Открыть область».
  3. Workspace → Folder «Doors», стена туда. Вторая область должна быть огорожена со всех сторон — иначе её обойдут.
  4. В PlayerInfo — Folder «Areas».
  5. StarterGui → ScreenGui «BuyAreaGui» (ResetOnSpawn выкл.) → Frame «BuyFrame» (Visible выкл.) → TextLabel «Question», TextButton «YesButton» и «NoButton».
  6. ReplicatedStorage → Events → RemoteEvent «BuyAreaEvent» и RemoteFunction «BuyAreaFunction».
  7. StarterPlayerScripts → LocalScript «MainLocal». Стена исчезает только у купившего: клиент сам убирает стены, которые есть в его папке Areas, — и при покупке, и после загрузки сохранения.
  8. SaveData уже сохраняет папку Areas — ничего менять не нужно.
  9. Публикация: скриншоты; Game Settings → Places — число игроков; Basic Info — название, описание, устройства; Permissions → Public.

Скрипты

MainScript

Где: ServerScriptService · Script
Зачем: + продажа областей.

-- все игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- модуль питомцев
local pet = require(script.Pet)
-- модуль целей
local drop = require(script.Pet.Drop)
-- сохранение
local saveData = require(script.SaveData)
-- события
local events = RS:WaitForChild("Events")
-- надеть / снять питомца
local equipFunction = events:WaitForChild("EquipPetFunction")
-- показать окно покупки
local buyAreaEvent = events:WaitForChild("BuyAreaEvent")
-- купить область
local buyAreaFunction = events:WaitForChild("BuyAreaFunction")
-- образцы питомцев
local pets = RS:WaitForChild("Pets")
-- питомцы игроков на карте
local playerPets = workspace:WaitForChild("PlayerPets")
-- цели
local drops = workspace:WaitForChild("Drops")
-- стены областей
local doors = workspace:WaitForChild("Doors")
-- сколько питомцев можно взять с собой
local MAX_EQUIPPED = 3

-- игрок зашёл
Players.PlayerAdded:Connect(function(player)
    -- папка для его питомцев
    local folder = Instance.new("Folder")
    -- имя = имя игрока
    folder.Name = player.Name
    -- в PlayerPets
    folder.Parent = playerPets
    -- папки из PlayerInfo
    for _, info in ipairs(script.PlayerInfo:GetChildren()) do
        -- копия — в игрока
        info:Clone().Parent = player
    -- конец перебора
    end
    -- загружаем монеты, питомцев, области
    saveData.Load(player)
-- конец обработчика
end)

-- игрок вышел
Players.PlayerRemoving:Connect(function(player)
    -- сохраняем
    saveData.Save(player)
    -- его папка
    local folder = playerPets:FindFirstChild(player.Name)
    -- убираем питомцев
    if folder then folder:Destroy() end
-- конец обработчика
end)

-- сервер выключается — сохраняем всех и ждём
game:BindToClose(saveData.SaveAll)

-- подготовить цель
local function SetupDrop(dropObject)
    -- полоска здоровья
    drop.UpdateGui(dropObject)
    -- клик по цели — подключаем ОДИН раз
    dropObject.ClickDetector.MouseClick:Connect(function(player)
        -- питомцы атакуют
        pet.Attack(player, dropObject)
    -- конец обработчика
    end)
-- конец функции
end
-- цели, что уже стоят
for _, obj in ipairs(drops:GetDescendants()) do
    -- это цель
    if obj:IsA("Model") and obj:FindFirstChild("ClickDetector") then SetupDrop(obj) end
-- конец перебора
end
-- цель появилась снова
drops.DescendantAdded:Connect(function(obj)
    -- подключаем и её
    if obj:IsA("Model") and obj:FindFirstChild("ClickDetector") then SetupDrop(obj) end
-- конец обработчика
end)
-- отдельный поток
task.spawn(function()
    -- каждую секунду
    while task.wait(1) do
        -- питомцы наносят урон
        pet.Tick()
    -- конец цикла
    end
-- конец потока
end)

-- игрок нажал на иконку питомца
equipFunction.OnServerInvoke = function(player, petName, equip)
    -- прислали не текст
    if typeof(petName) ~= "string" then return false end
    -- его питомцы на карте
    local folder = playerPets:FindFirstChild(player.Name)
    -- образец
    local template = pets:FindFirstChild(petName)
    -- нет папки или такого питомца
    if not folder or not template then return false end
    -- снять
    if not equip then
        -- этот питомец на карте
        local active = folder:FindFirstChild(petName)
        -- убираем
        if active then active:Destroy() end
        -- готово
        return true
    -- конец проверки
    end
    -- сколько таких есть и сколько уже надето
    local owned, active = 0, 0
    -- купленные питомцы
    for _, value in ipairs(player.Pets:GetChildren()) do
        -- считаем таких
        if value.Name == petName then owned += 1 end
    -- конец перебора
    end
    -- надетые
    for _, petModel in ipairs(folder:GetChildren()) do
        -- считаем таких
        if petModel.Name == petName then active += 1 end
    -- конец перебора
    end
    -- не свой или уже максимум
    if active >= owned or #folder:GetChildren() >= MAX_EQUIPPED then return false end
    -- питомец
    local clone = template:Clone()
    -- появляется у хозяина
    if player.Character then clone:PivotTo(player.Character:GetPivot()) end
    -- сразу к текущей цели
    clone.Attack.Value = player.Values.Target.Value
    -- на карту
    clone.Parent = folder
    -- надет
    return true
-- конец функции
end

-- игрок нажал «Да»
buyAreaFunction.OnServerInvoke = function(player, door)
    -- это не стена области
    if typeof(door) ~= "Instance" or door.Parent ~= doors then return false end
    -- уже куплено
    if player.Areas:FindFirstChild(door.Name) then return false end
    -- кошелёк
    local coins = player.leaderstats.Coins
    -- не хватает
    if coins.Value < door.Price.Value then return false end
    -- списываем
    coins.Value -= door.Price.Value
    -- отметка о покупке
    local areaValue = Instance.new("StringValue")
    -- имя области
    areaValue.Name = door.Name
    -- в папку Areas — клиент уберёт стену сам
    areaValue.Parent = player.Areas
    -- куплено
    return true
-- конец функции
end

-- каждая стена
for _, door in ipairs(doors:GetChildren()) do
    -- цена на стене
    door.PriceGui.PriceText.Text = door.Price.Value .. " Coins"
    -- игрок нажал E у стены
    door.Prompt.ProximityPrompt.Triggered:Connect(function(player)
        -- ещё не купил
        if not player.Areas:FindFirstChild(door.Name) then
            -- показываем ему окно покупки
            buyAreaEvent:FireClient(player, door)
        -- конец проверки
        end
    -- конец обработчика
    end)
-- конец перебора
end

MainLocal

Где: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: Окно покупки и исчезновение стен.

-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- события
local events = RS:WaitForChild("Events")
-- сервер: покажи окно
local buyAreaEvent = events:WaitForChild("BuyAreaEvent")
-- купить
local buyAreaFunction = events:WaitForChild("BuyAreaFunction")
-- этот игрок
local player = game.Players.LocalPlayer
-- его купленные области
local areas = player:WaitForChild("Areas")
-- стены
local doors = workspace:WaitForChild("Doors")
-- окно покупки
local buyFrame = player:WaitForChild("PlayerGui"):WaitForChild("BuyAreaGui"):WaitForChild("BuyFrame")
-- вопрос
local question = buyFrame:WaitForChild("Question")
-- «Да»
local yesButton = buyFrame:WaitForChild("YesButton")
-- «Нет»
local noButton = buyFrame:WaitForChild("NoButton")
-- какую стену покупаем
local currentDoor = nil

-- убрать стену — только у этого игрока
local function RemoveDoor(name)
    -- стена с именем области
    local door = doors:FindFirstChild(name)
    -- убираем
    if door then door:Destroy() end
-- конец функции
end
-- уже купленные (после загрузки)
for _, area in ipairs(areas:GetChildren()) do RemoveDoor(area.Name) end
-- купил или данные загрузились позже
areas.ChildAdded:Connect(function(area) RemoveDoor(area.Name) end)

-- сервер просит показать окно
buyAreaEvent.OnClientEvent:Connect(function(door)
    -- запоминаем стену
    currentDoor = door
    -- вопрос с ценой
    question.Text = "Открыть " .. door.Name .. " за " .. door.Price.Value .. " Coins?"
    -- показываем окно
    buyFrame.Visible = true
-- конец обработчика
end)

-- «Да» — обработчик один на всю игру
yesButton.MouseButton1Click:Connect(function()
    -- окно не для покупки
    if not currentDoor then return end
    -- стена
    local door = currentDoor
    -- сбрасываем
    currentDoor = nil
    -- сервер продал
    if buyAreaFunction:InvokeServer(door) then
        -- закрываем — стену уберёт ChildAdded
        buyFrame.Visible = false
    -- не продал
    else
        -- сообщаем
        question.Text = "Не хватает монет"
        -- полторы секунды
        task.wait(1.5)
        -- закрываем
        buyFrame.Visible = false
    -- конец проверки
    end
-- конец обработчика
end)
-- «Нет»
noButton.MouseButton1Click:Connect(function()
    -- ничего не покупаем
    currentDoor = nil
    -- закрываем
    buyFrame.Visible = false
-- конец обработчика
end)

Проверка

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