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) — обновлён
Где: 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)Где: 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Где: 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) и убедись: