View: Multi-Page Single Page

The Demo

What ships, how it's wired, and how to make it yours.

What ships

One scene, in Assets/SimpleKlotski/Scenes/, that plays all five modes: a Canvas menu, a 3D board, a HUD with par scoring, undo, hints and a step-by-step auto-solver, procedural sound, and a win flow that rolls into the next level.

It is meant to be read as much as played — the referee holds no UI code, the UI talks to it through one small interface, and every component is a plain MonoBehaviour you can copy or replace.

Controls

  • Gridlock / Klotski — drag a block to slide it. The drag is clamped live to what the rules allow, and a release of less than one cell snaps back.
  • Clear — click a block to send it off along its arrow. A blocked block thunks and stays.
  • Clear Snakes — click anywhere on a rope to send it off. It slithers out head first, its body following the same track, so only the lane ahead of the head has to be clear.
  • Clear 3D — click a block to pull it free; Alt-drag or right-drag orbits, middle-drag or Shift-drag pans, scroll zooms.
  • R reset · Z undo · H play one optimal move · F step-by-step auto-solve (R cancels it).

The HUD prints the controls for whatever you are playing, so the player never has to remember a menu screen.

Scene anatomy

Three objects, that's all:

1

Simple Klotski Demo

Carries the whole game: KlotskiLevelRenderer (builds the board), KlotskiGame (the referee), KlotskiInput (drag/click/keys), KlotskiBeeper (sound), KlotskiDemo (the flow), KlotskiCameraFramer (frames each board) and KlotskiOrbitCamera (3D navigation).

2

Klotski Menu

A Canvas with the mode cards and the source step. Hides itself on play and comes back when the flow returns.

3

Klotski HUD

A Canvas with the in-game card and the solved overlay.

The scene ships pre-built, so the one-click builder menu items that created it are commented out in Editor/Tools/. Uncomment a [MenuItem] there if you ever want to regenerate a piece of it from scratch.

The HUD

Top-left card: the level caption, the objective for this mode, a live counter (blocks left, or plates held when the board has checkpoints), Moves · Par, difficulty pips, the controls line, and Reset / Undo / Solve / Menu buttons. While the auto-solver runs the buttons swap for an "Auto-solving…" row with Cancel.

On a win, an overlay reports how you did against par — "Perfect — solved on par (14 moves)", or "Solved in 19 moves · par 14" — with a Next button.

All of it is driven from IPuzzleSession, so the numbers come from the referee rather than being tracked twice.

Generating while you play

A demanding move window can take hundreds of attempts to satisfy. Generating that on the main thread freezes the game, which reads as a hang rather than as work — so the demo runs it off-thread through KlotskiBatch.GenerateAsync and shows a progress overlay while it waits.

The overlay is a bar plus a label that counts attempts ("Generating… attempt 47"). It appears for endless boards and for the first board of a run; a pack level is already built, so it never shows there. Input is inert while it is up — the previous board is unloaded the moment generation starts, so R, Z, H and F cannot act on a board that is about to be replaced.

Reading it from your own code: KlotskiDemo.IsGenerating, GenerationProgress (0–1) and GenerationStatus are public, and KlotskiHud polls them in Update() rather than through a callback — the progress callback arrives on the worker thread and must not touch a Unity object.

The dials coerce themselves

Board dimensions are [Range] sliders (3–8 per side for the sliding modes, 4–16 for Clear Snakes, depth 1–8, fill 0.2–1). The ones a slider cannot express — paired move windows, and caps that depend on another field, like obstacles at a quarter of the board or pieces at width × height — are coerced in OnValidate as you type.

This was never a safety fix. Every generator already calls GenerationSettings.Clamp(), so a wild value could not break anything; it was dishonest. The field kept reading 999 while an 8×8 board was quietly built, and the demo looked like it had ignored you. Now the inspector always shows what generation will really use.

Driving the component from script? OnValidate never runs, so the clamp happens at generation time instead and logs once — "Demo dials coerced to valid ranges: width 999→8" — naming every field it changed. Repeats are suppressed, or one bad dial would log per level for the rest of the session.

Playing your own packs

Generate a batch, Save ▸ Pack (all), then drag the pack onto the matching slot of the KlotskiDemo component (one slot per mode, five of them). The mode's Play Level Pack button lights up and reports the level count; finishing the last level returns to the menu.

Play Endless needs nothing assigned — it generates fresh boards as you go, using the per-mode dials on the same component — including the rope dials, so Clear Snakes generates endlessly without needing a pack. Those boards are generated on a background thread, so the frame never stalls; see Generating while you play.

Reskinning it

The renderer is deliberately art-free by default, and every visual is a slot:

  • Piece prefab / Hero prefab — drop in your own meshes. Author them for a 1×1 cell; the renderer scales a prefab out to its block's footprint, so chunky symmetric shapes stretch best.
  • Arrow glyphs — three optional textures (arrow, circled dot, circled cross), white on transparent, 512×512 with a comfortable margin. Leave them empty and the same glyphs are generated procedurally, so a zero-art project still reads correctly.
  • Colours — block, hero, held, base, border, obstacle, target, exit, arrow and checkpoint colours are all fields. Tints go through a material property block setting both _BaseColor and _Color, which is why the demo looks right in Built-in, URP and HDRP without shader swapping.
  • Layout — cell size, block height, hero height, glide speed.

Blocks get their collider and a KlotskiPieceHandle automatically, so a custom prefab needs no special setup to be clickable.

Building your own game on it

Two honest options:

  • Keep the referee, replace the shell. KlotskiGame has no UI dependencies: call LoadLevel, TrySlide / TryRemove, listen to LevelSolved, and draw whatever you like. Undo, hints, par and auto-solve come along for free.
  • Keep the data, write your own referee. Everything the rules need is on KlotskiPuzzle, and KlotskiSolver.MaxSlide is the movement law in one call — implement IPuzzleSession and the shipped HUD will drive your referee unchanged.