Roblox · Pet Simulator · урок 1

Питомец ходит за хозяином

Pet Simulator — симулятор, где игрок собирает питомцев, они бьют цели ради монет, монеты тратятся на яйца с новыми питомцами и на новые области. Сегодня: модель питомца и скрипт, который водит питомцев кругом за хозяином.

Структура

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

ReplicatedStorage
└─ Pets (Folder) — образцы питомцев
   ├─ Cat (Model) — PrimaryPart = Primary
   │  ├─ Primary (Part) — покрывает питомца: прозрачная, лицом вперёд
   │  ├─ Body, Head, Ear… (Part) — все Anchored, CanCollide и CanQuery выкл.
   │  ├─ Attack (ObjectValue) — кого атакует
   │  └─ Damage (NumberValue) — урон в секунду, например 10
   └─ Bunny, Dog, … (Model) — так же
Workspace
└─ PlayerPets (Folder) — папки игроков с их питомцами
ServerScriptService
└─ MainScript (Script)
StarterPlayer
└─ StarterPlayerScripts
   └─ PetFollow (LocalScript)

Шаги

  1. Механики жанра: владение питомцами, взаимодействие с ними, улучшение, валюта, открытие новых областей.
  2. Питомец: собери модель из деталей. Деталь «Primary» покрывает всего питомца: Transparency 1, CanCollide выкл., передняя грань (Orientation Indicator) — вперёд, туда, где морда.
  3. Все детали питомца: Anchored вкл., CanCollide выкл., CanQuery выкл. — питомца двигает скрипт, а клики по целям он не перехватывает.
  4. Модель — с именем питомца (Cat), PrimaryPart = Primary. В неё ObjectValue «Attack» и NumberValue «Damage» (10).
  5. ReplicatedStorage → Folder «Pets», питомцы — туда. Workspace → Folder «PlayerPets».
  6. ServerScriptService → Script «MainScript»: создаёт игроку папку в PlayerPets и для проверки сразу кладёт туда питомца TEST_PET.
  7. StarterPlayer → StarterPlayerScripts → LocalScript «PetFollow». Он у каждого игрока двигает питомцев всех игроков.
  8. Формулы: круг = 2π радиан; точка на круге: x = cos(угол) · радиус, z = sin(угол) · радиус; угол между питомцами = 2π / число питомцев; подскоки — синус от времени.

Скрипты

MainScript

Где: ServerScriptService · Script
Зачем: Папка игрока в PlayerPets и тестовый питомец.

-- все игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- образцы питомцев
local pets = RS:WaitForChild("Pets")
-- питомцы игроков на карте
local playerPets = workspace:WaitForChild("PlayerPets")
-- питомец для проверки (до инвентаря)
local TEST_PET = "Cat"

-- игрок зашёл
Players.PlayerAdded:Connect(function(player)
    -- папка для его питомцев
    local folder = Instance.new("Folder")
    -- имя = имя игрока
    folder.Name = player.Name
    -- в PlayerPets
    folder.Parent = playerPets
    -- тестовый питомец
    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)

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
            -- хозяин идёт
            if 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) и убедись: