Roblox · Pet Simulator · урок 7
Итог курса на одной странице: полная структура Pet Simulator и финальные версии всех скриптов — без «добавь строку» и «замени блок». Удобно сверить свою игру или собрать её заново.
Синим — что добавляем на этом уроке.
Workspace ├─ PlayerPets (Folder) — папка каждого игрока с его питомцами ├─ Drops (Folder) │ └─ FirstArea, SecondArea (Folder) │ └─ FirstDrop, SecondDrop (Model) — Main, HealthGui (Background → Bar, HealthText), Health, MaxHealth, Coins, (Gems), ClickDetector ├─ Eggs (Folder) │ └─ Start Egg, Gem Egg (Part) — ProximityPrompt └─ Doors (Folder) └─ SecondArea (Part) — Price, PriceGui → PriceText, Prompt → ProximityPrompt ReplicatedStorage ├─ Pets (Folder) │ └─ Bunny, Cat, Dog, Dragon (Model) — Primary + детали (Anchored), Attack, Damage └─ Events (Folder) ├─ HatchEggEvent, BuyAreaEvent (RemoteEvent) └─ EquipPetFunction, BuyAreaFunction (RemoteFunction) ServerScriptService ├─ MainScript (Script) │ ├─ PlayerInfo (Folder) │ │ ├─ Values (Folder) — Target (ObjectValue) │ │ ├─ leaderstats (Folder) — Coins, Gems │ │ ├─ Pets (Folder) │ │ └─ Areas (Folder) │ ├─ Pet (ModuleScript) │ │ └─ Drop (ModuleScript) │ └─ SaveData (ModuleScript) └─ EggsServer (Script) └─ EggInfo (ModuleScript) StarterPlayer → StarterPlayerScripts └─ PetFollow, EggsLocal, MainLocal (LocalScript) StarterGui — у всех ResetOnSpawn выкл. ├─ EggSystemGui (ScreenGui) — EggViewport → EggName, EggRarity ├─ PetInventoryGui (ScreenGui) — OpenCloseButton (+ OpenCloseScript), PetFrame → Title, ScrollingFrame → Sample → Button, PetInventoryLocal └─ BuyAreaGui (ScreenGui) — BuyFrame → Question, YesButton, NoButton
Где: 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Где: 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 → MainScript · ModuleScript
Зачем: Загрузка и сохранение.
-- хранилище данных Roblox
local DSS = game:GetService("DataStoreService")
-- все игроки
local Players = game:GetService("Players")
-- наше хранилище
local dataStore = DSS:GetDataStore("PetSimulatorData")
-- какие папки игрока сохраняем (Values — нет)
local SAVE = {"leaderstats", "Pets", "Areas"}
-- таблица модуля
local saveData = {}
-- у кого данные загрузились
local loaded = {}
-- загрузить данные игрока
function saveData.Load(player)
-- запрос может не пройти
local ok, data = pcall(function()
-- данные по номеру игрока
return dataStore:GetAsync(player.UserId .. "Key")
-- конец попытки
end)
-- не загрузилось
if not ok then
-- без загрузки не сохраняем — иначе затрём
warn("Данные не загрузились (" .. tostring(data) .. ") — сохранение для " .. player.Name .. " выключено")
-- играем без сохранения
return
-- конец проверки
end
-- можно сохранять
loaded[player] = true
-- новый игрок — данных ещё нет
if not data then return end
-- каждая сохраняемая папка
for _, folderName in ipairs(SAVE) do
-- папка в игроке
local folder = player:FindFirstChild(folderName)
-- что сохранено
local saved = data[folderName]
-- есть и то и другое
if folder and saved then
-- каждое значение
for _, item in ipairs(saved) do
-- такое значение уже есть (Coins, Gems)
local existing = folder:FindFirstChild(item[1])
-- есть
if existing then
-- просто меняем число
existing.Value = item[2]
-- нет (питомцы, области)
else
-- создаём того же класса
local value = Instance.new(item[3])
-- имя
value.Name = item[1]
-- значение
value.Value = item[2]
-- в папку
value.Parent = folder
-- конец проверки
end
-- конец перебора
end
-- конец проверки
end
-- конец перебора
end
-- конец функции
end
-- сохранить данные игрока
function saveData.Save(player)
-- не загружали — не трогаем
if not loaded[player] then return end
-- второй раз не сохраняем
loaded[player] = nil
-- что запишем
local data = {}
-- каждая папка
for _, folderName in ipairs(SAVE) do
-- в игроке
local folder = player:FindFirstChild(folderName)
-- есть
if folder then
-- список значений
data[folderName] = {}
-- каждое значение
for _, value in ipairs(folder:GetChildren()) do
-- имя, значение, класс
table.insert(data[folderName], {value.Name, value.Value, value.ClassName})
-- конец перебора
end
-- конец проверки
end
-- конец перебора
end
-- запись может не пройти
local ok, err = pcall(function()
-- записываем
dataStore:SetAsync(player.UserId .. "Key", data)
-- конец попытки
end)
-- результат в Output
if ok then print("Сохранено: " .. player.Name) else warn(err) end
-- конец функции
end
-- сохранить всех и дождаться (выключение сервера)
function saveData.SaveAll()
-- сколько сохранений ещё идёт
local left = 0
-- каждый игрок
for _, player in ipairs(Players:GetPlayers()) do
-- ещё одно
left += 1
-- параллельно
task.spawn(function()
-- сохраняем
saveData.Save(player)
-- готово
left -= 1
-- конец потока
end)
-- конец перебора
end
-- ждём все сохранения
while left > 0 do task.wait() end
-- конец функции
end
-- отдаём таблицу модуля
return saveDataГде: ServerScriptService · Script
Зачем: Покупка яиц.
-- данные яиц
local eggData = require(script.EggInfo)
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- яйца на карте
local eggsFolder = workspace:WaitForChild("Eggs")
-- показать анимацию игроку
local hatchEggEvent = RS:WaitForChild("Events"):WaitForChild("HatchEggEvent")
-- кто сейчас открывает яйцо
local hatching = {}
-- случайный питомец с учётом шансов
local function SelectRandomPet(petTable)
-- сумма шансов
local total = 0
-- складываем — не обязательно 100
for _, info in ipairs(petTable) do total += info.Chance end
-- случайное число
local roll = math.random(1, total)
-- накопленный шанс
local sum = 0
-- по питомцам
for _, info in ipairs(petTable) do
-- прибавляем
sum += info.Chance
-- число попало на этого питомца
if roll <= sum then return info end
-- конец перебора
end
-- конец функции
end
-- каждое яйцо
for _, egg in ipairs(eggsFolder:GetChildren()) do
-- его данные
local info = eggData[egg.Name]
-- надпись «Купить»
local prompt = egg:FindFirstChildWhichIsA("ProximityPrompt", true)
-- чего-то нет
if not info or not prompt then
-- подсказка в Output
warn("Яйцо " .. egg.Name .. ": нет данных в EggInfo или ProximityPrompt")
-- всё есть
else
-- название
prompt.ObjectText = egg.Name
-- цена на надписи
prompt.ActionText = info.EggPrice .. " " .. info.EggMoney
-- игрок нажал E
prompt.Triggered:Connect(function(player)
-- уже открывает — ждём
if hatching[player] then return end
-- нужная валюта
local money = player.leaderstats:FindFirstChild(info.EggMoney)
-- не хватает
if not money or money.Value < info.EggPrice then return end
-- открывает
hatching[player] = true
-- списываем
money.Value -= info.EggPrice
-- кто выпал
local selected = SelectRandomPet(info.EggPets)
-- запись о питомце
local petValue = Instance.new("StringValue")
-- имя
petValue.Name = selected.PetName
-- в папку Pets игрока
petValue.Parent = player.Pets
-- анимация у игрока
hatchEggEvent:FireClient(player, egg.Name, selected.PetName, selected.Rarity)
-- пока идёт анимация
task.wait(5)
-- можно открыть следующее
hatching[player] = nil
-- конец обработчика
end)
-- конец проверки
end
-- конец перебора
endГде: ServerScriptService → EggsServer · ModuleScript
Зачем: Цены яиц и шансы питомцев.
-- все яйца
local eggData = {
-- имя — как у яйца в папке Eggs
["Start Egg"] = {
-- цена
EggPrice = 10,
-- валюта: Coins или Gems
EggMoney = "Coins",
-- кто может выпасть
EggPets = {
-- имя — как у модели в Pets; Chance — вес
{PetName = "Bunny", Chance = 50, Rarity = "Common"},
-- ещё один
{PetName = "Cat", Chance = 50, Rarity = "Common"},
-- конец списка
},
-- конец яйца
},
-- яйцо за кристаллы
["Gem Egg"] = {
-- цена
EggPrice = 5,
-- валюта
EggMoney = "Gems",
-- кто может выпасть
EggPets = {
-- чаще
{PetName = "Dog", Chance = 70, Rarity = "Uncommon"},
-- реже
{PetName = "Dragon", Chance = 30, Rarity = "Rare"},
-- конец списка
},
-- конец яйца
},
-- конец таблицы
}
-- отдаём таблицу
return eggDataГде: 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)Где: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: Анимация яйца.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- анимации
local TS = game:GetService("TweenService")
-- яйца
local eggsFolder = workspace:WaitForChild("Eggs")
-- образцы питомцев
local pets = RS:WaitForChild("Pets")
-- сигнал от сервера
local hatchEggEvent = RS:WaitForChild("Events"):WaitForChild("HatchEggEvent")
-- этот игрок
local player = game.Players.LocalPlayer
-- его интерфейс
local playerGui = player:WaitForChild("PlayerGui")
-- окно яйца
local eggSystemGui = playerGui:WaitForChild("EggSystemGui")
-- 3D-окошко
local eggViewport = eggSystemGui:WaitForChild("EggViewport")
-- имя питомца
local eggName = eggViewport:WaitForChild("EggName")
-- редкость
local eggRarity = eggViewport:WaitForChild("EggRarity")
-- размер окна
local FULL = UDim2.fromScale(0.3, 0.5)
-- окно схлопнуто
local HIDDEN = UDim2.fromScale(0, 0)
-- убрать из окошка яйцо, питомца и камеру
local function Clear()
-- всё внутри
for _, obj in ipairs(eggViewport:GetChildren()) do
-- камеры, детали, модели (надписи остаются)
if obj:IsA("Camera") or obj:IsA("PVInstance") then obj:Destroy() end
-- конец перебора
end
-- конец функции
end
-- поставить объект в окошко
local function Show(object)
-- старое убираем
Clear()
-- новый объект
object.Parent = eggViewport
-- его размер
local size = object:IsA("Model") and object:GetExtentsSize() or object.Size
-- камера окошка
local camera = Instance.new("Camera")
-- отходим тем дальше, чем больше объект
camera.CFrame = CFrame.new(0, 0, size.Magnitude * 1.1)
-- в окошко
camera.Parent = eggViewport
-- окошко смотрит через неё
eggViewport.CurrentCamera = camera
-- конец функции
end
-- плавно меняет размер окна
local function Resize(size)
-- за полсекунды
local tween = TS:Create(eggViewport, TweenInfo.new(0.5), {Size = size})
-- запускаем
tween:Play()
-- ждём
tween.Completed:Wait()
-- конец функции
end
-- наклон окна — яйцо качается
local function Rotate(rotation)
-- за 0,3 с
local tween = TS:Create(eggViewport, TweenInfo.new(0.3, Enum.EasingStyle.Sine), {Rotation = rotation})
-- запускаем
tween:Play()
-- ждём
tween.Completed:Wait()
-- конец функции
end
-- вся анимация открытия
local function HatchEgg(eggNameText, petName, petRarity)
-- интерфейсы, которые прячем
local hidden = {}
-- весь интерфейс игрока
for _, ui in ipairs(playerGui:GetChildren()) do
-- включённый и не окно яйца
if ui:IsA("ScreenGui") and ui ~= eggSystemGui and ui.Enabled then
-- прячем
ui.Enabled = false
-- запоминаем, чтобы вернуть только их
table.insert(hidden, ui)
-- конец проверки
end
-- конец перебора
end
-- надписи пока скрыты
eggName.Visible = false
-- тоже
eggRarity.Visible = false
-- окно схлопнуто
eggViewport.Size = HIDDEN
-- включаем окно яйца
eggSystemGui.Enabled = true
-- копия яйца
local egg = eggsFolder:FindFirstChild(eggNameText):Clone()
-- в центр окошка
egg:PivotTo(CFrame.new())
-- показываем
Show(egg)
-- окно раскрывается
Resize(FULL)
-- два раза
for _ = 1, 2 do
-- качнулось вправо
Rotate(20)
-- влево
Rotate(-20)
-- конец цикла
end
-- ровно
Rotate(0)
-- окно схлопнулось — яйцо «лопнуло»
Resize(HIDDEN)
-- образец питомца
local template = pets:FindFirstChild(petName)
-- есть
if template then
-- копия
local petModel = template:Clone()
-- мордой к камере
petModel:PivotTo(CFrame.Angles(0, math.rad(180), 0))
-- показываем
Show(petModel)
-- конец проверки
end
-- имя
eggName.Text = petName
-- редкость
eggRarity.Text = petRarity
-- показываем
eggName.Visible = true
-- показываем
eggRarity.Visible = true
-- окно раскрывается
Resize(FULL)
-- любуемся
task.wait(3)
-- схлопываем
Resize(HIDDEN)
-- чистим
Clear()
-- выключаем окно
eggSystemGui.Enabled = false
-- возвращаем спрятанные интерфейсы
for _, ui in ipairs(hidden) do ui.Enabled = true end
-- конец функции
end
-- сервер прислал питомца — анимация
hatchEggEvent.OnClientEvent:Connect(HatchEgg)Где: 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)Где: StarterGui → PetInventoryGui → OpenCloseButton · LocalScript
Зачем: Кнопка инвентаря.
-- кнопка «Питомцы»
local button = script.Parent
-- окно инвентаря
local frame = button.Parent:WaitForChild("PetFrame")
-- нажали
button.MouseButton1Click:Connect(function()
-- открыто — закрываем, закрыто — открываем
frame.Visible = not frame.Visible
-- конец обработчика
end)Где: 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)Запусти Play (или Run) и убедись: