Roblox · Pet Simulator · урок 2

Атака целей

На карте цели с полоской здоровья. Клик по цели — все питомцы бегут к ней и бьют, пока она не разрушится. За разрушение — монеты, через 5 секунд цель появляется снова.

Структура

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

Workspace
└─ Drops (Folder)
   ├─ FirstArea (Folder)
   │  └─ FirstDrop (Model) — PrimaryPart = Main
   │     ├─ Main (Part) — Anchored
   │     ├─ HealthGui (BillboardGui) — Adornee = Main
   │     │  └─ Background (Frame)
   │     │     ├─ Bar (Frame) — зелёная полоса, Size 1, 0, 1, 0
   │     │     └─ HealthText (TextLabel)
   │     ├─ Health, MaxHealth (NumberValue) — одинаковые, например 100
   │     ├─ Coins (NumberValue) — награда
   │     └─ ClickDetector (ClickDetector)
   └─ SecondArea (Folder) — цели новой области (SecondDrop)
ServerScriptService
└─ MainScript (Script)
   ├─ PlayerInfo (Folder) — копируется в каждого игрока
   │  ├─ Values (Folder)
   │  │  └─ Target (ObjectValue) — цель игрока
   │  └─ leaderstats (Folder)
   │     └─ Coins, Gems (NumberValue) — Coins = 10 — хватит на первое яйцо
   └─ Pet (ModuleScript)
      └─ Drop (ModuleScript)

Шаги

  1. Цель: деталь «Main» (Anchored) в модели «FirstDrop», PrimaryPart = Main.
  2. Полоска: в модель BillboardGui «HealthGui» (Adornee = Main, StudsOffset 0, 4, 0) → Frame «Background» → Frame «Bar» (зелёный, Size 1, 0, 1, 0) и TextLabel «HealthText».
  3. В модель: NumberValue «Health» и «MaxHealth» (одинаковые), «Coins» (награда), ClickDetector. Цели новой области — «SecondDrop», здоровье больше, можно добавить NumberValue «Gems».
  4. Workspace → Folder «Drops» → папки «FirstArea» и «SecondArea» с целями.
  5. В MainScript — Folder «PlayerInfo»: «Values» (ObjectValue «Target») и «leaderstats» (NumberValue «Coins» = 10, «Gems»). MainScript копирует эти папки в каждого игрока; leaderstats видно по Tab.
  6. В MainScript — ModuleScript «Pet», в Pet — ModuleScript «Drop».
  7. Как работает: клик (ClickDetector) → Pet.Attack ставит цель игроку и всем его питомцам. Раз в секунду Pet.Tick складывает Damage питомцев и бьёт цель на сервере — все игроки видят одинаковую полоску. Разрушил — монеты, питомцы всех, кто бил эту цель, возвращаются, цель через 5 секунд на месте.
  8. Повторный клик по той же цели — питомцы возвращаются к хозяину.

Скрипты

Pet

Где: ServerScriptService → MainScript · ModuleScript
Зачем: Цель для питомцев и урон раз в секунду.

-- все игроки
local Players = game:GetService("Players")
-- модуль целей
local drop = require(script.Drop)
-- папки с питомцами игроков
local playerPets = workspace:WaitForChild("PlayerPets")
-- таблица модуля
local pet = {}

-- питомцы игрока, которые сейчас с ним
local function PetsOf(player)
    -- его папка
    local folder = playerPets:FindFirstChild(player.Name)
    -- список или пустая таблица
    return folder and folder:GetChildren() or {}
-- конец функции
end

-- назначает цель всем питомцам игрока (nil — отозвать)
function pet.SetTarget(player, dropObject)
    -- цель игрока
    player.Values.Target.Value = dropObject
    -- каждый питомец
    for _, playerPet in ipairs(PetsOf(player)) do
        -- бежит к цели или к хозяину
        playerPet.Attack.Value = dropObject
    -- конец перебора
    end
-- конец функции
end

-- игрок кликнул по цели
function pet.Attack(player, dropObject)
    -- питомцев нет — атаковать некому
    if #PetsOf(player) == 0 then return end
    -- клик по той же цели
    if player.Values.Target.Value == dropObject then
        -- отзываем питомцев
        pet.SetTarget(player, nil)
    -- новая цель
    else
        -- все питомцы — к ней
        pet.SetTarget(player, dropObject)
    -- конец проверки
    end
-- конец функции
end

-- раз в секунду: питомцы бьют свои цели
function pet.Tick()
    -- каждый игрок
    for _, player in ipairs(Players:GetPlayers()) do
        -- его служебная папка
        local values = player:FindFirstChild("Values")
        -- его цель
        local target = values and values.Target.Value
        -- цель уже разрушена кем-то другим
        if target and not target.Parent then
            -- питомцы возвращаются
            pet.SetTarget(player, nil)
        -- цель на месте
        elseif target then
            -- общий урон
            local damage = 0
            -- каждый питомец
            for _, playerPet in ipairs(PetsOf(player)) do
                -- его урон
                local petDamage = playerPet:FindFirstChild("Damage")
                -- без Damage — 10
                damage += petDamage and petDamage.Value or 10
            -- конец перебора
            end
            -- бьём; цель разрушена
            if damage > 0 and drop.Damage(player, target, damage) then
                -- все игроки
                for _, other in ipairs(Players:GetPlayers()) do
                    -- их цели
                    local otherValues = other:FindFirstChild("Values")
                    -- тоже били эту цель
                    if otherValues and otherValues.Target.Value == target then
                        -- их питомцы возвращаются
                        pet.SetTarget(other, nil)
                    -- конец проверки
                    end
                -- конец перебора
                end
            -- конец проверки
            end
        -- конец проверки
        end
    -- конец перебора
    end
-- конец функции
end

-- отдаём таблицу модуля
return pet

Drop

Где: ServerScriptService → MainScript → Pet · ModuleScript
Зачем: Здоровье, полоска, награда и возвращение цели.

-- таблица модуля
local drop = {}
-- через сколько секунд цель появляется снова
local RESPAWN = 5

-- обновляет полоску здоровья над целью
function drop.UpdateGui(dropObject)
    -- полоска
    local gui = dropObject:FindFirstChild("HealthGui")
    -- полоски нет
    if not gui then return end
    -- здоровье сейчас
    local health = dropObject.Health.Value
    -- максимум
    local maxHealth = dropObject.MaxHealth.Value
    -- текст «70 / 100»
    gui.Background.HealthText.Text = math.ceil(health) .. " / " .. maxHealth
    -- зелёная полоса укорачивается
    gui.Background.Bar.Size = UDim2.fromScale(health / maxHealth, 1)
-- конец функции
end

-- урон цели; true — если цель разрушена
function drop.Damage(player, dropObject, damage)
    -- здоровье цели
    local health = dropObject.Health
    -- уже разрушена
    if health.Value <= 0 then return false end
    -- вычитаем урон, не ниже нуля
    health.Value = math.max(health.Value - damage, 0)
    -- обновляем полоску
    drop.UpdateGui(dropObject)
    -- ещё цела
    if health.Value > 0 then return false end
    -- награда монетами
    player.leaderstats.Coins.Value += dropObject.Coins.Value
    -- даёт ли цель кристаллы
    local gems = dropObject:FindFirstChild("Gems")
    -- награда кристаллами
    if gems then player.leaderstats.Gems.Value += gems.Value end
    -- папка области
    local parent = dropObject.Parent
    -- копия для возвращения
    local respawn = dropObject:Clone()
    -- у копии полное здоровье
    respawn.Health.Value = respawn.MaxHealth.Value
    -- и полная полоска
    drop.UpdateGui(respawn)
    -- цель исчезает
    dropObject:Destroy()
    -- через несколько секунд
    task.delay(RESPAWN, function()
        -- цель снова на месте
        respawn.Parent = parent
    -- конец отложенной функции
    end)
    -- разрушена
    return true
-- конец функции
end

-- отдаём таблицу модуля
return drop

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 pets = RS:WaitForChild("Pets")
-- питомцы игроков на карте
local playerPets = workspace:WaitForChild("PlayerPets")
-- цели
local drops = workspace:WaitForChild("Drops")
-- питомец для проверки (до инвентаря)
local TEST_PET = "Cat"

-- игрок зашёл
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
    -- тестовый питомец
    local testPet = pets:FindFirstChild(TEST_PET)
    -- сразу с игроком
    if testPet then testPet:Clone().Parent = folder 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)

PetFollow

Где: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: + питомцы прыгают вокруг цели.

-- события каждого кадра
local RunService = game:GetService("RunService")
-- все игроки
local Players = game:GetService("Players")
-- папки с питомцами игроков
local playerPets = workspace:WaitForChild("PlayerPets")
-- полный круг в радианах (360°)
local CIRCLE = math.pi * 2

-- точка на окружности вокруг персонажа
local function GetPosition(angle, radius)
    -- x и z этой точки
    return math.cos(angle) * radius, math.sin(angle) * radius
-- конец функции
end

-- двигает питомцев одного игрока
local function PositionPets(character, folder)
    -- центр персонажа
    local root = character:FindFirstChild("HumanoidRootPart")
    -- его Humanoid
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    -- персонаж ещё не загрузился
    if not root or not humanoid then return end
    -- питомцы игрока
    local list = folder:GetChildren()
    -- сколько их
    local count = #list
    -- чем больше питомцев, тем шире круг
    local radius = 4 + count
    -- размер персонажа
    local _, characterSize = character:GetBoundingBox()
    -- каждый питомец
    for i, pet in ipairs(list) do
        -- это модель с главной деталью
        if pet:IsA("Model") and pet.PrimaryPart then
            -- своё место на круге
            local angle = i * (CIRCLE / count)
            -- координаты места
            local x, z = GetPosition(angle, radius)
            -- размер питомца
            local _, petSize = pet:GetBoundingBox()
            -- питомец стоит на земле, а не висит
            local offsetY = -characterSize.Y / 2 + petSize.Y / 2
            -- подскоки: от 0 до 1
            local bounce = math.sin(15 * time() + i) * 0.5 + 0.5
            -- покачивание вбок
            local tilt = math.cos(7 * time() + i) / 4
            -- куда питомец хочет попасть
            local goal
            -- кого атакует
            local attack = pet:FindFirstChild("Attack")
            -- цель или nil
            local target = attack and attack.Value
            -- цель есть и ещё не разрушена
            if target and target.Parent and target.PrimaryPart then
                -- центр цели
                local center = target.PrimaryPart.Position
                -- место вокруг цели
                local position = center + Vector3.new(x, 0, z)
                -- смотрит на цель и прыгает
                goal = CFrame.lookAt(position, center) * CFrame.new(0, 1 + bounce, 0) * CFrame.Angles(0, 0, tilt)
            -- хозяин идёт
            elseif humanoid.MoveDirection.Magnitude > 0 then
                -- бежит рядом вприпрыжку
                goal = root.CFrame * CFrame.new(x, offsetY + bounce, z) * CFrame.Angles(0, 0, tilt)
            -- хозяин стоит
            else
                -- спокойно стоит рядом
                goal = root.CFrame * CFrame.new(x, offsetY, z)
            -- конец проверки
            end
            -- каждый кадр проходит 10% пути — плавно
            pet:PivotTo(pet:GetPivot():Lerp(goal, 0.1))
        -- конец проверки
        end
    -- конец перебора
    end
-- конец функции
end

-- каждый кадр
RunService.RenderStepped:Connect(function()
    -- папка каждого игрока
    for _, folder in ipairs(playerPets:GetChildren()) do
        -- игрок с таким именем
        local player = Players:FindFirstChild(folder.Name)
        -- он в игре и с персонажем
        if player and player.Character then
            -- двигаем его питомцев
            PositionPets(player.Character, folder)
        -- конец проверки
        end
    -- конец перебора
    end
-- конец обработчика
end)

Проверка

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