Roblox · Doors · урок 6

Монстр

После каждой третьей двери из прошлой комнаты вылетает монстр и проносится через комнаты вперёд. Пока он только пугает — убивать научим на следующем уроке.

Структура

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

ServerStorage
├─ EnterEvent (BindableEvent) — Door → MainScript
├─ Enemies (Folder)
│  └─ RedMonster (Model) — PrimaryPart = Monster
│     ├─ Monster (Part) — размером с Enter: полупрозрачный, Anchored, CanCollide выкл.
│     │  └─ Decal (Decal) — картинка монстра
│     ├─ Emitter (Part) — зона на всю большую комнату: прозрачная, CanCollide выкл.
│     │  └─ ParticleEmitter (ParticleEmitter) — дым, искры
│     └─ WeldConstraint (WeldConstraint) — Monster + Emitter
└─ RoomSamples → извитые комнаты (Model)
   └─ Waypoints (Folder) — точки 1, 2, 3… в углах — монстр не режет углы
ServerScriptService
└─ MainScript (Script)
   └─ Monster (ModuleScript)

Шаги

  1. Деталь «Monster» размером с Enter, картинка монстра (Decal) из Toolbox, полупрозрачная, Anchored, CanCollide выкл.
  2. «Emitter» на месте Monster, размером с самую большую комнату: прозрачная, CanCollide выкл., Anchored выкл.; эффекты (ParticleEmitter). Сварена с Monster. Это зона, в которой монстр «чует» игроков.
  3. Модель «RedMonster», PrimaryPart = Monster → ServerStorage → Folder «Enemies».
  4. В зигзагах и лестницах монстр летел бы сквозь стены: поставь точки — копии Enter с именами 1, 2, 3… в углах по порядку от входа, в папку «Waypoints» этой комнаты.
  5. ServerStorage → BindableEvent «EnterEvent»: Door сообщает MainScript номер открытой двери (сервер → сервер, поэтому Bindable, а не Remote).
  6. В MainScript — ModuleScript «Monster». Door и MainScript обновились.

Скрипты

Monster

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

-- события каждого кадра
local RunService = game:GetService("RunService")
-- здесь лежит образец монстра
local SS = game:GetService("ServerStorage")

-- таблица модуля
local monster = {}
-- скорость: studs в секунду
monster.speed = 20

-- плавно ведёт монстра к детали target
function monster.LerpTo(model, target)
    -- откуда
    local start = model:GetPivot()
    -- сколько лететь
    local distance = (start.Position - target.Position).Magnitude
    -- сколько секунд (не ноль)
    local duration = math.max(distance / monster.speed, 0.05)
    -- доля пути: 0 — начало, 1 — конец
    local alpha = 0
    -- пока не долетел
    while alpha < 1 do
        -- прибавляем долю за прошедший кадр
        alpha = math.min(alpha + RunService.Heartbeat:Wait() / duration, 1)
        -- точка между началом и целью
        model:PivotTo(start:Lerp(target.CFrame, alpha))
    -- конец цикла
    end
-- конец функции
end

-- проводит монстра через комнаты
function monster.Navigate(model, fromNumber, toNumber, rooms)
    -- каждая комната по порядку
    for i = fromNumber, toNumber do
        -- комната
        local roomModel = rooms[i]
        -- ко входу
        if roomModel:FindFirstChild("Enter") then monster.LerpTo(model, roomModel.Enter) end
        -- точки поворота в извитых комнатах
        local waypoints = roomModel:FindFirstChild("Waypoints")
        -- есть
        if waypoints then
            -- по номерам 1, 2, 3…
            for w = 1, #waypoints:GetChildren() do
                -- точка с этим номером
                local point = waypoints:FindFirstChild(tostring(w))
                -- летим к ней
                if point then monster.LerpTo(model, point) end
            -- конец перебора
            end
        -- конец проверки
        end
        -- к выходу
        monster.LerpTo(model, roomModel.Exit)
    -- конец перебора
    end
-- конец функции
end

-- монстр проходит комнату number и следующую
function monster.New(number, rooms)
    -- дальше последней комнаты не идёт
    local toNumber = math.min(number + 1, #rooms)
    -- откуда начинает
    local startRoom = rooms[number]
    -- копия монстра
    local model = SS.Enemies.RedMonster:Clone()
    -- ставим на вход комнаты
    model:PivotTo((startRoom:FindFirstChild("Enter") or startRoom.Exit).CFrame)
    -- появляется
    model.Parent = workspace
    -- проходит комнаты
    monster.Navigate(model, number, toNumber, rooms)
    -- исчезает
    model:Destroy()
-- конец функции
end

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

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

-- ключ в руках или в рюкзаке
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
Зачем: Таблица комнат по номерам; монстр после 3-й, 6-й, 9-й двери.

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

-- первая «предыдущая» — стартовая
local prevRoom = workspace.StartRoom
-- все комнаты по номерам: 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

-- открыта дверь number
enterEvent.Event:Connect(function(number)
    -- номер делится на 3 и комната не последняя
    if number % MONSTER_EVERY == 0 and number < #generatedRooms then
        -- монстр пролетает комнаты
        monster.New(number, generatedRooms)
    -- конец проверки
    end
-- конец обработчика
end)

Проверка

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