Roblox · Тайкун · урок 7

Своё производство для каждого игрока

Мультиплеер: каждый вошедший получает своё здание на свободной платформе и появляется у его входа; при выходе здание исчезает, платформа освобождается.

Структура

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

ServerStorage
├─ TycoonTemplate (Model) — бывший IndustryModel; PrimaryPart = Floor
│  ├─ Decoration_Items (Folder)
│  │  └─ Floor (Part) — пол
│  └─ CharSpawn (Part) — у входа; Anchored, CanCollide выкл., прозрачный
└─ TycoonSpawn (Part) — образец платформы, размер = Floor
Workspace
├─ TycoonSpawns (Folder) — платформы на карте
└─ Tycoons (Folder) — производства игроков
ServerScriptService
├─ CreatingTycoon (Script)
└─ Stats (Script) — обновлён

Шаги

  1. IndustryModel переименуй в TycoonTemplate, PrimaryPart = Floor, перенеси в ServerStorage — шаблон сам не работает, работают копии.
  2. Платформы — того же размера и высоты, что Floor, Anchored; несколько штук в TycoonSpawns, между ними дорога. Одну такую — в ServerStorage как TycoonSpawn.
  3. Покупки встают на то же место относительно пола, что в шаблоне: расставь предметы в шаблоне, потом перенеси в Conveyors.
  4. Game Settings → Places → Max Players = число платформ.
  5. BuyScript в шаблоне замени на новый.

Скрипты

CreatingTycoon

Где: ServerScriptService · Script
Зачем: Ставит производство на свободную платформу, переносит игрока к входу, при выходе освобождает место.

-- серверное хранилище
local SS = game:GetService("ServerStorage")
-- шаблон производства (бывший IndustryModel)
local template = SS:WaitForChild("TycoonTemplate")
-- свободные платформы
local spawns = workspace:WaitForChild("TycoonSpawns")
-- производства игроков
local tycoons = workspace:WaitForChild("Tycoons")

-- ставит персонажа у входа в его производство
local function MoveCharacter(character, tycoon)
    -- ждём, пока персонаж загрузится
    character:WaitForChild("HumanoidRootPart")
    -- кадр: Roblox сначала сам ставит персонажа на SpawnLocation
    task.wait()
    -- переносим к CharSpawn, чуть выше пола
    character:PivotTo(tycoon.CharSpawn.CFrame + Vector3.new(0, 3, 0))
-- конец функции
end

-- игрок зашёл
game.Players.PlayerAdded:Connect(function(player)
    -- список свободных платформ
    local free = spawns:GetChildren()
    -- мест нет
    if #free == 0 then
        -- Max Players больше, чем платформ
        warn("Нет свободных платформ")
        -- производство не создаём
        return
    -- конец проверки
    end
    -- случайная свободная платформа
    local platform = free[math.random(1, #free)]
    -- копия шаблона
    local tycoon = template:Clone()
    -- производство называется именем игрока
    tycoon.Name = player.Name
    -- пол производства — точно на место платформы
    tycoon:PivotTo(platform.CFrame)
    -- владелец — сразу
    tycoon.Values.Owner.Value = player
    -- имя на двери
    tycoon.Decoration_Items.Door.OwnerGui.Owner.Text = player.Name
    -- платформа занята — убираем
    platform:Destroy()
    -- ставим в мир — скрипты внутри начинают работать
    tycoon.Parent = tycoons
    -- каждое появление персонажа
    player.CharacterAdded:Connect(function(character)
        -- у своего производства
        MoveCharacter(character, tycoon)
    -- конец обработчика
    end)
    -- персонаж появился раньше, чем мы подключились
    if player.Character then
        -- переносим и его
        MoveCharacter(player.Character, tycoon)
    -- конец проверки
    end
-- конец обработчика
end)

-- игрок вышел
game.Players.PlayerRemoving:Connect(function(player)
    -- его производство
    local tycoon = tycoons:FindFirstChild(player.Name)
    -- производства не было
    if not tycoon then return end
    -- новая свободная платформа
    local platform = SS.TycoonSpawn:Clone()
    -- на место пола производства
    platform.CFrame = tycoon.Decoration_Items.Floor.CFrame
    -- снова в списке свободных
    platform.Parent = spawns
    -- производство убираем
    tycoon:Destroy()
-- конец обработчика
end)

BuyScript

Где: ServerStorage → TycoonTemplate → Buttons → BuyButtons · Script
Зачем: Покупка встаёт на своё место внутри производства игрока.

-- папка BuyButtons
local buttons = script.Parent
-- производство: BuyButtons → Buttons → производство игрока
local tycoon = buttons.Parent.Parent
-- владелец
local valuesFolder = tycoon.Values
-- куда ставим купленное
local ownedFolder = tycoon.Owned_Items
-- серверное хранилище
local SS = game:GetService("ServerStorage")
-- пол шаблона
local templateFloor = SS:WaitForChild("TycoonTemplate").Decoration_Items.Floor
-- пол этого производства
local floor = tycoon.Decoration_Items.Floor

-- все кнопки покупки
for _, button in pairs(buttons:GetChildren()) do
    -- только парты-кнопки
    if button:IsA("BasePart") then
        -- на кнопку нажали
        button.ClickDetector.MouseClick:Connect(function(player)
            -- нажал владелец, и денег хватает
            if valuesFolder.Owner.Value == player and player.leaderstats.Money.Value >= button.Price.Value then
                -- списываем цену
                player.leaderstats.Money.Value -= button.Price.Value
                -- образец в ServerStorage.Conveyors
                local sample = button.Conveyor.Value
                -- копия
                local item = sample:Clone()
                -- то же место относительно пола, что в шаблоне
                item:PivotTo(floor.CFrame * templateFloor.CFrame:ToObjectSpace(sample:GetPivot()))
                -- ставим в производство
                item.Parent = ownedFolder
                -- кнопку убираем
                button:Destroy()
            -- конец проверки
            end
        -- конец обработчика
        end)
    -- конец проверки типа
    end
-- конец перебора
end

Stats

Где: ServerScriptService · Script
Зачем: Сохранение для производства конкретного игрока.

-- сервис сохранений
local DSS = game:GetService("DataStoreService")
-- наше хранилище
local store = DSS:GetDataStore("TycoonData")
-- серверное хранилище
local SS = game:GetService("ServerStorage")
-- пол шаблона — от него отсчитываем места предметов
local templateFloor = SS:WaitForChild("TycoonTemplate").Decoration_Items.Floor

-- сохраняет данные игрока
local function Save(player)
    -- данные не загрузились — не затираем сохранение нулями
    if not player:GetAttribute("DataLoaded") then return end
    -- список купленного
    local items = {}
    -- всё, что игрок купил
    for _, item in pairs(player.ItemsFolder:GetChildren()) do
        -- запоминаем имя
        table.insert(items, item.Name)
    -- конец перебора
    end
    -- кошелёк, касса, покупки
    local data = {Money = player.leaderstats.Money.Value, Gain = player.GainIndustry.Value, Items = items}
    -- защищённый вызов: сеть может подвести
    local success, err = pcall(function()
        -- записываем по ID игрока
        store:SetAsync(player.UserId, data)
    -- конец защищённого вызова
    end)
    -- не получилось — пишем причину в Output
    if not success then warn(err) end
-- конец функции
end

-- игрок зашёл
game.Players.PlayerAdded:Connect(function(player)
    -- папка для таблицы лидеров
    local leaderstats = Instance.new("Folder", player)
    -- имя, которое понимает Roblox
    leaderstats.Name = "leaderstats"
    -- кошелёк
    local money = Instance.new("NumberValue", leaderstats)
    -- называем Money
    money.Name = "Money"
    -- копия кассы — чтобы сохранить её при выходе
    local gainIndustry = Instance.new("NumberValue", player)
    -- называем GainIndustry
    gainIndustry.Name = "GainIndustry"
    -- список покупок игрока
    local itemsFolder = Instance.new("Folder", player)
    -- называем ItemsFolder
    itemsFolder.Name = "ItemsFolder"
    -- ждём, пока CreatingTycoon поставит производство
    local tycoon = workspace.Tycoons:WaitForChild(player.Name, 10)
    -- свободного места не было — сохранять нечего
    if not tycoon then return end
    -- купленное в его производстве
    local ownedItems = tycoon.Owned_Items
    -- его кнопки покупки
    local buttons = tycoon.Buttons.BuyButtons
    -- пол его производства
    local floor = tycoon.Decoration_Items.Floor
    -- что-то куплено
    ownedItems.ChildAdded:Connect(function(child)
        -- записываем в список покупок
        local item = Instance.new("StringValue", itemsFolder)
        -- по имени предмета
        item.Name = child.Name
    -- конец обработчика
    end)

    -- защищённый вызов
    local success, data = pcall(function()
        -- читаем сохранение
        return store:GetAsync(player.UserId)
    -- конец защищённого вызова
    end)
    -- прочитали — при выходе можно сохранять
    if success then player:SetAttribute("DataLoaded", true) end
    -- прочитали, и сохранение есть (у нового игрока его нет)
    if success and data then
        -- кошелёк
        money.Value = data.Money or 0
        -- касса
        gainIndustry.Value = data.Gain or 0
        -- возвращаем деньги в кассу производства
        tycoon.Values.Gain.Value = gainIndustry.Value
        -- все покупки
        for _, name in pairs(data.Items or {}) do
            -- образец в ServerStorage
            local sample = SS.Conveyors:FindFirstChild(name)
            -- образец есть и ещё не стоит
            if sample and not ownedItems:FindFirstChild(name) then
                -- копия
                local item = sample:Clone()
                -- то же место относительно пола, что в шаблоне
                item:PivotTo(floor.CFrame * templateFloor.CFrame:ToObjectSpace(sample:GetPivot()))
                -- ставим в производство
                item.Parent = ownedItems
                -- кнопка с тем же именем
                local button = buttons:FindFirstChild(name)
                -- уже куплено — кнопку убираем
                if button then button:Destroy() end
            -- конец проверки
            end
        -- конец перебора
        end
    -- сервис недоступен
    elseif not success then
        -- причина — в Output
        warn(data)
    -- конец проверки
    end
-- конец обработчика
end)

-- игрок вышел — сохраняем
game.Players.PlayerRemoving:Connect(Save)
-- сервер выключается (и Stop в Studio)
game:BindToClose(function()
    -- всех, кто ещё в игре
    for _, player in pairs(game.Players:GetPlayers()) do
        -- сохраняем
        Save(player)
    -- конец перебора
    end
-- конец функции
end)

Проверка

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