Roblox · Дополнительное

Генератор лабиринта

Каждый раз новый лабиринт: скрипт сам ставит стены так, что от входа до выхода всегда есть путь. Дошёл до выхода — +1 в таблице и новый лабиринт.

Структура

Синим — что нужно создать.

Workspace
└─ Maze (Model)
   ├─ Floor (Part) — пол: размер = Cells × CellSize
   ├─ Entrance (Part) — площадка у входа
   ├─ Finish (Part) — выход, CanCollide выкл.
   ├─ Walls (Folder) — стены создаёт скрипт
   ├─ Configuration (Configuration) — Cells, CellSize, WallHeight
   └─ MazeServer (Script)

Шаги

  1. Пол «Floor»: квадрат со стороной Cells × CellSize (10 × 10 = 100). Лабиринт строится от его центра.
  2. Configuration: Cells — клеток в ряду, CellSize — размер клетки, WallHeight — высота стен.
  3. Алгоритм «поиск с возвратом»: копатель идёт от угла в случайного непосещённого соседа и ломает стену между клетками; в тупике возвращается назад. Когда вернулся в начало — все клетки соединены ровно одним путём.
  4. В Алгоритмике блоки ставились случайно (шанс 25/75) — проход мог не существовать. Здесь путь есть всегда.
  5. Вход — разрыв во внешней стене у первой клетки, выход — у последней (по диагонали).
  6. Хочешь мишени, как в шутере Алгоритмики, — поставь в лабиринт NPC (Анимации → «Анимации NPC»).

Скрипты

MazeServer

Где: Workspace → Maze · Script
Зачем: Строит лабиринт и пересобирает после выхода.

-- игроки
local Players = game:GetService("Players")
-- модель Maze
local maze = script.Parent
-- Cells, CellSize, WallHeight
local config = maze:WaitForChild("Configuration")
-- пол — по нему считаются клетки
local floor = maze:WaitForChild("Floor")
-- выход
local finish = maze:WaitForChild("Finish")
-- куда вернуть после выхода
local entrance = maze:WaitForChild("Entrance")
-- папка стен
local walls = maze:WaitForChild("Walls")
-- лабиринт пересобирается
local busy = false

-- стена по координатам относительно центра пола
local function Wall(x, z, sizeX, sizeZ)
    -- высота
    local h = config.WallHeight.Value
    -- деталь
    local wall = Instance.new("Part")
    -- не падает
    wall.Anchored = true
    -- размер
    wall.Size = Vector3.new(sizeX, h, sizeZ)
    -- стоит на полу
    wall.CFrame = floor.CFrame * CFrame.new(x, floor.Size.Y / 2 + h / 2, z)
    -- кирпич
    wall.Material = Enum.Material.Brick
    -- цвет
    wall.Color = Color3.fromRGB(150, 90, 70)
    -- в папку
    wall.Parent = walls
-- конец функции
end

-- новый лабиринт
local function Generate()
    -- старые стены убираем
    walls:ClearAllChildren()
    -- клеток в ряду и размер клетки
    local n, s = config.Cells.Value, config.CellSize.Value
    -- посещена ли клетка, есть ли стена справа и снизу
    local visited, right, down = {}, {}, {}
    -- каждый столбец
    for x = 1, n do
        -- пустые
        visited[x], right[x], down[x] = {}, {}, {}
        -- сначала все стены стоят
        for z = 1, n do right[x][z], down[x][z] = true, true end
    -- конец перебора
    end
    -- путь копателя: начинаем с угла
    local stack = {{1, 1}}
    -- угол посещён
    visited[1][1] = true
    -- пока есть куда возвращаться
    while #stack > 0 do
        -- где копатель
        local x, z = stack[#stack][1], stack[#stack][2]
        -- непосещённые соседи
        local options = {}
        -- четыре стороны
        for _, d in ipairs({{1, 0}, {-1, 0}, {0, 1}, {0, -1}}) do
            -- сосед
            local nx, nz = x + d[1], z + d[2]
            -- в лабиринте и не посещён
            if nx >= 1 and nx <= n and nz >= 1 and nz <= n and not visited[nx][nz] then table.insert(options, {nx, nz}) end
        -- конец перебора
        end
        -- тупик
        if #options == 0 then
            -- шаг назад
            table.remove(stack)
        -- можно копать
        else
            -- случайный сосед
            local nx, nz = table.unpack(options[math.random(1, #options)])
            -- ломаем стену между клетками
            if nx > x then right[x][z] = false elseif nx < x then right[nx][z] = false elseif nz > z then down[x][z] = false else down[x][nz] = false end
            -- сосед посещён
            visited[nx][nz] = true
            -- копатель идёт туда
            table.insert(stack, {nx, nz})
        -- конец проверки
        end
    -- конец цикла: все клетки соединены
    end
    -- половина лабиринта
    local half = n * s / 2
    -- каждая клетка
    for x = 1, n do
        -- …
        for z = 1, n do
            -- центр клетки
            local cx, cz = (x - 0.5) * s - half, (z - 0.5) * s - half
            -- стена справа
            if right[x][z] and x < n then Wall(cx + s / 2, cz, 1, s + 1) end
            -- стена снизу
            if down[x][z] and z < n then Wall(cx, cz + s / 2, s + 1, 1) end
            -- внешняя стена сверху; в первой клетке — вход
            if z == 1 and x ~= 1 then Wall(cx, -half, s + 1, 1) end
            -- внешняя стена снизу; в последней — выход
            if z == n and x ~= n then Wall(cx, half, s + 1, 1) end
        -- конец строки
        end
        -- левая внешняя стена
        Wall(-half, (x - 0.5) * s - half, 1, s + 1)
        -- правая внешняя стена
        Wall(half, (x - 0.5) * s - half, 1, s + 1)
    -- конец перебора
    end
    -- выход — за последней клеткой
    finish.CFrame = floor.CFrame * CFrame.new(half - s / 2, floor.Size.Y / 2 + 0.6, half + s / 2)
-- конец функции
end

-- кто-то дошёл
finish.Touched:Connect(function(hit)
    -- игрок
    local player = Players:GetPlayerFromCharacter(hit.Parent)
    -- не игрок или уже пересобираем
    if not player or busy then return end
    -- один раз
    busy = true
    -- таблица справа
    local stats = player:FindFirstChild("leaderstats") or Instance.new("Folder")
    -- имя
    stats.Name = "leaderstats"
    -- в игрока
    stats.Parent = player
    -- пройдено лабиринтов
    local mazes = stats:FindFirstChild("Mazes") or Instance.new("IntValue")
    -- имя
    mazes.Name = "Mazes"
    -- в таблицу
    mazes.Parent = stats
    -- +1
    mazes.Value += 1
    -- всех, кто внутри, — ко входу
    for _, other in ipairs(Players:GetPlayers()) do
        -- ко входу
        if other.Character then other.Character:PivotTo(entrance.CFrame + Vector3.new(0, 3, 0)) end
    -- конец перебора
    end
    -- новый лабиринт
    Generate()
    -- пауза
    task.wait(1)
    -- готов
    busy = false
-- конец функции
end)
-- первый лабиринт при запуске
Generate()

Проверка

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

Собрать лабиринт одной командой