Roblox · Pet Simulator · урок 4

Инвентарь питомцев

Кнопка «Питомцы» открывает окно со всеми купленными питомцами. Нажал на иконку — питомец с тобой (иконка зелёная), ещё раз — убрал. С собой — не больше трёх.

Структура

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

ReplicatedStorage → Events (Folder)
└─ EquipPetFunction (RemoteFunction) — клиент просит — сервер отвечает да/нет
StarterGui
└─ PetInventoryGui (ScreenGui) — ResetOnSpawn выкл.
   ├─ OpenCloseButton (ImageButton / TextButton)
   │  └─ OpenCloseScript (LocalScript)
   └─ PetFrame (Frame) — Visible выкл.
      ├─ Title (TextLabel)
      ├─ ScrollingFrame (ScrollingFrame) — AutomaticCanvasSize = Y
      │  ├─ UIGridLayout, UIPadding — иконки сеткой
      │  └─ Sample (ViewportFrame) — шаблон иконки, Visible выкл.
      │     └─ Button (TextButton) — во весь Sample, прозрачная
      └─ PetInventoryLocal (LocalScript)

Шаги

  1. StarterGui → ScreenGui «PetInventoryGui», ResetOnSpawn выкл. — после смерти не сбросятся надетые питомцы.
  2. Кнопка «OpenCloseButton» (ImageButton или TextButton), в неё LocalScript «OpenCloseScript».
  3. Frame «PetFrame» (Visible выкл.) → TextLabel «Title» и ScrollingFrame (AutomaticCanvasSize = Y) с UIGridLayout и UIPadding.
  4. В ScrollingFrame — ViewportFrame «Sample» (Visible выкл.), в нём TextButton «Button» того же размера: BackgroundTransparency 1, Text пустой.
  5. ReplicatedStorage → Events → RemoteFunction «EquipPetFunction».
  6. В PetFrame — LocalScript «PetInventoryLocal». Иконки строятся по папке Pets игрока — и для уже купленных, и для новых.
  7. MainScript: тестовый питомец больше не нужен, добавлен обработчик EquipPetFunction. Сервер проверяет, что питомец куплен и что надето меньше трёх; иконка зеленеет, только если сервер ответил «да».

Скрипты

OpenCloseScript

Где: StarterGui → PetInventoryGui → OpenCloseButton · LocalScript
Зачем: Открывает и закрывает инвентарь.

-- кнопка «Питомцы»
local button = script.Parent
-- окно инвентаря
local frame = button.Parent:WaitForChild("PetFrame")

-- нажали
button.MouseButton1Click:Connect(function()
    -- открыто — закрываем, закрыто — открываем
    frame.Visible = not frame.Visible
-- конец обработчика
end)

PetInventoryLocal

Где: StarterGui → PetInventoryGui → PetFrame · LocalScript
Зачем: Иконки питомцев и надевание по клику.

-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- образцы питомцев
local pets = RS:WaitForChild("Pets")
-- надеть / снять
local equipFunction = RS:WaitForChild("Events"):WaitForChild("EquipPetFunction")
-- этот игрок
local player = game.Players.LocalPlayer
-- его купленные питомцы
local ownedPets = player:WaitForChild("Pets")
-- окно инвентаря
local frame = script.Parent
-- шаблон иконки
local sample = frame:WaitForChild("ScrollingFrame"):WaitForChild("Sample")
-- цвет: не надет
local OFF = Color3.fromRGB(80, 200, 255)
-- цвет: надет
local ON = Color3.fromRGB(90, 230, 120)

-- иконка одного питомца
local function AddIcon(petValue)
    -- его образец
    local template = pets:FindFirstChild(petValue.Name)
    -- нет такого
    if not template then return end
    -- копия шаблона
    local icon = sample:Clone()
    -- имя
    icon.Name = petValue.Name
    -- показываем
    icon.Visible = true
    -- не надет
    icon.BackgroundColor3 = OFF
    -- в список
    icon.Parent = sample.Parent
    -- питомец для картинки
    local petModel = template:Clone()
    -- мордой к камере
    petModel:PivotTo(CFrame.Angles(0, math.rad(180), 0))
    -- в окошко иконки
    petModel.Parent = icon
    -- камера иконки
    local camera = Instance.new("Camera")
    -- отходим под размер питомца
    camera.CFrame = CFrame.new(0, 0, petModel:GetExtentsSize().Magnitude * 1.2)
    -- в иконку
    camera.Parent = icon
    -- иконка смотрит через неё
    icon.CurrentCamera = camera
    -- надет ли этот питомец
    local equipped = false
    -- нажали на иконку
    icon.Button.MouseButton1Click:Connect(function()
        -- сервер согласился
        if equipFunction:InvokeServer(petValue.Name, not equipped) then
            -- меняем состояние
            equipped = not equipped
            -- и цвет
            icon.BackgroundColor3 = equipped and ON or OFF
        -- конец проверки
        end
    -- конец обработчика
    end)
    -- питомца не стало
    petValue.AncestryChanged:Connect(function()
        -- убираем иконку
        if not petValue.Parent then icon:Destroy() end
    -- конец обработчика
    end)
-- конец функции
end

-- питомцы, что уже есть
for _, petValue in ipairs(ownedPets:GetChildren()) do AddIcon(petValue) end
-- новый питомец
ownedPets.ChildAdded:Connect(function(petValue)
    -- иконка — после анимации яйца
    task.wait(4)
    -- добавляем
    AddIcon(petValue)
-- конец обработчика
end)

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 events = RS:WaitForChild("Events")
-- надеть / снять питомца
local equipFunction = events:WaitForChild("EquipPetFunction")
-- образцы питомцев
local pets = RS:WaitForChild("Pets")
-- питомцы игроков на карте
local playerPets = workspace:WaitForChild("PlayerPets")
-- цели
local drops = workspace:WaitForChild("Drops")
-- сколько питомцев можно взять с собой
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
-- конец обработчика
end)

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

-- подготовить цель
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

Проверка

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