Roblox · Королевская битва · урок 5

Лобби

Игроки ждут раунда в лобби на платформе в небе: вошёл в ворота — попал на площадку готовых, табло считает игроков и секунды, по нулю экран темнеет и всех переносит на остров.

Структура

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

Workspace
├─ Lobby (Model) — платформа в небе далеко от острова
│  ├─ SpawnLocation (SpawnLocation) — все появляются здесь
│  ├─ Gate (Model) — арка ворот
│  │  ├─ Gate (Part) — светящаяся рамка, CanCollide выкл.
│  │  ├─ GuiPart (Part) — невидимая; Board (BillboardGui) → Frame → Status, Players, Time; UIPadding
│  │  └─ GateScript (Script)
│  ├─ ReadyZone (Model) — Barrier — стенки площадки; TeleportPart — невидимая середина
│  └─ LeaveRoomPart (Part) — невидимая, куда вернуть передумавшего
└─ MapSpawn (Part) — невидимая точка на острове (до урока «Десантирование»)
ReplicatedStorage
└─ LeaveEvent, TeleportingEvent (RemoteEvent)
StarterGui
└─ MainGui (ScreenGui) — ResetOnSpawn выкл.
   ├─ Teleporting (Frame) — на весь экран, чёрный, BackgroundTransparency 1
   │  ├─ TextLabel (TextLabel) — TextTransparency 1
   │  └─ TeleportingLocal (LocalScript)
   └─ LeaveButton (TextButton) — «Выйти»
      └─ LeaveLocal (LocalScript)

Шаги

  1. Зачем лобби: пока собираются игроки, можно пострелять, покататься, познакомиться. Лобби небольшое — основной игры в нём нет.
  2. Здесь лобби — платформа в небе над морем, далеко от острова (например, в 600 studs). Единственный SpawnLocation — в лобби: погибшие возвращаются сюда.
  3. Ворота «Gate»: арка + светящаяся рамка «Gate» (CanCollide выкл.) + невидимая «GuiPart» с BillboardGui «Board» → Frame (UIPadding — отступы от краёв) → TextLabel «Status», «Players», «Time».
  4. Площадка готовых «ReadyZone»: стенки «Barrier» и невидимая «TeleportPart» в середине. Рядом с воротами — невидимая «LeaveRoomPart». На острове — невидимая «MapSpawn».
  5. MainGui: Frame «Teleporting» на весь экран (Size {1,0},{1,0}, IgnoreGuiInset у ScreenGui вкл.), прозрачность через BackgroundTransparency и TextTransparency = 1, а не Visible — чтобы плавно проявлять. Кнопка «LeaveButton».
  6. MIN_PLAYERS = 1 — чтобы проверить одному; в настоящей игре 2 и больше.
  7. Лобби отдельным плейсом (как в методичке): опубликуй лобби, остров — File → Publish As → игра лобби → Add as a new place; Game Settings → Security → Allow HTTP/API; в GateScript вместо переноса — TeleportService:ReserveServer(placeId) и TeleportToPrivateServer. Работает только в опубликованной игре, не в Studio — поэтому мы делаем одну карту.

Скрипты

GateScript

Где: Workspace → Lobby → Gate · Script
Зачем: Готовые, табло, отправка.

-- игроки
local Players = game:GetService("Players")
-- события
local RS = game:GetService("ReplicatedStorage")
-- сигнал начала раунда
local SS = game:GetService("ServerStorage")
-- модель Gate
local gate = script.Parent
-- модель Lobby
local lobby = gate.Parent
-- светящаяся рамка — войти в неё
local gatePart = gate:WaitForChild("Gate")
-- табло над воротами
local board = gate:WaitForChild("GuiPart"):WaitForChild("Board"):WaitForChild("Frame")
-- площадка готовых
local readyPart = lobby:WaitForChild("ReadyZone"):WaitForChild("TeleportPart")
-- куда вернуть, если передумал
local leavePart = lobby:WaitForChild("LeaveRoomPart")
-- кнопка «Выйти»
local leaveEvent = RS:WaitForChild("LeaveEvent")
-- затемнение экрана
local teleportingEvent = RS:WaitForChild("TeleportingEvent")
-- точка на острове (до урока «Десантирование»)
local mapSpawn = workspace:WaitForChild("MapSpawn")
-- мест в раунде
local MAX_PLAYERS = 30
-- для проверки одному; в настоящей игре поставь 2 и больше
local MIN_PLAYERS = 1
-- секунд отсчёта
local WAIT_TIME = 30
-- игрок → true: кто вошёл в ворота
local ready = {}
-- идёт отправка — ворота закрыты
local sending = false

-- сколько готовых
local function Count()
    -- счётчик
    local n = 0
    -- каждый готовый
    for player in pairs(ready) do
        -- вышел из игры — убираем
        if player.Parent then n += 1 else ready[player] = nil end
    -- конец перебора
    end
    -- число
    return n
-- конец функции
end
-- обновить табло
local function UpdateBoard(status, seconds)
    -- готовые
    board.Players.Text = "Игроков: " .. Count() .. "/" .. MAX_PLAYERS
    -- состояние
    board.Status.Text = status
    -- отсчёт
    board.Time.Text = seconds and (seconds .. " с") or ""
-- конец функции
end

-- кто-то вошёл в ворота
gatePart.Touched:Connect(function(hit)
    -- модель персонажа
    local character = hit.Parent
    -- игрок (а не NPC или предмет)
    local player = Players:GetPlayerFromCharacter(character)
    -- Humanoid
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    -- уже готов, отправка или погиб
    if not player or ready[player] or sending or not humanoid or humanoid.Health <= 0 then return end
    -- мест нет
    if Count() >= MAX_PLAYERS then return end
    -- готов
    ready[player] = true
    -- на площадку готовых
    character:PivotTo(readyPart.CFrame + Vector3.new(math.random(-6, 6), 3, math.random(-6, 6)))
    -- показать кнопку «Выйти»
    leaveEvent:FireClient(player, true)
-- конец функции
end)
-- нажал «Выйти»
leaveEvent.OnServerEvent:Connect(function(player)
    -- не в списке или уже отправляем
    if not ready[player] or sending then return end
    -- не готов
    ready[player] = nil
    -- обратно в лобби
    if player.Character then player.Character:PivotTo(leavePart.CFrame + Vector3.new(0, 3, 0)) end
    -- спрятать кнопку
    leaveEvent:FireClient(player, false)
-- конец функции
end)
-- игрок зашёл
local function Setup(player)
    -- персонаж пропал — не готов
    player.CharacterRemoving:Connect(function() ready[player] = nil end)
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do Setup(player) end

-- бесконечно
while true do
    -- отсчёт с начала
    local left = WAIT_TIME
    -- пока не 0
    while left > 0 do
        -- мало игроков
        if Count() < MIN_PLAYERS then
            -- отсчёт заново
            left = WAIT_TIME
            -- табло
            UpdateBoard("ЖДЁМ ИГРОКОВ")
        -- хватает
        else
            -- табло с секундами
            UpdateBoard("ГОТОВЬСЯ", left)
            -- минус секунда
            left -= 1
        -- конец проверки
        end
        -- секунда
        task.wait(1)
    -- конец отсчёта
    end
    -- ворота закрыты
    sending = true
    -- кого отправляем
    local list = {}
    -- готовые
    for player in pairs(ready) do
        -- ещё в игре
        if player.Parent then
            -- в список
            table.insert(list, player)
            -- кнопка «Выйти» больше не нужна
            leaveEvent:FireClient(player, false)
            -- экран темнеет
            teleportingEvent:FireClient(player)
        -- конец проверки
        end
    -- конец перебора
    end
    -- список пуст
    table.clear(ready)
    -- табло
    UpdateBoard("ОТПРАВКА")
    -- экран успел потемнеть
    task.wait(2)
    -- каждого
    for _, player in ipairs(list) do
        -- на остров
        if player.Character then player.Character:PivotTo(mapSpawn.CFrame + Vector3.new(math.random(-10, 10), 4, math.random(-10, 10))) end
    -- конец перебора
    end
    -- ворота снова открыты
    sending = false
-- конец цикла
end

TeleportingLocal

Где: StarterGui → MainGui → Teleporting · LocalScript
Зачем: Затемнение.

-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- плавность
local TS = game:GetService("TweenService")
-- Frame «Teleporting» на весь экран
local background = script.Parent
-- надпись
local text = background:WaitForChild("TextLabel")
-- сервер отправляет
local teleportingEvent = RS:WaitForChild("TeleportingEvent")
-- 2 секунды
local info = TweenInfo.new(2)

-- отправка
teleportingEvent.OnClientEvent:Connect(function()
    -- экран темнеет
    TS:Create(background, info, {BackgroundTransparency = 0}):Play()
    -- надпись проявляется
    TS:Create(text, info, {TextTransparency = 0}):Play()
    -- пока переносят
    task.wait(3.5)
    -- экран светлеет
    TS:Create(background, info, {BackgroundTransparency = 1}):Play()
    -- надпись исчезает
    TS:Create(text, info, {TextTransparency = 1}):Play()
-- конец функции
end)

LeaveLocal

Где: StarterGui → MainGui → LeaveButton · LocalScript
Зачем: Кнопка «Выйти».

-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- кнопка «Выйти»
local button = script.Parent
-- событие
local leaveEvent = RS:WaitForChild("LeaveEvent")
-- сначала скрыта
button.Visible = false

-- сервер: показать или спрятать
leaveEvent.OnClientEvent:Connect(function(show)
    -- видна или нет
    button.Visible = show
-- конец функции
end)
-- клик
button.MouseButton1Click:Connect(function()
    -- просим вернуть в лобби
    leaveEvent:FireServer()
-- конец функции
end)

Проверка

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