Roblox · Doors · урок 8

Финал и публикация

За последней дверью — финальная комната. Камера разворачивается, дверь за спиной медленно захлопывается, экран темнеет, появляется надпись. Публикуем игру.

Структура

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

Workspace
└─ EndRoom (Model) — как StartRoom, но с Enter и без Exit
   ├─ Build → Lights (Model)
   ├─ Enter (Part)
   └─ CameraPart (Part) — точка камеры: прозрачная, лицом вглубь комнаты
ReplicatedStorage
└─ EndEvent (RemoteEvent) — сервер → игроки
StarterGui
└─ EndGui (ScreenGui) — IgnoreGuiInset вкл.
   └─ Frame (Frame) — на весь экран, чёрный
      ├─ EndText (TextLabel) — «Ты выбрался!»
      └─ EndLocal (LocalScript)

Шаги

  1. «EndRoom» — как стартовая, но с Enter и без Exit; лампы в Build → Lights. Положи в Workspace подальше от старта — MainScript сам поставит её за последней дверью.
  2. В EndRoom — прозрачная деталь «CameraPart» (CanCollide и CanQuery выкл.) в нескольких studs от входа, передней гранью вглубь комнаты. Камера встанет сюда и развернётся к двери.
  3. ReplicatedStorage → RemoteEvent «EndEvent» (камера и экран — у игрока, поэтому Remote).
  4. StarterGui → ScreenGui «EndGui» (IgnoreGuiInset вкл.) → Frame на весь экран (Size 1, 0, 1, 0), чёрный → TextLabel «EndText». Размеры — через Scale. В Frame — LocalScript «EndLocal».
  5. Door (функция Close) и MainScript обновились.
  6. Публикация: скриншоты; Game Settings → Places — 1–4 игрока; Basic Info — название, описание, устройства (телефон, планшет, консоль); Permissions → Public.

Скрипты

Door

Где: ServerScriptService → MainScript → Room · ModuleScript
Зачем: + медленное закрытие двери.

-- плавный поворот двери
local TS = game:GetService("TweenService")
-- здесь лежит образец двери
local SS = game:GetService("ServerStorage")
-- сообщает главному скрипту номер открытой двери
local enterEvent = SS:WaitForChild("EnterEvent")

-- таблица модуля
local door = {}

-- открывает дверь
function door.Open(doorModel, number)
    -- отмечаем: открыта
    doorModel:SetAttribute("Open", true)
    -- петля повёрнута на 90°
    local cframe = doorModel.Spinner.CFrame * CFrame.Angles(0, math.rad(90), 0)
    -- за полсекунды
    local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Linear)
    -- поворачиваем петлю — дверь приварена и едет следом
    TS:Create(doorModel.Spinner, tweenInfo, {CFrame = cframe}):Play()
    -- скрип двери
    doorModel.Door.Open:Play()
    -- игрок вошёл в комнату number + 1
    enterEvent:Fire(number)
-- конец функции
end

-- медленно захлопывает дверь (финал)
function door.Close(doorModel)
    -- обратно на 90°
    local cframe = doorModel.Spinner.CFrame * CFrame.Angles(0, math.rad(-90), 0)
    -- 10 секунд, с отскоком в конце
    local tweenInfo = TweenInfo.new(10, Enum.EasingStyle.Bounce, Enum.EasingDirection.Out)
    -- закрываем
    TS:Create(doorModel.Spinner, tweenInfo, {CFrame = cframe}):Play()
-- конец функции
end

-- ключ в руках или в рюкзаке
local function FindKey(character)
    -- чей это персонаж
    local player = game.Players:GetPlayerFromCharacter(character)
    -- Tool «Key» или nil
    return character:FindFirstChild("Key") or (player and player.Backpack:FindFirstChild("Key"))
-- конец функции
end

-- ставит дверь на выход комнаты
function door.New(roomModel, number, locked)
    -- копия образца
    local doorModel = SS.Door:Clone()
    -- в дверной проём
    doorModel:PivotTo(roomModel.Exit.CFrame)
    -- закрыта
    doorModel:SetAttribute("Open", false)
    -- номер на табличке: 01, 02…
    doorModel.Label.NumberGui.Number.Text = string.format("%02d", number)
    -- замок виден, только если дверь заперта
    doorModel.Lock.Transparency = locked and 0 or 1
    -- кто-то подошёл к двери
    doorModel.Trigger.Touched:Connect(function(otherPart)
        -- модель, которой принадлежит деталь
        local character = otherPart.Parent
        -- это персонаж?
        local humanoid = character:FindFirstChildOfClass("Humanoid")
        -- не персонаж или уже открыта
        if not humanoid or doorModel:GetAttribute("Open") then return end
        -- замка нет
        if not locked then
            -- просто открываем
            door.Open(doorModel, number)
            -- готово
            return
        -- конец проверки
        end
        -- есть ли у игрока ключ
        local key = FindKey(character)
        -- есть
        if key then
            -- дверь больше не заперта
            locked = false
            -- ключ тратится
            key:Destroy()
            -- замок исчезает
            doorModel.Lock:Destroy()
            -- щелчок замка
            doorModel.Door.Unlock:Play()
            -- открываем
            door.Open(doorModel, number)
        -- конец проверки
        end
    -- конец обработчика
    end)
    -- дверь — часть комнаты
    doorModel.Parent = roomModel
    -- отдаём дверь
    return doorModel
-- конец функции
end

-- отдаём таблицу модуля
return door

MainScript

Где: ServerScriptService · Script
Зачем: Финальная комната и катсцена после последней двери.

-- модуль комнат
local room = require(script.Room)
-- модуль дверей
local door = require(script.Room.Door)
-- модуль монстра
local monster = require(script.Monster)
-- номер открытой двери
local enterEvent = game.ServerStorage:WaitForChild("EnterEvent")
-- финал — всем игрокам
local endEvent = game.ReplicatedStorage:WaitForChild("EndEvent")
-- сколько комнат вместе со стартовой
local ROOMS = 10
-- монстр после каждой 3-й двери
local MONSTER_EVERY = 3

-- первая «предыдущая» — стартовая
local prevRoom = workspace.StartRoom
-- финальная комната
local endRoom = workspace.EndRoom
-- все комнаты по номерам: 1 — стартовая
local generatedRooms = {prevRoom}
-- дверь 01 в стартовой комнате
door.New(prevRoom, 1, false)
-- комнаты 2…10
for i = 2, ROOMS do
    -- новая комната с дверью номер i
    prevRoom = room.Generate(prevRoom, i)
    -- запоминаем под номером
    generatedRooms[i] = prevRoom
-- конец цикла
end
-- двигаем финальную комнату за вход
endRoom.PrimaryPart = endRoom.Enter
-- ставим за последней комнатой
endRoom:PivotTo(prevRoom.Exit.CFrame)

-- финал уже показан?
local finished = false
-- открыта дверь number
enterEvent.Event:Connect(function(number)
    -- открыта последняя дверь — финал
    if number == #generatedRooms then
        -- уже показали
        if finished then return end
        -- больше не показываем
        finished = true
        -- игрок заходит в финальную комнату
        task.wait(2)
        -- катсцена у всех игроков
        endEvent:FireAllClients()
        -- камера разворачивается
        task.wait(2)
        -- дверь за спиной медленно закрывается
        door.Close(prevRoom.Door)
        -- свет мигает
        room.Lights(endRoom)
    -- номер делится на 3 — монстр
    elseif number % MONSTER_EVERY == 0 then
        -- комната, куда вошёл игрок
        local nextRoom = generatedRooms[number + 1]
        -- звук мигания
        local sound = workspace:FindFirstChild("Sounds") and workspace.Sounds:FindFirstChild("Lights")
        -- играем, если есть
        if sound then sound:Play() end
        -- лампы мигают — предупреждение
        room.Lights(nextRoom)
        -- 2 секунды, чтобы спрятаться
        task.wait(2)
        -- монстр пролетает комнаты
        monster.New(number, generatedRooms)
    -- конец проверки
    end
-- конец обработчика
end)

EndLocal

Где: StarterGui → EndGui → Frame · LocalScript
Зачем: Камера, затемнение и надпись.

-- плавные изменения
local TS = game:GetService("TweenService")
-- чёрный экран
local frame = script.Parent
-- надпись
local text = frame:WaitForChild("EndText")
-- камера игрока
local camera = workspace.CurrentCamera
-- сигнал финала
local endEvent = game.ReplicatedStorage:WaitForChild("EndEvent")
-- 2 секунды, ровно
local tweenInfo = TweenInfo.new(2, Enum.EasingStyle.Linear)

-- экран пока прозрачный
frame.BackgroundTransparency = 1
-- текст тоже
text.TextTransparency = 1

-- сервер прислал финал
endEvent.OnClientEvent:Connect(function()
    -- точка камеры — берём сейчас, когда комната уже на месте
    local cameraPart = workspace.EndRoom:WaitForChild("CameraPart")
    -- развернуться к двери
    local goal = cameraPart.CFrame * CFrame.Angles(0, math.rad(180), 0)
    -- камерой управляет скрипт
    camera.CameraType = Enum.CameraType.Scriptable
    -- камера плывёт и разворачивается
    TS:Create(camera, tweenInfo, {CFrame = goal}):Play()
    -- смотрим, как закрывается дверь
    task.wait(10)
    -- экран темнеет
    TS:Create(frame, tweenInfo, {BackgroundTransparency = 0}):Play()
    -- чуть позже
    task.wait(0.4)
    -- появляется надпись
    TS:Create(text, tweenInfo, {TextTransparency = 0}):Play()
-- конец обработчика
end)

Проверка

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