Skip to content

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dream Blast

A match-blast puzzle game built in Unity 6 (6000.3.21f1). Tap groups of same-coloured cubes to clear them, build rockets from large groups, and clear every obstacle before the moves run out.

Ten levels, defined in JSON, with three obstacle types and a rocket combo.


Running it

Open the project in Unity 6000.3.21f1 and press play from Assets/Scenes/MainScene.unity. The game is portrait-only; the Game view is set up for 1080x1920.

To jump to a specific level without playing through the earlier ones, use the editor menu DreamBlast → Set Level Number. It writes straight to the saved progress, so the change shows up the next time MainScene loads.

Tests

70 edit-mode tests cover the game rules. Run them from Window → General → Test Runner, or from the command line:

/Applications/Unity/Hub/Editor/6000.3.21f1/Unity.app/Contents/MacOS/Unity -batchmode -projectPath . -runTests -testPlatform EditMode -testResults results.xml -logFile -

How it fits together

Core/       Scene orchestration, level loading, input, saved progress
Board/      GameBoard owns the grid; BoardItem hierarchy for what sits in it
Mechanics/  The rules: matching, blasting, gravity, rockets
Effects/    Debris particles and the win celebration
UI/         Top bar, fail popup, celebration
Animation/  TweenHelper — the project's tweening
Data/       Level format, enums, the sprite library asset
Utils/      Constants, object pool

The shape of it: GameBoard owns state, the mechanics own rules, and nothing else touches the grid directly.

GameBoard holds the 2D array of items and converts between grid coordinates and world positions. It does not know how a blast propagates or how gravity settles a column — it delegates to BlastMechanic, FallMechanic, RocketMechanic and MatchFinder, each of which takes the board in its constructor and reads and writes the grid through the board's accessors.

LevelSceneController sits above all of it as the conductor: it loads the level, hands it to the board, wires the UI to the board's events, and reacts to win and loss. The board raises OnMoveMade, OnGoalUpdated, OnLevelWon and OnLevelLost; it never reaches into the UI itself.

Input is deliberately separate. InputHandler turns a screen tap into a grid coordinate and calls OnTapped() on whatever is there. The board never learns where taps come from, which is what makes the mechanics testable without simulating input.

A move, end to end

  1. InputHandler converts the tap to a cell and calls BoardItem.OnTapped().
  2. A Cube asks the board to blast; MatchFinder flood-fills its colour group.
  3. If the group is 2 or more, the move is spent and BlastMechanic clears the cubes and damages adjacent obstacles.
  4. If the group was 4 or more, the cubes animate into the tapped cell and a rocket is left behind.
  5. Any rockets caught in the action explode, in turn queueing whatever they hit.
  6. FallMechanic settles the columns and refills from the top.
  7. Rocket hints are recalculated, and the board checks for win or loss.

Game rules

Item Cleared by Falls Notes
Cube Any blast or rocket Yes Tap a group of 2+ to clear it
Rocket Tap, or another rocket Yes Splits into two parts that sweep the row or column
Box Adjacent blast, or rocket No 1 hit
Stone Rocket only No 1 hit; adjacent blasts do nothing
Vase Adjacent blast, or rocket Yes 2 hits, but at most 1 per blast

A group of 4 or more creates a rocket at the tapped cell, and every cube in such a group wears a rocket icon so the opportunity is visible before you tap.

Two adjacent rockets combo: instead of one row, the explosion sweeps three rows and three columns centred on the tapped rocket.

The level is won when every obstacle is gone, and lost when the moves run out first.

Level format

Levels live in Assets/Resources/Levels/level_01.jsonlevel_10.json.

{
  "level_number": 1,
  "grid_width": 9,
  "grid_height": 10,
  "move_count": 20,
  "grid": ["bo", "bo", "r", "rand", ...]
}

grid is a flat array read bottom row first, left to right: grid[0] is the bottom-left cell, grid[grid_width] starts the second row from the bottom.

Token Meaning
r g b y Red, green, blue, yellow cube
rand Random-coloured cube
hro vro Horizontal / vertical rocket
bo Box
s Stone
v Vase

LevelLoader rejects a malformed file at load time — wrong grid length, non-positive move count, unknown token — and says exactly which cell is wrong. Without that, a short grid array quietly builds a half-empty board and an unknown token silently becomes a random cube; both are far harder to trace than a load-time error.

Notable decisions

No third-party dependencies. The case study allows tween libraries; TweenHelper is a small hand-rolled one instead. It is built on a single core coroutine that drives a normalised value through an easing curve, so there is one timing loop in the codebase rather than one per animation.

Everything is pooled that runs during play. Cubes, rockets, rocket parts and debris particles all come from ComponentPool<T>; a board prewarms width * height cubes, so play itself does not allocate. Because pooled items are reused, BoardItem.Initialize doubles as the reset point for anything gameplay or animation code mutates. Obstacles are not pooled — they spawn once when the board is built — and neither are the celebration particles, which run a few dozen times after the level is already over.

Sprites are normalised to the cell. Every world-space sprite has its pixels-per-unit set to its own width, so one sprite is exactly one cell wide at scale 1 and CELL_SIZE stays 1. The art is slightly taller than a cell, so items in adjacent rows overlap; each item offsets its sorting order by its row, which makes lower rows draw in front instead of leaving the overlap to Unity's undefined same-order sorting.

Items are built in code, not from prefabs. Every board item is the same two components — a SpriteRenderer and its BoardItem script — so a prefab per type would be five assets to keep in sync for no gain.

Batch animations keep their own loop. Falling and group-merging animate dozens of items on one shared timeline, so those run a single loop rather than a tween per item. They still share TweenHelper's easing functions.

Deadlocks are reshuffled, not survived. If an action leaves a board with no pair and no rocket, every tap would be rejected and the player could only lose by running the clock out. MatchFinder.HasAnyValidMove catches that and the board redistributes the colours it already has — obstacles stay put, so the puzzle's shape and the goals are untouched. Random redistribution can keep missing, so after a bounded number of attempts a matching pair is placed deliberately. The same check runs while the board is built, so a level never starts unplayable.

Known limitations

  • No audio.
  • Rocket direction is random when one is created.
  • Rocket parts sweep the whole row or column; nothing stops them early.
  • A board where no two cubes are adjacent at all cannot be rescued by reshuffling, and is left as it is.

About

Unity 6 match-blast puzzle game (Dream Games case study) — cube blasting, rockets, obstacles, 10 JSON-driven levels

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages