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)
Где: 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Где: 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Где: 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)Где: 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) и убедись: