Roblox · Doors · урок 7
Теперь монстр убивает: кто в его зоне и на виду — погибает. Спасает шкаф. Перед появлением в комнате мигает свет — есть 2 секунды, чтобы спрятаться.
Синим — что добавляем на этом уроке.
ServerStorage → Enemies → RedMonster └─ Monster (Part) ├─ Appear (Sound) — гул «Drone» └─ Disappear (Sound) — «Whoosh», PlayOnRemove вкл. Workspace └─ Sounds (Folder) └─ Lights (Sound) — мигание «Light flicker» ServerStorage → RoomSamples → комната └─ Build (Model) └─ Lights (Model) — все лампы комнаты └─ Lamp (Part) └─ PointLight (PointLight)
Где: ServerScriptService → MainScript · ModuleScript
Зачем: Ищет игроков в зоне и убивает тех, кого видит.
-- события каждого кадра
local RunService = game:GetService("RunService")
-- здесь лежит образец монстра
local SS = game:GetService("ServerStorage")
-- все игроки
local Players = game:GetService("Players")
-- таблица модуля
local monster = {}
-- скорость: studs в секунду
monster.speed = 20
-- видит ли монстр игрока (нет ли стены между ними)
local function CanSee(model, root)
-- откуда смотрит
local origin = model.Monster.Position
-- настройки луча
local params = RaycastParams.new()
-- режим «не замечать список»
params.FilterType = Enum.RaycastFilterType.Exclude
-- сам монстр не мешает
local ignore = {model}
-- не больше 20 попыток
for _ = 1, 20 do
-- обновляем список
params.FilterDescendantsInstances = ignore
-- луч к игроку
local result = workspace:Raycast(origin, root.Position - origin, params)
-- ни во что не попал
if not result then return false end
-- попал в игрока — видит
if result.Instance:IsDescendantOf(root.Parent) then return true end
-- стена, шкаф, барьер — не видит
if result.Instance.CanCollide then return false end
-- невидимая деталь (Trigger, Enter…) — смотрим сквозь неё
table.insert(ignore, result.Instance)
-- следующая попытка
end
-- слишком много преград
return false
-- конец функции
end
-- ищет игроков в зоне монстра
function monster.FindPlayers(model)
-- персонажи всех игроков
local characters = {}
-- каждый игрок
for _, player in ipairs(Players:GetPlayers()) do
-- есть персонаж — в список
if player.Character then table.insert(characters, player.Character) end
-- конец перебора
end
-- настройки поиска
local params = OverlapParams.new()
-- искать только среди списка
params.FilterType = Enum.RaycastFilterType.Include
-- список — персонажи
params.FilterDescendantsInstances = characters
-- детали персонажей внутри зоны Emitter
for _, part in ipairs(workspace:GetPartsInPart(model.Emitter, params)) do
-- центр персонажа
if part.Name == "HumanoidRootPart" then
-- его здоровье
local humanoid = part.Parent:FindFirstChildOfClass("Humanoid")
-- жив и на виду
if humanoid and humanoid.Health > 0 and CanSee(model, part) then
-- монстр настиг
humanoid.Health = 0
-- конец проверки
end
-- конец проверки
end
-- конец перебора
end
-- конец функции
end
-- плавно ведёт монстра к детали 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))
-- каждый кадр ищем игроков
monster.FindPlayers(model)
-- конец цикла
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
-- гул появления
model.Monster.Appear:Play()
-- проходит комнаты
monster.Navigate(model, number, toNumber, rooms)
-- исчезает (звук Disappear сыграет сам — PlayOnRemove)
model:Destroy()
-- конец функции
end
-- отдаём таблицу модуля
return monsterГде: ServerScriptService → MainScript · ModuleScript
Зачем: + мигание ламп комнаты.
-- плавное мигание света
local TS = game:GetService("TweenService")
-- здесь лежат образцы комнат
local SS = game:GetService("ServerStorage")
-- модуль дверей
local door = require(script.Door)
-- модуль мебели
local furniture = require(script.Furniture)
-- модуль предметов (ключ)
local item = require(script.Item)
-- таблица модуля
local room = {}
-- редкость и правила комнат
room.info = require(script.RoomInfo)
-- генератор случайных чисел
room.random = Random.new()
-- куда был последний поворот
room.lastTurnDirection = nil
-- одна лампа мигает
function room.TweenLight(light)
-- обычная яркость (запоминаем один раз)
local base = light:GetAttribute("Base") or light.Brightness
-- храним в самой лампе
light:SetAttribute("Base", base)
-- резкое дёрганое мигание
local tweenInfo = TweenInfo.new(0.3, Enum.EasingStyle.Elastic, Enum.EasingDirection.InOut)
-- погасить
local off = TS:Create(light, tweenInfo, {Brightness = 0})
-- вспышка
local on = TS:Create(light, tweenInfo, {Brightness = base * 4})
-- гаснет
off:Play()
-- ждём
off.Completed:Wait()
-- вспыхивает
on:Play()
-- ждём
on.Completed:Wait()
-- снова гаснет
off:Play()
-- ждём
off.Completed:Wait()
-- за секунду возвращаем обычный свет
TS:Create(light, TweenInfo.new(1), {Brightness = base}):Play()
-- конец функции
end
-- мигают все лампы комнаты
function room.Lights(roomModel)
-- постройка комнаты
local build = roomModel:FindFirstChild("Build")
-- модель с лампами
local lights = build and build:FindFirstChild("Lights")
-- ламп нет — нечему мигать
if not lights then return end
-- всё внутри ламп
for _, obj in ipairs(lights:GetDescendants()) do
-- источник света: PointLight, SpotLight, SurfaceLight
if obj:IsA("Light") then
-- мигает одновременно с остальными
task.spawn(room.TweenLight, obj)
-- конец проверки
end
-- конец перебора
end
-- конец функции
end
-- можно ли поставить эту комнату следующей
local function Allowed(name, info, prevRoom)
-- настройки предыдущей (у стартовой их нет)
local prevInfo = room.info[prevRoom.Name] or {}
-- образца нет — пропускаем
if not SS.RoomSamples:FindFirstChild(name) then return false end
-- две одинаковые подряд — скучно
if name == prevRoom.Name then return false end
-- два поворота в одну сторону — коридор замкнётся
if info.Direction and info.Direction == room.lastTurnDirection then return false end
-- две лестницы подряд — нельзя
if info.Stairs and prevInfo.Stairs then return false end
-- подходит
return true
-- конец функции
end
-- случайная комната с учётом редкости
function room.GetRandom(prevRoom)
-- сумма весов подходящих комнат
local totalRarity = 0
-- все комнаты из RoomInfo
for name, info in pairs(room.info) do
-- подходящие складываем
if Allowed(name, info, prevRoom) then totalRarity += info.Rarity end
-- конец перебора
end
-- понятная ошибка вместо зависания
if totalRarity == 0 then error("Нет подходящих комнат в ServerStorage.RoomSamples") end
-- случайное число от 1 до суммы
local randomRarity = room.random:NextInteger(1, totalRarity)
-- накопленный вес
local currentRarity = 0
-- снова по комнатам (порядок тот же)
for name, info in pairs(room.info) do
-- только подходящие
if Allowed(name, info, prevRoom) then
-- прибавляем вес
currentRarity += info.Rarity
-- число попало на эту комнату
if randomRarity <= currentRarity then
-- запоминаем поворот
if info.Direction then room.lastTurnDirection = info.Direction end
-- отдаём образец
return SS.RoomSamples[name]
-- конец проверки
end
-- конец проверки
end
-- конец перебора
end
-- конец функции
end
-- ставит новую комнату за предыдущей
function room.Generate(prevRoom, number)
-- копия случайной комнаты
local newRoom = room.GetRandom(prevRoom):Clone()
-- двигаем комнату за вход
newRoom.PrimaryPart = newRoom.Enter
-- вход новой — точно на выход предыдущей
newRoom:PivotTo(prevRoom.Exit.CFrame)
-- дверь пока без замка
local keyNeeded = false
-- расставляем мебель; получаем места для ключа
local locations = furniture.Generate(newRoom)
-- есть ящики и выпал шанс 1 из 4
if locations and room.random:NextInteger(1, 4) == 4 then
-- дверь этой комнаты будет заперта
keyNeeded = true
-- кладём ключ в случайный ящик
item.New(locations[room.random:NextInteger(1, #locations)], "Key")
-- конец проверки
end
-- дверь на выходе — с замком или без
door.New(newRoom, number, keyNeeded)
-- показываем комнату
newRoom.Parent = workspace.GeneratedRooms
-- она станет «предыдущей» для следующей
return newRoom
-- конец функции
end
-- отдаём таблицу модуля
return roomГде: ServerScriptService · Script
Зачем: Свет мигает, через 2 секунды — монстр.
-- модуль комнат
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
-- комната, куда вошёл игрок
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)Запусти Play (или Run) и убедись: