View: Multi-Page Single Page

Simple Maze

Mazes for Unity that are generated, solved exactly and rated, on square, hex, triangle and circular grids.

Simple Maze builds a maze from your settings, then proves it can be solved and works out its par, the fewest moves it takes. It can add keys and doors, switches and gates, gems, traps, one-way sides and portals, stack up to 8 floors joined by stairs, put up to 4 players on the board, and make the players roll like on ice instead of stepping. Every maze is rated 1 to 5 stars and can be saved as a ScriptableObject, exported as a PNG or SVG picture, or played in the included 3D demo.

Version 1.0, by Living Failure

What it is

Simple Maze is a maze generator with a solver built in. You choose the grid, the size and what goes in the maze, and it carves one for you. Then it solves the maze exactly, so you know for certain that it can be finished, how many moves the best solution takes and how many different best solutions there are.

Everything is deterministic. The same settings and the same seed always give the same maze, and every maze keeps the settings and seed it was made from. When the generator can't give you exactly what you asked for, it says so in the maze's notes instead of quietly handing you something else.

The four grids

Square

Classic square cells with four sides, from 2 by 2 up to 64 by 64. The only grid with weave crossings, where a passage tunnels under a corridor.

Hex

Pointy-top hexagons with six ways out of each cell, up to 64 by 64.

Triangle

Alternating up and down triangles with three sides each, up to 64 by 64.

Circular

A polar maze: a centre cell inside 2 to 40 rings that split into more cells as they grow, so the cells stay roughly square from the centre to the rim.

Any grid can be stacked into 2 to 8 floors joined by 1 to 4 stairs between each pair of floors, and any grid can be cut to a shape: a circle, ring, heart, star, diamond, cross, a word in the built-in font, or your own picture.

Features

Proven par

The solver searches every position the players can reach, so the par is the true minimum and not an estimate.

Soft-lock check

Every maze reports whether a player can get stuck somewhere they can no longer finish from, and NoSoftLocks only accepts mazes where that can't happen.

Difficulty stars

A 1 to 5 star rating worked out from the par, the real decisions on the way and the gimmicks, calibrated so each star covers about a fifth of everyday mazes. Ask for a star count and the generator keeps trying until it gets one.

Gimmicks

Up to 6 keys and doors, 4 switches with up to 3 gates each, 8 gems, 24 one-way sides, 4 portal pairs and traps, all placed so the maze stays solvable and proven together.

Players and movement

Up to 4 players, either racing to one goal from starts the same distance away or each heading to a goal of its own. Players step one cell at a time, or roll in a straight line until something stops them (Square and Hex).

Six carving algorithms

Recursive Backtracker, Prim, Kruskal, Wilson, Hunt-and-Kill and Growing Tree, each with its own look, plus braid, extra loops, open rooms and holes.

Shapes from pictures

Drop an image into the generator window and its dark, light or opaque areas become the maze.

Printable export

Save any maze as a PNG or SVG: a black on white puzzle sheet, an answer key with the solution drawn in, or the full colour view with every gimmick.

ScriptableObjects

Save single mazes or ordered packs, with inspectors that preview, re-check and regenerate them.

Play rules included

MazeRules tells your game what a move does, using the same rules the solver counted with, so the game and the par always agree. It also gives hints from any position.

Editor tooling

One Maze Generator window with 19 presets, an Auto button that fixes settings that fight each other, and generation on a background thread with a progress bar and Cancel.

A 3D demo

Five game modes with endless mazes and 50 ready-made levels, built entirely in code. It works in URP, HDRP and the Built-in pipeline.

How it works

1

Configure

Pick a grid, its size and what goes in the maze, in the Maze Generator window or with a MazeSettings object in code.

2

Build

The board is cut to its shape, carved, and given its rooms, loops and crossings. Then the start and goal are placed, the extra players are added and the gimmicks are set out along the way.

3

Solve and rate

The solver explores every reachable position, counting keys held, gems taken and switches flipped. It records the par, how many best solutions exist, one of them, whether anyone can get stuck, and the star rating.

4

Use it

Save it as an asset, export a picture of it, play it in the demo, or read the MazeData in your own scripts.

Why "verified"

Lots of generators make mazes that are probably fine. Simple Maze proves the things you design around:

  • The par is real. It is the shortest solution that exists, counting every key, door, gate and portal, so a 40-move maze really takes 40 moves.
  • Gimmicks are part of the proof. Doors, gates and gems are solved together with the maze, and by default every door and every switch's first gate stands in the way, so its key or switch is really needed.
  • Getting stuck is detected. One-way sides, traps and rolling can leave a player somewhere they can't finish from. Every maze reports it, and you can ask for mazes where it never happens.
  • Misses are reported. When a target star count, a coverage or a gimmick can't be met, the maze's notes say what happened and what you got instead.

One honest limit: the solver has a state budget (500,000 positions by default). A very large maze with many players and gimmicks can run past it, and then the par is reported as not proven rather than guessed. Raise StateBudget or make the maze smaller.

Getting started

Open Window > Living Failure > Simple Maze > Maze Generator, pick a preset, press Generate and look at the analysis on the right. Then open the demo scene and play. The Quick Start walks through the whole loop in about five minutes.

Quick Start

From import to a maze you can play, in about five minutes.

1. Install

Import the package. Everything lives under Assets/SimpleMaze/, and nothing is added to your scenes until you ask for it.

The core (the generator, the solver, the play rules and the maze data) has no package dependencies. The demo uses the Input System and Unity UI packages. It reads the keyboard through the Input System, so set Project Settings > Player > Active Input Handling to Input System Package or Both before you play it.

Render pipelines: URP is recommended, HDRP is supported and the Built-in pipeline still works. The demo builds its materials from the active pipeline's default shader, and on HDRP it adds a small volume so it looks the same as on the others (turn off HdrpLook on the MazeDemo component to use your own lighting instead). Simple Maze was built and tested with Unity 6000.6.

2. Generate a maze

Open Window > Living Failure > Simple Maze > Maze Generator. You can also get there from Assets > Create > Simple Maze > Maze Generator.

1

Take a preset

Press Presets and pick Classic/Circular 10 rings. A preset replaces every setting with a ready-made recipe.

2

Generate

Press Generate. Mazes are built on a background thread, so the window stays responsive, and a long batch shows a progress bar with a Cancel button. Raise Batch count to make up to 20 at a time.

3

Try a few more

Gimmicks/Keys and doors locks the way with doors whose keys hide before them. Roll/Ice board (square) makes the player slide until a wall stops it. Big/Tower of 3 floors stacks three floors joined by stairs, and Shapes/Word: MAZE carves the maze into the letters of a word.

If you change settings by hand and they fight each other (Roll on a triangle grid, say), press Auto. It fixes them and tells you what it changed.

3. Read the analysis

The pane on the right shows the selected maze. From the top:

  • The verdict: the par in steps or rolls, whether the best solution is unique or how many there are, and the difficulty out of 5. A warning shows when a player can get stuck.
  • Board: a picture of the maze with a legend, tabs for each floor, and a zoom. Hover a cell to see what is in it.
  • Best solution: a slider that steps through the solution, with the players moving, keys and gems disappearing as they're taken and gates flipping. Below it is the move list and a Copy solution button.
  • Facts and Generation report: the numbers (dead ends, loops, decisions, coverage and more) and every note the generator left.
  • Export: save the maze as a PNG or SVG, as a printable sheet, an answer key or the colour view.

Dragging the solution slider is the quickest way to judge a maze. You can see at a glance whether the route wanders the whole board or runs straight to the goal.

4. Save it

Press Save and choose:

  • Current saves the selected maze as a MazePuzzleAsset.
  • Pack (all) saves every maze in the set as one ordered MazePuzzlePack.
  • Pack (unique only) saves just the mazes with exactly one best solution.

Both asset types have inspectors with the same analysis pane, plus Verify, which solves the maze again and refreshes its stored verdict, and Re-roll, which makes a new maze from the same settings with a fresh seed. A pack also has Export all, which writes every maze as a printable picture and its answer key into a folder you pick.

5. Play the demo

Open Assets/SimpleMaze/Scenes/SimpleMazeDemo.unity and press Play. The demo opens on a welcome screen. Press Let's play, pick a mode, then choose Endless for a new maze to your order or Level Pack for the ready-made levels.

The controls are also listed at the bottom of the screen while you play:

  • Arrows or WASD move or roll, and Q, E, Z and C take the diagonal sides on hex, triangle and circular mazes.
  • Space or Enter takes the stairs (or Page Up and Page Down).
  • U (or Backspace, or Ctrl+Z) undoes a move, R restarts and H shows a hint.
  • F lets the solver play the rest of the maze from where you are, and F or R stops it.
  • Tab or 1 to 4 switches player when several players each have a goal of their own.
  • [ and ] or a right-drag turn the view, Home and End tilt it, the mouse wheel or + and - zoom, and V resets it.
  • M mutes the sound and Esc goes back to the menu.

6. Use it in your game

Make a maze in code, check what the solver found, and read the rest straight off the MazeData:

using SimpleMaze;

var settings = new MazeSettings
{
    Grid = TopologySpec.Circle(10),
    Endpoints = Endpoints.OutsideIn,   // start on the rim, goal in the centre
    Keys = 2,
    TargetDifficulty = 3,              // keep trying until it rates 3 stars
};
MazeData maze = MazeGenerator.GenerateVerified(settings);
Debug.Log($"Par {maze.Par} steps, {maze.Difficulty} stars, solvable: {maze.Solvable}");

To run your own game on it, keep a MazePlay and ask MazeRules what each move does. These are the same rules the solver used, so your game always agrees with the par:

MazePlay play = MazeRules.NewGame(maze);
int here = play.Positions[0];
int next = maze.Topology.Neighbor(here, 0);       // the cell through side 0

if (MazeRules.TryStep(maze, play, 0, next, out string reason))
{
    if (MazeRules.IsSolved(maze, play)) Debug.Log("Solved!");
}
else Debug.Log(reason);                            // why it can't go: a wall, a locked door...

Or let the demo's 3D view draw and play it for you. MazeGame adds a MazeView3D to the same object and builds the whole maze from primitives:

using SimpleMaze.Demo;

var game = new GameObject("Maze").AddComponent<MazeGame>();
game.Begin(maze);
game.View.FrameCamera(Camera.main, -1);           // -1 frames every floor

A saved maze works the same way: give your script a MazePuzzleAsset field and use its Maze, or a MazePuzzlePack and its Get(index). See the API Reference for the rest.

Where to next

  • Generating explains every setting and what it does.
  • Demos shows how the demo scene is put together and how to change it.
  • Troubleshooting covers notes, fallbacks and limits.

Generating

Every setting, what it changes, and how the generator keeps its promises.

The Maze Generator window

Open Window > Living Failure > Simple Maze > Maze Generator. The settings are on the left in six sections (Board, Carving, Shape, Players and movement, Gimmicks and Quality), and the mazes you've made are on the right with the analysis pane. Fields only appear when they apply, so Rings shows for a circular board and Width and Height for the others.

Every maze the window makes goes through verified generation: if a maze misses what you asked for, the generator tries the next seed, and if none of its tries succeed it gives you the closest one with a note saying why. Generation runs on a background thread with a progress bar and a Cancel button, and your settings and the unsaved mazes survive a recompile.

Each table below gives the label in the window, the matching field on MazeSettings for code, and the range it accepts. Values outside the range are clamped, so code can pass anything.

Board

SettingFieldRangeWhat it does
GridGrid.ShapeSquare, Hex, Triangle, CircularThe cell shape. Square cells have 4 sides, Hex 6 (pointy-top), Triangle 3 (alternating up and down), and Circular is a centre cell inside rings.
Width, HeightGrid.Width, Grid.Height2 to 64Cells across and rows (on Triangle, Width counts triangles per row).
RingsGrid.Rings2 to 40Circular only: the rings around the centre cell, which is the board's radius. Rings split into more cells as they grow, so cells stay roughly square from the centre to the rim.
First ring cellsGrid.FirstRing0, or 3 to 12Circular only: the cells in the first ring. 0 means automatic, which is 6.
FloorsGrid.Floors1 to 8Stack the board into floors joined by stairs. Every floor has the same outline and holes, and each is carved as its own maze.
Stairs per floorStairs1 to 4Stairs between each pair of floors. The first goes where that floor's maze is farthest from the stairs you arrived by, so every floor gets crossed, and each extra one adds a loop.

In code, TopologySpec.Grid(shape, width, height) and TopologySpec.Circle(rings) build the board, and you set Floors on the result.

Carving

SettingFieldRangeWhat it does
AlgorithmAlgorithm6 choicesHow the maze is carved. Each one has its own look: Recursive Backtracker makes long winding corridors, Prim short and bushy ones, Kruskal an even, patchy maze, Wilson a perfectly uniform one, Hunt and Kill long corridors with fewer dead ends, and Growing Tree anything in between.
BranchinessBranchiness0 to 1Growing Tree only: 0 gives long winding corridors and 1 short, bushy ones.
BraidBraid0 to 1Dead-end removal. 0 is a perfect maze, with exactly one route between any two cells, and 1 leaves no dead ends at all. Rolling mazes need some braid, because rolling needs loops.
Extra loopsLoops0 to 200 in the window, up to 999 in codeRandom openings, each adding a second way round.
RoomsRooms0 to 12Open rooms carved into the maze, spread out.
Room sizeRoomSize1 to 4How far each room reaches from its centre, in cells.
HolesHoles0 to 0.35The fraction of cells removed as holes. The board always stays in one piece.
Weave crossingsWeave0 to 40Square boards only: places where a passage tunnels under a corridor, like the bridges in a printed weave maze. The maze keeps its routes and just gains crossings.

Shape

SettingFieldRangeWhat it does
ShapeShapeNone, Circle, Ring, Heart, Star, Diamond, Cross, Text, CustomCuts the board to an outline. It works on every grid.
TextShapeTextA to Z, 0 to 9, space, - ! ? . + and <3 for a heartShape Text: the word, in the built-in 5 by 7 font. Give it a wide board, about 6 cells across per letter and at least 9 rows.
Image, or Mask rowsShapeMaskany picture, or rows of # and .Shape Custom: drop in any picture and choose whether its Dark, Light or Opaque areas are inside the maze, where the line falls, and how much Detail to keep (24 to 160 mask pixels across). The picture needs no import settings. Leave the image empty to type the shape as rows instead, top row first.
FillShapeFill0.3 to 1How much of the board the shape fills.
Connect piecesConnectPieceson or offWhen the shape falls apart into pieces, like the letters of a word, join them with the shortest bridges. Off keeps only the largest piece.

Players and movement

SettingFieldRangeWhat it does
MovementMovementStep, RollStep moves one cell at a time on every grid. Roll slides in a straight line until something stops it, on one-floor Square and Hex boards only. Anywhere else a Roll maze uses Step, with a note.
Start / goalEndpointsFarthest, Center Out, Outside In, FixedFarthest puts them at the two cells farthest apart along the maze. Center Out starts in the middle with the goal on the rim, and Outside In is the other way round. Fixed uses the two cells you give.
Start cell, Goal cellStart, Goalcell indicesFixed only. If they aren't two different playable cells, the farthest pair is used instead, with a note.
Open borderOpenBorderon or offCuts an entrance and an exit in the outer wall where the start and goal touch it, for printed mazes. With Farthest, both are then picked on the border.
PlayersPlayers1 to 4How many players are on the board.
Shared goalSharedGoalon or offWith several players: on gives everyone one goal (the centre, or the Fixed goal) from fair starts, which makes it a race. Off gives each player a goal of its own.

Fair races. On Step, a race's starts are picked so that every player's par is exactly the same. With gimmicks, each player has to fetch its own keys and gems, so the other players' starts are chosen after the gimmicks are placed, using each cell's real par. When a board has no fair starts, a "Players:" note says so, MazeData.Fair is false, and verified generation tries the next seed.

How players move

The solver, the generator and the play rules your game uses all follow the same rules, so a move your game allows is exactly a move the solver counted.

Step. A player moves one cell through an open side. A door needs its key, a shut gate blocks, a one-way side only lets you out its own way, a tunnel side leads under the corridor to the cell beyond, and stairs lead up or down a floor. A trap is never a legal step. Stepping into a portal puts you on its twin. Keys and gems are picked up where you land, and landing on a switch flips every gate of its colour. You are home when you land on your goal holding every gem. With several players, each one moves on its own and keeps its own keys, gems and switch flips, and players can pass through each other.

Roll. Every player rolls the chosen way at once, front-most first, until a wall, a locked door, a shut gate, a one-way side, the border or another player stops it. Tunnels are rolled straight through. Rolling onto a trap fails the whole roll, so nothing changes. A player picks up whatever it rolls over, and the players share keys and gems, which makes several rolling players a cooperative puzzle. A player is home the moment it rolls over its goal holding every gem, a portal throws it out of its twin still rolling, and a player that comes to rest on a switch flips its gates once the roll is over.

Gimmicks

Gimmicks are placed one type at a time in a fixed order (portals, keys and doors, switches and gates, gems, one-way sides, then traps), and the maze is solved again after each type. A type that can't be made to work is dropped on its own, with a note, and never drags the others down.

SettingFieldRangeWhat it does
Keys and doorsKeys0 to 6Each door locks a side on the way to the goal, and its key waits somewhere you can reach before that door, off the way and as far from it as possible.
SwitchesSwitches0 to 4Each switch has its own colour, and landing on it flips every gate of that colour between open and shut. Its first gate is shut across the way, and the switch waits before it.
Gates per switchGatesPerSwitch1 to 3The extra gates are decoys off the way. Some start open, and the switch shuts them.
Shortcut doorsShortcutDoorson or offOff (the default) puts every door and every switch's first gate truly in the way, so its key or switch is always needed. A maze with lots of loops may then get fewer doors, with a note. On lets doors and gates go where a player could walk around them, so the key opens a shortcut instead of the only way.
GemsGems0 to 8Every gem must be collected before a goal lets a player in. They go in dead ends first, spread out.
TrapsTraps0 to 0.25The fraction of free cells off the way that become traps. They sit beside the way as decoys, never on the way to anything you need.
One-way sidesOneWays0 to 24About half go along the way, so there's no going back, and the rest are decoys.
Portal pairsPortals0 to 4Pairs the best solution actually uses, so each one shortens the way, with the two ends far apart.
No soft-locksNoSoftLockson or offOnly accept mazes where no player can ever reach a position it can't finish from. It's off by default, because rolling mazes very often have such positions (like ice puzzles, give players a reset). Every maze reports it in CanGetStuck either way. On Roll boards it works best with Braid near 1.

When fewer doors, gates or portals fit than you ask for, you get fewer, with a note. As a rule of thumb, about one key or switch fits per 40 cells, and Auto lowers the count to that for you.

Quality

SettingFieldRangeWhat it does
Target difficultyTargetDifficulty0 to 5Keep trying mazes until one rates exactly this many stars. 0 accepts any rating. A miss returns the closest maze and says so in its notes.
Min coverageMinCoverage0 to 0.9The best solution must pass through at least this much of the maze. Gimmicks are also placed to make the player roam as much as they can.
Max attemptsMaxAttempts1 to 500Under Advanced: how many mazes to try for each result before falling back to the closest one.
State budgetStateBudget1,000 to 5,000,000Under Advanced: how many positions the solver may explore before it reports the maze as not verified. Several rolling players need more.

How the stars work

The rating comes from a score. On Step the score is the longest player's par, plus 5 for every real decision on the way (a fork whose wrong way runs 3 or more cells deep, so you can't see the dead end at a glance), plus 6 per key, 2 per gem or portal pair and 1 per one-way side. On Roll it is the par in rolls, plus 2 per key and 1 per gem.

StarsStep scoreRoll score
1up to 50up to 3
251 to 854 to 7
386 to 1268 to 11
4127 to 17912 to 22
5above 179above 22

The bands were measured on a sweep of everyday mazes (576 Step and 288 Roll boards, on every grid, sizes 6 to 24, all six algorithms, with and without braid and holes), so each star covers about a fifth of them. A maze that isn't proven solvable gets 0 stars. Bigger boards usually rate higher, so to hit a target, pick a board size that can reach it.

Auto and presets

Auto fixes settings that fight each other and tells you what it changed. In code it is MazePresets.Tune. It:

  • switches Roll to Step on a grid that can't roll (Triangle, Circular or stacked floors);
  • raises Braid to 0.6 on a Roll maze below 0.5, or to 0.9 when No soft-locks is on;
  • turns Weave off on a board that isn't Square;
  • lowers keys and switches to about one per 40 cells;
  • raises the State budget to 2,000,000 for several rolling players;
  • raises Max attempts to 150 for 4 or 5 star targets, coverage targets or No soft-locks.

Presets replace every setting with a ready-made recipe. In code they are MazePresets.All, and each one's Make() gives a fresh MazeSettings.

PresetWhat you get
Classic/Square 15×15A plain perfect maze.
Classic/Circular 10 ringsThe polar maze: start on the rim, finish in the centre.
Classic/Hex 13×13Honeycomb cells, six ways out of each.
Classic/Triangle 21×10Alternating triangles, three sides each.
Classic/Printable 30×30Big, with an entrance and an exit cut in the outer wall.
Shapes/Heart (circular)A circular board cut to a heart.
Shapes/Star (hex)A hex board cut to a star.
Shapes/Word: MAZEThe board is the word, and its letters are joined by bridges.
Shapes/Ring with roomsA circular ring with open rooms to cross.
Gimmicks/Keys and doorsThree locked doors on the way, each with its key hidden before it, and gems to collect.
Gimmicks/Switches and gatesShut gates on the way, opened by switches, and decoy gates the switches shut.
Gimmicks/Traps and one-ways (hex)Traps beside the way, and one-way sides you can't go back through.
Gimmicks/Portal hop (circular)Portal pairs and gems on a circular board. It asks for 3 pairs, and often fewer fit on this board, which its note tells you.
Roll/Ice board (square)Roll until a wall stops you, rated 3 stars.
Roll/Co-op pair (hex)Two players roll together, each to its own goal.
Players/Race of 4 (hex)Four players and one goal in the middle, with exactly equal routes.
Players/Duel (circular)Two players race to the centre.
Big/Weave 20×20Corridors crossing over and under each other.
Big/Tower of 3 floorsThree floors joined by stairs, with the goal on the top floor and a key and a switch on the way.

Seeds, batches and saving

The same settings and the same seed always give the same maze. In the window, Random uses a fresh seed for every Generate and shows the one it used. Turn Random off to type a seed and keep it. Each maze gets its own seed, the batch's seed followed by a dash and the maze's number in the set (-0, -1 and so on), and every maze stores its own seed, which the analysis pane can copy.

Verified generation tries the seed itself first, then the same seed with #1, #2 and so on added, until a maze meets the request. A maze's stored seed is the one that worked, such as level-7-0#15, so it always reproduces that exact maze.

Batch count makes 1 to 20 mazes per click and adds them to the set. Clear on generate empties the set first, so each click replaces the batch. Save then writes Current as a MazePuzzleAsset, or Pack (all) or Pack (unique only) as a MazePuzzlePack, using the name in Save name as the suggestion.

Limits and notes

The generator never quietly gives you less than you asked for. Whenever it adjusts something, the maze's Notes say what happened, and the Generation report in the analysis pane lists them. Each note starts with the setting it is about, for example "Rooms:", "Players:" or "Target difficulty:". Common ones:

  • Not enough room: rooms, loops, crossings, players, portal pairs or doors that wouldn't fit on the board.
  • A gimmick dropped: no layout with it could be solved (without a soft-lock, if you asked for that) in 8 tries, so it was left out.
  • A setting that doesn't apply: Roll on a board that can't roll, or Weave off Square.
  • A shape that's too small for the board, so the whole board was used.
  • A missed target: no maze in the attempts rated the stars you asked for, and you got the closest.
  • Not verified: the solver's state budget ran out before every player got home.

Use MazeAudit.MeetsRequest(maze) to check whether a maze got everything its settings asked for. A pack's inspector can re-roll every maze in the pack that didn't (Re-roll > Fallbacks).

Generating from code

Everything the window does is available from code through MazeGenerator. Your settings object is copied and clamped first, so it is never changed.

using SimpleMaze;

var s = new MazeSettings
{
    Grid = TopologySpec.Grid(GridShape.Hex, 13, 13),
    Players = 2,
    SharedGoal = true,
    Keys = 1,
    Gems = 2,
    TargetDifficulty = 4,
    Seed = "level-7",                 // empty = a fresh random seed
};

MazeData one = MazeGenerator.Generate(s);          // one maze from this exact seed, solved and rated
MazeData good = MazeGenerator.GenerateVerified(s); // tries seeds until the maze meets the request
List<MazeData> pack = MazeGenerator.GenerateBatch(s, 10);   // seeds level-7-0 to level-7-9, each verified

Each one has an Async version that runs on a background thread and takes a CancellationToken. In Unity, await carries on on the main thread afterwards. GenerateBatchAsync also reports progress:

var progress = new Progress<int>(n => Debug.Log($"{n} of 10 done"));
List<MazeData> mazes = await MazeGenerator.GenerateBatchAsync(s, 10, progress, cancel.Token);

A custom shape comes from ShapeMask.FromRows, ShapeMask.FromText or ShapeMask.FromPixels, which reads raw RGBA bytes, so set Shape = ShapeKind.Custom and ShapeMask on the settings:

s.Shape = ShapeKind.Custom;
s.ShapeMask = ShapeMask.FromRows(
    "..##...##..",
    ".####.####.",
    "###########",
    ".#########.",
    "..#######..",
    "....###....");

See the API Reference for everything on MazeData.

API Reference

The public surface: generating, the maze data, playing, solving, pictures, assets and the demo.

Assemblies

Assembly and namespaceWhat's in it
SimpleMazeThe core: generation, the maze data, the play rules, the solver, pictures and the ScriptableObjects. It references no packages, and everything except the two asset types is plain C# that also runs outside Unity.
SimpleMaze.DemoThe 3D demo: the view, the game, the HUD, the hub, sounds and effects. It uses the Input System and Unity UI packages, and HDRP when that package is installed.
SimpleMaze.EditorThe Maze Generator window, the inspectors and the documentation menu. Editor only.

Everything below is in the SimpleMaze namespace unless it says SimpleMaze.Demo.

MazeGenerator

The entry point. Every method copies and clamps the settings first, so your object is never changed, and the same settings with the same seed always give the same maze. A null settings object means the defaults, and an empty seed means a fresh random one.

MethodWhat it does
MazeData Generate(MazeSettings settings)One maze from this exact seed, solved and rated.
MazeData GenerateVerified(MazeSettings settings, CancellationToken cancel = default) Tries the seed, then the seed with #1, #2 and so on, up to MaxAttempts, until a maze is sound: solvable, a fair race, no soft-lock and enough coverage when asked for, and the target stars. On a miss it returns the closest maze with a note. If it is cancelled before the first maze, it throws OperationCanceledException.
List<MazeData> GenerateBatch(MazeSettings settings, int count, CancellationToken cancel = default) A pack of verified mazes with seeds root-0, root-1 and so on.
Task<MazeData> GenerateAsync(...), GenerateVerifiedAsync(...)The same on a background thread.
Task<List<MazeData>> GenerateBatchAsync(MazeSettings settings, int count, IProgress<int> progress = null, CancellationToken cancel = default) A batch on a background thread, reporting how many are done (1 to count).

MazeSettings and enums

MazeSettings holds every input to the generator. Each field, its range and what it does is on the Generating page. It also has:

MemberWhat it does
MazeSettings Clamp()Puts every field into its valid range, in place, and returns the same object.
MazeSettings Clone()A copy.
bool HasGimmicksTrue when any gimmick is asked for.
EnumValues
GridShapeSquare, Hex, Triangle, Circular
CarveAlgorithmRecursiveBacktracker, Prim, Kruskal, Wilson, HuntAndKill, GrowingTree
EndpointsFarthest, CenterOut, OutsideIn, Fixed
MovementStep, Roll
ShapeKindNone, Circle, Ring, Heart, Star, Diamond, Cross, Text, Custom
ShapeSourceDark, Light, Opaque (which pixels of a picture are inside)

TopologySpec describes the board: Shape, Width, Height, Rings, FirstRing and Floors. Build one with TopologySpec.Grid(shape, width, height) or TopologySpec.Circle(rings, firstRing = 0), and Clamp() returns a copy with every field in range.

MazeData

A finished maze: the board, the passages, the players and gimmicks, and the solver's verdict stamped on it. It is serializable, so it stores inside a ScriptableObject, and plain C#, so it works outside Unity too. Cells are numbered as described in Cells and slots.

Board
TopologySpec GridThe board this maze is carved on.
string SeedThe seed that made it. With Settings it rebuilds this exact maze.
CarveAlgorithm AlgorithmHow it was carved.
int[] MasksEach cell's open sides: bit s set means slot s is open.
List<int> HolesCells removed from the board, including the ones a shape cut away.
ShapeKind Shape, List<int> OutsideThe shape it was cut to and the cells the shape cut away.
List<MazeRoom> RoomsThe open rooms, each a list of Cells.
List<Tunnel> TunnelsWeave crossings: each runs under the corridor cell Over, joining A and C on either side of it.
Players
List<int> Starts, List<int> GoalsOne start per player. One goal per player, or a single goal everyone heads to.
int Start, int GoalPlayer 1's start and goal.
int PlayerCountHow many players.
int GoalOf(int player)The goal that player heads to.
bool SharedGoalEvery player heads to Goals[0].
Movement MovementHow the players move. When Roll can't be used (a board that can't roll, or no start that rolls into the goal), the maze is stored as Step, with a note.
Gimmicks
List<int> KeysKeys[k] is the cell holding key k. Taking it opens every door of colour k for good.
List<Door> DoorsLocked sides: Cell, Slot and the Key colour that opens it. A door blocks both ways.
List<Switch> Switches, List<Gate> GatesA switch has a Cell and a Channel (0 to 3). A gate has a Cell, a Slot, the Channel that flips it, and StartsOpen for a decoy the switch shuts.
List<int> GemsEvery gem must be collected before a goal lets a player in.
List<int> TrapsTrap cells. Stepping or rolling onto one fails.
List<OneWay> OneWaysA side you can leave Cell through Slot, but never come back.
List<Portal> PortalsPairs of cells A and B. Entering either puts you on the other.
The verdict
bool SolvableThe solver proved every player can reach its goal.
int ParThe proven minimum: steps, or rolls. With several stepping players it is the total of their routes. -1 means unsolvable or not verified.
List<int> PlayerParsStep: each player's own par.
bool FairFalse only for a race (Step, several players, one goal) whose players' pars differ.
long OptimalCount, bool CountCompleteHow many different best solutions there are. When CountComplete is false the count was cut short and is a lower bound.
bool UniqueExactly one best solution, proven.
bool CanGetStuckSome reachable position can no longer reach the goal (a soft-lock).
int Difficulty, int ScoreThe 1 to 5 star rating (0 when unsolvable) and the raw score it comes from.
List<int> SolutionPlayer 1's positions along a best solution, start first. Step lists every cell, Roll lists where the player comes to rest after each roll.
List<PlayerRoute> RoutesStep with several players: each player's best route as Cells.
List<int> MovesRoll: the directions of the best solution.
Facts and notes
int DeadEnds, int Junctions, int LoopsCells with one way in or out, cells where three or more ways meet, and independent loops (0 is a perfect maze).
int SolutionLength, int Decisions, int RealDecisionsSteps on player 1's shortest route through the bare maze, the forks on it, and the forks whose wrong way runs 3 or more cells deep.
float CoverageThe fraction of the maze the best solution passes through (0 to 1).
int ShortcutsDoors and first gates a player can walk around.
MazeSettings SettingsThe settings it was made with.
List<string> NotesWhat the generator adjusted or couldn't do.
Helpers
Topology Topology, int CellCountThe grid, built from Grid on first use.
bool IsOpen(int cell, int slot)Is this side open? (Doors and one-way sides are open sides with a rule on them.)
bool IsHole(int cell)Is this cell removed from the board?
Passages ToPassages()An editable copy of the passages.

Cells and slots

A cell is an index from 0 to CellCount - 1, and each side of a cell is a slot. Use Topology.Neighbor(cell, slot) to find the cell across a side and SlotTo(cell, other) for the side between two neighbours.

GridCell indexSlots
Squarey * Width + x, row 0 at the bottom0 N, 1 E, 2 S, 3 W
Hexy * Width + x, row 0 at the bottom, odd rows shifted half a cell right 0 E, 1 NE, 2 NW, 3 W, 4 SW, 5 SE
Triangley * Width + x, row 0 at the bottom; cell (x, y) points up when x + y is even0 left, 1 right, 2 the flat side
Circular0 is the centre, then each ring counter-clockwise from angle 00 counter-clockwise, 1 clockwise, 2 inward, then one outward slot per cell in the next ring (the centre has one slot per first-ring cell)
Floorsfloor * cells per floor + base cellThe base grid's slots, then Up and Down (the ground floor has no Down slot and the top floor no Up slot)

Roll directions use the same numbers as the slots on Square and Hex.

Playing: MazeRules

Your game keeps a MazePlay and asks MazeRules what each move does. These are the same rules the solver counted with, so the game and the par always agree.

MazePlay
int[] PositionsEach player's cell (-1 once home).
bool[] HomeWhich players are home.
int[] KeysKeys held, one bit per colour, per player (on Roll every entry is the same, because keys are shared). Bits 16 to 19 are the switch channels flipped an odd number of times; read a gate with MazeRules.IsGateOpen.
int[] GemsGems collected, one bit per gem, per player.
MazePlay Clone()A copy, for undo.
MazeRulesWhat it does
MazePlay NewGame(MazeData m)Every player on its start, nothing held.
bool TryStep(MazeData m, MazePlay play, int player, int toCell, out string reason) Moves one player one cell: a neighbour, the far end of a tunnel, or up or down the stairs. On success the position is updated in place, including portals, pickups, switches and getting home. Otherwise it returns false with the reason, such as a wall, a locked door, a shut gate, a one-way side or a trap. There is also an overload without reason.
RollResult Roll(MazeData m, MazePlay play, int dir, List<int>[] trails = null) Rolls every player in direction dir. play is not changed. The result has After (the new position), Moved (false for a wasted roll) and Trapped (someone rolled onto a trap, so the roll fails). The optional trails, one list per player, receive every cell each player passes, for animation.
bool Hint(MazeData m, MazePlay play, int player, out int move, int stateBudget = 500_000) The first move of a best way on from here. On Step move is the cell to step to, and on Roll the direction. False with -1 when it's solved, that player is home, there is no way on (a soft-lock) or the search ran out of budget.
bool IsSolved(MazeData m, MazePlay play)Every player is home.
bool IsGateOpen(MazePlay play, int player, Gate gate)Is this gate open for that player now?
bool CanRoll(GridShape shape), bool CanRoll(TopologySpec grid)Roll is defined on Square and Hex, on one floor.
int DirectionCount(GridShape shape)Roll directions: 4 on Square, 6 on Hex, 0 elsewhere.
Vec2 Direction(Topology t, int dir)The way a roll direction points, in board units.
bool IsLegalStep(MazeData m, int from, int to, out string reason)Can a player step between two cells of the bare maze, ignoring gimmicks?
bool Validate(MazeData m, out string error)Is the data well-formed? Both sides of every opening agree, holes are sealed, every start, goal and gimmick sits on a playable cell, nothing overlaps, every gate has its switch, every tunnel runs straight under a corridor, and the stored route walks.
const int SwitchShift = 16Where the switch flips sit in MazePlay.Keys.

Solving and rating

MazeSolverWhat it does
MazeVerdict Solve(MazeData m, int stateBudget = 500_000)Solves the maze as it stands and returns a MazeVerdict: Solvable, Par, PlayerPars, Count, CountComplete, Unique, CanGetStuck, Explored, Path, Routes and Moves.
MazeVerdict SolveFrom(MazeData m, MazePlay play, int player = 0, int stateBudget = 500_000) Solves on from a position in play. Par is the moves still needed. On Step it solves for that player alone, and Moves holds the slot of each step.
int[] StepParsFromEveryCell(MazeData m, int player = 0, int stateBudget = 500_000) On Step, that player's par to its goal from every cell, holding nothing, with all the gimmicks (-1 for a hole or no way home). Null when the budget ran out.
const long CountCapThe best-solution count stops at 1,000,000,000.
Rating and checksWhat it does
MazeDifficulty.Score(MazeData m), MazeDifficulty.Stars(MazeData m)The raw score and the 1 to 5 stars. See How the stars work.
MazeAudit.Verify(MazeData m, out string before, out string after)Solves a stored maze again and restamps its verdict. True when anything changed.
MazeAudit.Reroll(MazeData m)A fresh maze from the same settings and a new seed, or null for a maze with no settings.
MazeAudit.MeetsRequest(MazeData m)Did the maze get everything its settings asked for: solvable, the target stars, the coverage, no soft-lock when asked, and a fair race?
MazeAudit.Short(MazeData m)One line, such as "par 63 steps, 3 of 5 stars, unique".

Presets and Auto

MemberWhat it does
MazePresets.AllEvery preset, in menu order. Each MazePreset has a Name (its menu path), a Description, and Make(), which returns a fresh MazeSettings.
MazePresets.Tune(MazeSettings s, List<string> changes)What the Auto button does: a copy with settings that fight each other fixed, and a line per change.

Pictures

MazeExport draws a maze in plain C#, so it works in a build, in the editor or on a server.

MemberWhat it does
byte[] Png(MazeData m, ExportOptions options = null)A PNG file's bytes.
string Svg(MazeData m, ExportOptions options = null)An SVG document.
byte[] Pixels(MazeData m, ExportOptions options, out int width, out int height)Raw RGBA bytes, top row first.
Png and Pixels with a MazeSketchA picture of a given moment, for example part way through the solution.
int FitSize(MazeData m, MazeSketch sketch, ExportOptions options, int maxWidth, int maxHeight) The Size that fits a box.
ExportOptionsDefaultWhat it does
StylePrintablePrintable (black on white with the start and goal), AnswerKey (with the solution drawn in), or AsShown (colour, every gimmick, the solution and a legend).
Size2048The maze's longer side in pixels (64 to 8192).
WallWidth0.12Wall thickness as a fraction of a cell (0.02 to 0.4).
Margin0.8The white border, in cells (0 to 4).
LegendtrueAsShown: draw the legend under the maze.
Floor-1Stacked boards: -1 draws every floor side by side, and 0 or more just that floor.

MazeSketch is the drawing model behind the pictures. MazeSketch.Build(maze, play, focus, trails) sketches a position, MazeSketch.Frames(maze) steps through the stored solution move by move, and MazeSketch.Describe(maze, cell, play) says what is in a cell in plain words.

Shapes

A ShapeMask is a black and white bitmap (Width, Height, Pixels, row 0 at the top). Set it on MazeSettings.ShapeMask with Shape = ShapeKind.Custom.

MethodWhat it does
ShapeMask.FromRows(params string[] rows)From rows of text: # (or any character other than a space or .) is inside.
ShapeMask.FromText(string text)Text in the built-in 5 by 7 font.
ShapeMask.FromPixels(byte[] rgba, int width, int height, bool bottomRowFirst, ShapeSource source, float threshold = 0.5f, int maxSide = 128) From a picture's RGBA bytes. Pass bottomRowFirst true for Unity's GetPixels32. The result is cropped to the shape and shrunk to at most maxSide across, and is null when nothing counts as inside.

Topology

A Topology is a grid as a graph of cells and slots. Get one from MazeData.Topology or Topology.Create(spec).

MemberWhat it does
int CellCount, int SlotCount(int cell)How many cells, and how many sides a cell has.
int Neighbor(int cell, int slot)The cell across a side, or -1 at the border.
int OppositeSlot(int cell, int slot), int SlotTo(int cell, int other)The neighbour's side that faces back, and the side between two cells (-1 when they don't touch).
Vec2 Center(int cell)A cell's centre in board units (x right, y up).
Edge EdgeOf(int cell, int slot)The wall on that side: a segment from A to B, or an arc when Arc is true.
int CellAt(Vec2 p)The cell under a point, or -1.
void Bounds(out Vec2 min, out Vec2 max)The board's bounds, for framing.
(int x, int y) Coord(int cell)Column and row, or index in ring and ring on a circular board.

A board with floors is a LayeredTopology, which adds Base, Floors, FloorOf(cell), BaseCell(cell), At(floor, baseCell), UpSlot(cell), DownSlot(cell) and IsStairSlot(cell, slot).

ScriptableObjects

TypeWhat it holds
MazePuzzleAssetOne maze: Maze and Label, plus shortcuts to its verdict (Shape, Floors, Movement, PlayerCount, Solvable, IsUnique, OptimalCount, Par, Difficulty, Seed) and Set(data, label).
MazePuzzlePackAn ordered list of mazes: PackName, Mazes, Count, Get(index) (null when out of range), Set, Add, Replace(index, data) and Clear().
PuzzleAssetsCreatePuzzle(data, label) and CreatePack(mazes, packName) make new instances in memory, which you can save with AssetDatabase.CreateAsset in the editor or use at runtime.
IPuzzleProviderBoth asset types implement it: AppendTo(List<MazeData> levels) adds their mazes in order, so one list in the inspector can take either.

Both are created by the Maze Generator's Save menu, not by an empty right-click Create.

Demo components

These are in SimpleMaze.Demo. The Demos page explains how they fit together.

TypeWhat it does
MazeDemoThe one component the demo scene needs. It builds the view, game, HUD, hub, sounds and effects in code and runs the flow. Fields: Camera, Pitch, HdrpLook, UiThroughCamera and a level pack per mode. Methods include Setup(), PlayEndless(mode, grid, stars), PlayLevel(mode, index), Generate(mode, settings), Begin(maze), NextGame() and BackToHub().
MazeGamePlays a maze on a MazeView3D: input, movement, every gimmick's animation, moves against par, undo, restart, hints, auto-solve, players and races, stairs and the camera. Start a game with Begin(maze), drive it with Push(Vector2 screen) or the keyboard, and listen to Won, Cue, Moment and Message.
MazeView3DBuilds a maze in 3D from primitives and meshes, on any grid, with no prefabs: Build(maze), CellPosition, StandPosition, FocusFloor, FrameCamera, and lists of the objects it made (keys, doors, gates, players and so on).
MazeHudThe in-game HUD and win panel, in uGUI with the built-in font.
MazeHubThe front screen: the welcome panel, the mode cards, and each mode's Endless and Level Pack setup.
MazeMode, MazeModesThe five demo modes, their names, colours, sample mazes and the Endless recipe for each mode, grid and star count.
MazeProgressEach pack level's best moves, kept in PlayerPrefs: Best, Record, Clear and NameOf.
MazeBeeperSounds made in code for every MazeCue, with no audio files.
MazeEffectsSmall effects (sparkles, dust, confetti) from a pool of primitives, for every MazeMoment.
DemoLook, DemoHdrpThe demo's colours and materials for the active render pipeline, and the HDRP volume that gives it the same look.

Building blocks

The pieces the generator is made of are public too, for anyone who wants to build their own pipeline. Most projects never need them.

TypeWhat it does
MazeBuilderBuild(settings, seed) makes one maze from clamped settings and an exact seed, which is what every MazeGenerator method calls.
PassagesThe open sides of every cell over a topology, plus holes, with Open, Close, IsOpen, Degree and OnRim.
CarverCarves a perfect maze through every playable cell with a CarveAlgorithm.
HolePlacer, BoardShape, Weave, GimmickPlacerHoles, shapes, crossings and gimmicks.
GraphSearchBreadth-first search, shortest paths and connectivity over passages.
RngThe small deterministic random generator, seeded from a string.

Demos

What the 3D demo does, how it is put together, and how to use its pieces in your own game.

The demo scene

Open Assets/SimpleMaze/Scenes/SimpleMazeDemo.unity and press Play. The scene holds a camera, a sun, and one object called Simple Maze Demo with the MazeDemo component and the five level packs assigned. Everything else, from the maze to the menus, is built in code when the scene starts, so there are no prefabs, models, textures or audio files to look after.

The demo reads the keyboard through the Input System package. If nothing happens when you press keys, set Project Settings > Player > Active Input Handling to Input System Package or Both.

One component: MazeDemo

Add MazeDemo to any object in a scene with a camera and press Play. When it starts, it builds:

  • a Maze child with MazeView3D, which draws the maze, and MazeGame, which plays it;
  • the HUD (MazeHud) and the hub (MazeHub), both in uGUI with Unity's built-in font;
  • MazeBeeper for the sounds and MazeEffects for the effects.

It also sets the camera up for the maze, giving it a plain background colour and a 40 degree field of view, and adds a sun if the scene has no light.

FieldWhat it does
CameraThe camera that shows the maze. Empty uses the main camera, and if there is none, one is made.
UiThroughCameraDraw the HUD and the hub through the camera instead of over the screen, so the camera's own picture includes them.
PitchHow steeply the camera looks down at the maze, in degrees (58 by default).
HdrpLookHDRP only: give the demo the same look as on the other pipelines. Turn it off to use your own volumes and lights.
ClassicPack, GimmicksPack, RollPack, TowerPack, RacePackThe level pack for each mode. Any can be empty, and that mode's Level Pack tab then says so. GimmicksPack is the Dungeon mode's pack.

The flow

1

Welcome

The first screen says what Simple Maze is and where to make your own mazes, with the publisher's contact at the bottom. Let's play goes on, and About on the mode screen brings it back.

2

Pick a mode

Five cards, each with a picture of a small maze of that mode drawn without its solution.

3

Endless or Level Pack

Endless lets you pick a grid and a difficulty from 1 to 5 stars, then builds a new maze to that order on a background thread. Level Pack shows the mode's levels as numbered buttons: grey still to do, the mode's colour solved, and gold solved on par. Play starts the first level you haven't solved. The demo remembers your choices for each mode while it runs.

4

Play

The HUD shows the maze, its stars, your moves against par, and whose turn it is. A button bar has Undo, Restart, Hint, Solve, Stairs (when you stand on stairs) and Menu.

5

Win

After a short celebration a panel shows your moves, the par and a rating, with Replay, Menu and Next (or press Enter). In Endless, Next builds a new maze to the same order, and in a pack it goes to the next level, then back to the pack after the last one. Menu or Esc returns to the mode cards at any time.

The five modes

ModeGridsEndless mazes
Classicall fourPlain mazes. The board grows with the stars.
Dungeon (MazeMode.Gimmicks in code)all fourKeys and doors from 1 star, gems from 2, a switch and traps from 3, a portal pair and one-way sides from 4, and a second switch at 5.
RollSquare and HexThe player rolls until something stops it. Braid is set so rolling has loops to use.
Towerall four2 floors joined by stairs, or 3 floors from 3 stars, from the rim to the centre.
Raceall fourTwo players take turns heading for one goal from fair starts, and the first one home wins.

On a circular board, Endless mazes run from the rim to the centre. The recipes are in MazeModes.Endless, and each one goes through MazePresets.Tune and verified generation, so the maze you get rates the stars you picked whenever the board can reach them.

The level packs

The demo ships with 50 levels in five packs in Assets/SimpleMaze/Generated. Each was made by the generator from a fixed seed, and each was played through to par to check it. They are ordinary MazePuzzlePack assets, so you can open them in the inspector, read their analysis, or export them as printable pictures.

PackLevelsWhat's in it
Classic12Every grid, from a 6 by 6 square up to an 18 by 18 square, including a hex star and a circular heart. Rated 1 to 5 stars.
Dungeon12One new idea per level for the first seven (a key, gems, a switch, a portal, one-way sides, traps and weave crossings), then mixes of them, ending with a 14 by 14 maze that has keys, gems, traps, a switch, a portal and one-way sides.
Roll10Square and hex boards from 6 by 6 to 10 by 10, the last one with gems and a portal.
Tower82 to 4 floors on every grid, some with a key or gems.
Race82 to 4 players on every grid, each race with equal routes for everyone.

Your own packs: make mazes in the Maze Generator, choose Save > Pack (all), and drag the pack into a slot on the MazeDemo component.

Progress: each level's best number of moves is saved in PlayerPrefs under SimpleMaze.Demo.Best.<pack name>.<level>, so it stays on that device. Races are not recorded, and neither is a level the auto-solver helped with. The win panel says "Level complete!", "New best" or "Your best is" accordingly.

Playing

The keys are listed at the bottom of the screen while you play, and in the Quick Start. A few details:

  • Moving: a key moves you toward the side whose direction on screen is closest to it, so the controls feel right from any camera angle and on any grid. Holding a key keeps walking, and a key pressed during a move is kept for the next one.
  • Moves count steps, or rolls that moved someone. Bumping a wall, a roll that goes nowhere and a roll onto a trap don't count.
  • Undo takes back one move at a time and Restart puts the whole maze back to the start.
  • Hint lays a gold arrow toward the next move of a best way on, worked out from where you are.
  • Solve (or F) lets the solver play the rest from where you are. Press it again, or F or R, to stop.
  • Several players: in a race the turn passes after every step, and the board shows the maze as the player whose turn it is sees it (their keys, doors and gates). When each player has its own goal, Tab or 1 to 4 switches player. On Roll every roll moves everyone.
  • The view: the camera frames the board in the space the HUD leaves free. You can turn it, tilt it and zoom, and when zoomed in it follows the player. On a maze with floors it follows the player's floor and hides the floors above.
  • The rating on the win panel is 3 pips on par, 2 a little over (up to 2 moves, or a quarter of the par on bigger mazes) and 1 beyond that. Races show who won instead.

Sounds and effects

MazeBeeper makes every sound in code when the demo starts: steps (three variants, so walking doesn't sound robotic), a bump, a roll, the stairs, a key, a gem, the goal opening, a door, a switch with its gates, a portal, spikes, a "no way" buzz, a hint, undo and restart, the win, and a button blip. M mutes it.

MazeEffects adds small effects made of a pool of tiny shapes: sparkles for a key or a gem in its colour, a gold ring when the goal opens, dust at a bump, a purple swirl at both ends of a portal, sparks off spikes, and confetti for the win. Nothing is created or destroyed while you play, and because the effects use the demo's ordinary materials, they look the same in every render pipeline.

Render pipelines

The demo works in URP (recommended), HDRP and the Built-in pipeline without any changes. Its materials use the active pipeline's default lit shader.

HDRP lights in physical units, sets the exposure on its own and takes its ambient light from a sky, so the demo would come out dark and tinted. With HdrpLook on, DemoHdrp adds a global volume with a fixed exposure, no tone mapping or bloom, and a gradient sky in the demo's colours, clears the camera to the demo's background colour, and sets the sun in lux. That code only compiles when the HDRP package is installed.

Using the pieces yourself

The demo components work on their own too. The smallest setup is one MazeGame, which adds its MazeView3D automatically:

using SimpleMaze;
using SimpleMaze.Demo;

var game = new GameObject("Maze").AddComponent<MazeGame>();
game.Begin(maze);                                   // build it and start a game
game.View.FrameCamera(Camera.main, -1);             // -1 frames every floor
game.Won += g => Debug.Log($"Solved in {g.Moves}, par {g.Par}");
  • Input: MazeGame reads the keyboard itself. For touch or a gamepad, call Push(Vector2 screen) with the direction on screen, and Undo(), Restart(), ShowHint(), StartAutoSolve() and TakeStairs(way) for the rest.
  • The camera is yours unless you turn on CameraControls. Only FollowFloor moves it, on mazes with floors.
  • Events: Won fires once when the game is won, Cue for every moment worth a sound, Moment for the same moments with a position and colour for effects, and Message for short notes such as "No stairs here".
  • A HUD: add a MazeHud and set its Game, or read Moves, Par, Solved and Player into your own UI.
  • The look: the colours, materials and shapes are in DemoLook.cs, and the timing is on MazeGame (StepTime, RollTime, PortalTime, Hop and SolvePause).
  • Without the 3D view: keep a MazePlay and use MazeRules directly, as in the Quick Start.

Troubleshooting

What a note means, why a maze came out the way it did, and what to change.

Reading the notes

Whenever the generator adjusts something, it writes a note on the maze instead of quietly giving you less. The notes are listed under Generation report in the analysis pane, and in MazeData.Notes from code. Each one starts with what it is about, such as "Keys:", "Players:" or "Target difficulty:", and the sections below explain the common ones. MazeAudit.MeetsRequest(maze) tells you whether a maze got everything its settings asked for, and a pack's Re-roll > Fallbacks replaces every maze that didn't.

"Target difficulty: 4 stars not found in 60 tries; closest was 3."

The stars come mostly from the par, so the board size sets what a maze can rate. A small board can't reach 5 stars and a huge one rarely rates 1. To hit a target:

  • make the board bigger for more stars, or smaller for fewer;
  • try another algorithm: long corridors make long routes, so on a 15 by 15 square board Recursive Backtracker mazes average 4.5 stars, Hunt and Kill 4.0, and Prim 2.9;
  • raise Max attempts under Advanced, or press Auto, which raises it to 150 for 4 and 5 star targets.

The rules behind the stars are on the Generating page.

Fewer gimmicks than I asked for

Gimmicks are only placed where they do their job, and the generator says when it couldn't fit them all:

  • "Keys: the maze's loops (or portals) leave room for 1 of 3 doors that can't be walked around." A door only counts when it's truly in the way, and loops give the player ways round it. Lower Braid and Extra loops, or turn on Shortcut doors to allow doors that can be walked around. The same goes for switches and their gates.
  • "Keys: the way to the goal is too short for 4 doors." Use a bigger board, or Farthest endpoints for a longer way.
  • "Portals: room for 1 of 3 pairs the solution would actually use." Each portal must shorten the way, so it needs a long way with loops. A bigger board or more loops allows more.
  • "Gems: room for 2 of 5." Gems go off the way, in dead ends first, and each one must be collectable together with the others. A small board has few such spots, and on a rolling board a gem also has to be reachable without getting stuck. Use a bigger board.
  • "Gimmicks: no layout with traps was solvable in 8 tries; dropped the traps." That type couldn't be made to work with everything else on this board, so it was left out and the rest kept.

As a rule of thumb about one key or switch fits per 40 cells, and Auto lowers the counts to that.

"Can get stuck"

The analysis pane warns when a player can reach a position it can no longer finish from. One-way sides, switches and rolling can all cause it. Rolling mazes have it very often, just like ice puzzles, so it is reported rather than forbidden by default. You can:

  • turn on No soft-locks so only mazes without it are accepted (on Roll, raise Braid close to 1 as well, or press Auto);
  • or keep the maze and give players a Restart and Undo, as the demo does.

"Not verified" or "the state budget ran out"

The solver explores every position the players can reach, counting keys, gems and switch flips. Several rolling players, many keys and gems, or a very large board can need more positions than the budget allows, and then the maze is marked not verified instead of guessed. Raise State budget under Advanced (up to 5,000,000), or use a smaller board, fewer players or fewer gimmicks. Several rolling players need the most, and Auto raises the budget to 2,000,000 for them.

The race isn't fair

"Players: the gimmicks leave no other start exactly as far from the goal as player 1's (pars 36, 32), so the race isn't fair." On Step, every player in a race needs the same par, and with gimmicks the other players' starts are picked after the gimmicks are placed, using each cell's real par. On some boards no such cells exist. Verified generation, which the window always uses, skips these mazes and tries the next seed, so you only see this note from MazeGenerator.Generate or after every attempt missed. A bigger board or fewer gimmicks helps.

Roll or weave was ignored

  • "Movement: Roll needs a Square or Hex board" or "Roll keeps to one floor": rolling needs straight lines, which Triangle and Circular boards and stacked floors don't have, so those mazes use Step.
  • "Movement: no start could roll into the goal on this maze": rolling needs loops. Raise Braid (Auto sets at least 0.6).
  • "Weave: crossings need a Square board": weave crossings only exist on Square boards.

My shape looks wrong

  • "Shape: Heart leaves only 20 cells on this board, which is too few, so the whole board was used." Use a bigger board or a higher Fill.
  • "Shape: 4 separate pieces; joined them with 3 bridges." The shape fell apart, for example into the letters of a word, and was joined up. Turn off Connect pieces to keep only the largest piece instead.
  • Text: give it a wide board, about 6 cells across per letter and at least 9 rows. On a Square board the font's diagonal strokes only touch at their corners, so a letter like M can lose its diagonals and read as H. Check the preview, and try another word or grid if it matters.
  • "Nothing in this image counts as inside." Switch Inside is between Dark, Light and Opaque, or move the threshold slider.

Generation feels slow

Plain mazes are fast at any size. Measured on a desktop PC outside Unity (your machine and Unity's runtime will differ):

MazeTime
Square 64 by 64, or circular with 40 ringsunder 10 ms
Square 64 by 64 with 8 floorsabout 30 ms
Hex 13 by 13, a race of 4 with a key and gems, verifiedunder 10 ms
Square 30 by 30 with 6 keys, 8 gems, 4 switches and 4 portals0.5 to 1.4 s
Square 14 by 14, 4 rolling players with gems1.3 to 4.7 s

The costly part is solving, which grows with every key, gem and switch (each one multiplies the positions to search) and most of all with several rolling players, who are solved together. Every gimmick type is solved again after it is placed, up to 8 times. Generation runs on a background thread in the window and with the Async methods, so it never freezes the editor or your game, and it can be cancelled.

The demo ignores the keyboard or the mouse

The demo uses the Input System package. Set Project Settings > Player > Active Input Handling to Input System Package or Both. The hub and the HUD make an EventSystem with the Input System's UI module when the scene has none. If your scene already has an EventSystem, its input module has to match your input handling.

HDRP looks dark, or ignores my volumes

Out of the box HDRP uses physical light units, automatic exposure and sky lighting, which would make the demo dark and tinted. MazeDemo.HdrpLook (on by default) adds a global volume at priority 10 with a fixed exposure and a gradient sky, and sets the sun in lux, so the demo looks the same as on the other pipelines. If you want your own volumes and lights to take over, turn HdrpLook off.

Resetting the demo's progress

The demo saves each pack level's best moves in PlayerPrefs, under SimpleMaze.Demo.Best.<pack name>.<level>. Call MazeProgress.Clear(packName, levelCount) to forget a pack, or delete those keys. A pack's progress is filed under its pack name, so renaming a pack starts it fresh.

Seeds and versions

The same settings and seed always give the same maze within a version of Simple Maze. A future version that improves the generator may change what a seed produces. Saved assets store the maze itself, not just its seed, so your saved mazes and packs never change. Verified generation stores the seed that actually worked, such as level-7#3, so copying a maze's own seed always rebuilds that exact maze.

Console messages

  • "[Simple Maze] Documentation page not found": the documentation menu looks for these pages in a folder whose path contains SimpleMaze and Documentation. Keep the Documentation folder inside the Simple Maze folder.
  • "[Simple Maze] Generation failed": something went wrong on the generation thread. Please send the message with your settings and seed.

Still stuck

Email livingfailuregames@gmail.com with your Unity version and, for a maze-specific question, its seed and settings. Copy seed and Copy solution in the analysis pane make that easy, and a seed rebuilds the maze exactly.

About

What this is, who made it, and how to get help.

About Simple Maze

Simple Maze is a maze generator for Unity, made by Living Failure. It builds mazes on square, hex, triangle and circular grids, with keys and doors, switches and gates, gems, traps, one-way sides, portals, rolling players and up to 8 floors. Then it solves each maze exactly, stamps it with its proven par and a 1 to 5 star rating, and stores it as a ScriptableObject or exports it as a picture. A complete 3D demo, built entirely in code, is included.

Version 1.0, made and tested with Unity 6000.6.

What's in the package

FolderWhat it holds
RuntimeThe core (generation, the maze data, the play rules, the solver, pictures and the ScriptableObjects) and, in Runtime/Demo, the 3D demo.
EditorThe Maze Generator window, the inspectors and the documentation menu.
ScenesThe demo scene.
GeneratedThe demo's five level packs.
DocumentationThese pages, which open offline from Window > Living Failure > Simple Maze > Documentation.

Design principles

  • Solve exactly. The par and the number of best solutions come from searching every position the players can reach, not from an estimate.
  • Prove the gimmicks together. Keys, gates, gems, portals and the rest are solved with the maze, so a door that looks necessary really is.
  • Be fair. Race starts are picked so every player's par is the same, gimmicks included.
  • Be honest. When a request can't be met, the maze's notes say so. Nothing is quietly downgraded, and the store page claims nothing the solver can't back up.
  • One set of rules. The generator, the solver and the play rules your game uses all move players the same way, so your game and the par always agree.
  • No bloat. The core has no package dependencies, and the demo and the editor tools live in their own assemblies. The demo needs no prefabs, models, textures or audio files.

More from Living Failure

Simple Klotski: sliding-block puzzles in five modes (Gridlock, Klotski, Clear, Clear 3D and Clear Snakes), each board solved exactly and stamped with its proven par.

Simple States: switch-and-state logic puzzles in four modes: Classic combinations, Ordered chains with traps, Lights Out with paintable pattern goals, and the three-state Trilight grid.

Simple Connect: a connection-puzzle toolkit where you draw the pairs (square and hex), rotate the pipes, or rotate a whole network back together.

Simple Hamiltonian: a cover-all, single-path puzzle generator (square and hex), where the solution is one unbroken line that visits every tile once.

All on the Unity Asset Store.

Support

Questions, bugs and ideas are welcome at livingfailuregames@gmail.com. Please include your Unity version and, for anything about a particular maze, its seed and settings, since a seed rebuilds the maze exactly. Corrections to these pages are welcome too.

License

Distributed under the Unity Asset Store End User License Agreement. Use it in your games and tools, but don't resell or redistribute the source as an asset.