Roblox · Doors · урок 4
В случайной комнате (шанс 1 из 4) в случайном ящике лежит ключ, а дверь этой комнаты заперта — на ней висит замок. Нашёл ключ — подошёл к двери — замок щёлкает, дверь открывается.
Синим — что добавляем на этом уроке.
ServerStorage ├─ Door (Model) │ ├─ Door (Part) │ │ └─ Unlock (Sound) — щелчок замка │ └─ Lock (Part) — замок на двери; приварен к Door └─ Items (Folder) └─ Key (Tool) └─ Handle (Part) — модель ключа ServerScriptService └─ MainScript → Room (ModuleScript) └─ Item (ModuleScript)
Где: ServerScriptService → MainScript → Room · ModuleScript
Зачем: Кладёт ключ в ящик и отдаёт его игроку.
-- здесь лежат предметы
local SS = game:GetService("ServerStorage")
-- таблица модуля
local item = {}
-- игрок взял предмет
function item.Interact(player, prompt, handle, itemName)
-- его персонаж
local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
-- персонажа нет
if not humanoid then return end
-- второй раз не взять
prompt.Enabled = false
-- предмет пропадает из ящика
handle:Destroy()
-- это ключ
if itemName == "Key" then
-- копия Tool «Key»
local tool = SS.Items.Key:Clone()
-- в рюкзак
tool.Parent = player.Backpack
-- и сразу в руки
humanoid:EquipTool(tool)
-- конец проверки
end
-- конец функции
end
-- кладёт предмет в ящик
function item.New(location, itemName)
-- образец предмета
local template = SS.Items:FindFirstChild(itemName)
-- нет такого
if not template then return end
-- дно ящика, где точка Location
local bottom = location.Parent
-- только модель, без Tool
local handle = template.Handle:Clone()
-- поедет вместе с ящиком
handle.Anchored = false
-- не мешает ящику двигаться
handle.CanCollide = false
-- ничего не весит
handle.Massless = true
-- в точку Location
handle.CFrame = location.WorldCFrame
-- в ящик
handle.Parent = bottom
-- приварка к дну
local weld = Instance.new("WeldConstraint")
-- предмет
weld.Part0 = handle
-- к дну ящика
weld.Part1 = bottom
-- в предмет
weld.Parent = handle
-- надпись «Взять»
local prompt = Instance.new("ProximityPrompt")
-- текст
prompt.ActionText = "Взять"
-- что берём
prompt.ObjectText = itemName
-- с 5 studs
prompt.MaxActivationDistance = 5
-- мебель не загораживает
prompt.RequiresLineOfSight = false
-- видно, только когда ящик открыт
prompt.Enabled = bottom:GetAttribute("Open") == true
-- на предмете
prompt.Parent = handle
-- ящик открыли или закрыли
bottom:GetAttributeChangedSignal("Open"):Connect(function()
-- предмет ещё здесь — показываем/прячем
if handle.Parent then prompt.Enabled = bottom:GetAttribute("Open") == true end
-- конец обработчика
end)
-- нажали «Взять»
prompt.Triggered:Connect(function(player)
-- отдаём игроку
item.Interact(player, prompt, handle, itemName)
-- конец обработчика
end)
-- конец функции
end
-- отдаём таблицу модуля
return itemГде: ServerScriptService → MainScript → Room · ModuleScript
Зачем: Замок: дверь открывается только с ключом.
-- плавный поворот двери
local TS = game:GetService("TweenService")
-- здесь лежит образец двери
local SS = game:GetService("ServerStorage")
-- таблица модуля
local door = {}
-- открывает дверь
function door.Open(doorModel)
-- отмечаем: открыта
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()
-- конец функции
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)
-- готово
return
-- конец проверки
end
-- есть ли у игрока ключ
local key = FindKey(character)
-- есть
if key then
-- дверь больше не заперта
locked = false
-- ключ тратится
key:Destroy()
-- замок исчезает
doorModel.Lock:Destroy()
-- щелчок замка
doorModel.Door.Unlock:Play()
-- открываем
door.Open(doorModel)
-- конец проверки
end
-- конец обработчика
end)
-- дверь — часть комнаты
doorModel.Parent = roomModel
-- отдаём дверь
return doorModel
-- конец функции
end
-- отдаём таблицу модуля
return doorГде: ServerScriptService → MainScript → Room · ModuleScript
Зачем: Отдаёт места внутри ящиков.
-- плавное движение ящиков
local TS = game:GetService("TweenService")
-- здесь лежат образцы мебели
local SS = game:GetService("ServerStorage")
-- на сколько studs выдвигается ящик
local SLIDE = 1.5
-- таблица модуля
local furniture = {}
-- открыть или закрыть ящик / дверцу
function furniture.Open(bottom)
-- двигается — второе нажатие не примем
bottom:SetAttribute("Moving", true)
-- сейчас открыт?
local opened = bottom:GetAttribute("Open")
-- открыт — задвигаем (1), закрыт — выдвигаем (−1)
local direction = opened and 1 or -1
-- Bottom → папка Bottoms → модель мебели
local model = bottom.Parent.Parent
-- анимация
local tween
-- у мебели есть петля — это дверца (шкаф-гардероб)
if model:FindFirstChild("Spinner") then
-- поворот петли на 90°
local cframe = model.Spinner.CFrame * CFrame.Angles(0, math.rad(90 * direction), 0)
-- поворачиваем петлю
tween = TS:Create(model.Spinner, TweenInfo.new(0.5), {CFrame = cframe})
-- ящик
else
-- сдвиг вперёд или назад
local cframe = bottom.CFrame * CFrame.new(0, 0, SLIDE * direction)
-- двигаем дно — стенки приварены
tween = TS:Create(bottom, TweenInfo.new(0.5), {CFrame = cframe})
-- конец проверки
end
-- запускаем
tween:Play()
-- ждём конца
tween.Completed:Wait()
-- запоминаем новое положение
bottom:SetAttribute("Open", not opened)
-- можно нажимать снова
bottom:SetAttribute("Moving", false)
-- конец функции
end
-- ставит мебель на место локатора
function furniture.New(sample)
-- образец с тем же именем
local template = SS.FurnitureSamples:FindFirstChild(sample.Name)
-- образца нет
if not template then
-- подсказка в Output
warn("Нет мебели " .. sample.Name .. " в ServerStorage.FurnitureSamples")
-- ничего не ставим
return nil
-- конец проверки
end
-- копия мебели
local model = template:Clone()
-- на место и с поворотом локатора
model:PivotTo(sample.CFrame)
-- места внутри ящиков, куда можно положить ключ
local locations = {}
-- папка с донышками ящиков
local bottoms = model:FindFirstChild("Bottoms")
-- ящики есть
if bottoms then
-- каждый ящик
for _, bottom in ipairs(bottoms:GetChildren()) do
-- запоминаем место внутри
table.insert(locations, bottom.Location)
-- закрыт
bottom:SetAttribute("Open", false)
-- не двигается
bottom:SetAttribute("Moving", false)
-- надпись «Открыть» (E / тап / X)
local prompt = Instance.new("ProximityPrompt")
-- текст
prompt.ActionText = "Открыть"
-- с 5 studs
prompt.MaxActivationDistance = 5
-- невидимая деталь Primary не мешает
prompt.RequiresLineOfSight = false
-- на ручке ящика
prompt.Parent = bottom.Handle
-- нажали
prompt.Triggered:Connect(function()
-- ещё едет — ждём
if bottom:GetAttribute("Moving") then return end
-- открываем или закрываем
furniture.Open(bottom)
-- меняем надпись
prompt.ActionText = bottom:GetAttribute("Open") and "Закрыть" or "Открыть"
-- конец обработчика
end)
-- конец перебора
end
-- конец проверки
end
-- мебель в комнату
model.Parent = sample.Parent
-- локатор больше не нужен
sample:Destroy()
-- отдаём места для ключа
return locations
-- конец функции
end
-- расставляет всю мебель комнаты
function furniture.Generate(roomModel)
-- все места для ключа в этой комнате
local roomLocations = {}
-- папка с локаторами
local folder = roomModel:FindFirstChild("Furniture")
-- мебели в комнате нет
if not folder then return nil end
-- каждый локатор
for _, sample in ipairs(folder:GetChildren()) do
-- ставим мебель
local locations = furniture.New(sample)
-- поставили
if locations then
-- её места
for _, location in ipairs(locations) do
-- добавляем к местам комнаты
table.insert(roomLocations, location)
-- конец перебора
end
-- конец проверки
end
-- конец перебора
end
-- есть куда положить ключ
if #roomLocations > 0 then return roomLocations end
-- ящиков нет
return nil
-- конец функции
end
-- отдаём таблицу модуля
return furnitureГде: ServerScriptService → MainScript · ModuleScript
Зачем: Иногда прячет ключ и запирает дверь.
-- здесь лежат образцы комнат
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
-- можно ли поставить эту комнату следующей
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
Зачем: Стартовая дверь — без замка.
-- модуль комнат
local room = require(script.Room)
-- модуль дверей
local door = require(script.Room.Door)
-- сколько комнат вместе со стартовой
local ROOMS = 10
-- первая «предыдущая» — стартовая
local prevRoom = workspace.StartRoom
-- дверь 01 в стартовой комнате
door.New(prevRoom, 1, false)
-- комнаты 2…10
for i = 2, ROOMS do
-- новая комната с дверью номер i
prevRoom = room.Generate(prevRoom, i)
-- конец цикла
endЗапусти Play (или Run) и убедись: