Inspiration
For this project, I took inspiration from Rush Hour and other escape-to-win sliding puzzles. I wanted to make something more than just a carbon copy of existing puzzles, so I brainstormed new mechanics.
At first, I had the idea of having movement in the form of power-ups. Blocks would be allocated power-ups for vertical/horizontal movement at the start of the game. Then they could be moved accordingly once the level began. So the player would have to think of how to allocate these movements, and subsequently escape the puzzle.
However, this seemed too complicated, both in terms of UX design and game design. Hence I went back to the drawing board and came up with Blocked.
What it does
An innovative spin on the classic sliding puzzle game. Sliding puzzle games usually feature free-moving blocks that have to be slid to achieve a set position or to let a block free (Rush Hour). Any block may be moved, as long as there is space ahead of it.
Core game mechanic - Control
Blocked introduces a new concept: control.
At any point in the game, only the controlled block may be moved. Control may be transferred to another block by sliding into it. However, control may only be transferred if the currently controlled block impacts exactly one other block.
Having a single controllable block at a time, combined with the control-transfer mechanic, introduces several interesting mechanisms that are explored in the several levels in the game. Blocks have to be coordinated in order to prevent deadlock later in the game. Walls also add a new dimension to the game by implicitly introducing single-axis movement and one-way mechanisms.
Level editor
The game also features an in-built level editor that allows for convenient level prototyping and sharing.
Sharing levels
User-created levels can be shared via a button that is on the generated level page. This copies a shareable URL into the user's clipboard.
Generating solutions
Solutions can be generated then autoplayed. The generator uses the A* algorithm to search for the optimal solution.
How I built it
Most of the views and logic were built on top of Flutter native widgets and capability.
FocusableActionDetector
was used for hotkey detection on keyboard-enabled devices. GestureDetector
and MouseRegion
were used extensively for the level editor which required gesture information for the drag-and-drop UX.
Animations
Almost all animations in Blocked are done implicitly via animation widgets.
AnimatedPositioned - Block movement
The movement of blocks are implicitly animated using AnimatedPositioned
.
This automatically animates the following:
- Block movement within the puzzle.
- Playing the solution to a puzzle.
- Resizing the resizable blocks/walls in the editor.
AnimationController - Main menu background
The main menu background animation is done using an AnimationController
, and a Bloc
that provides the same controller to subsequent app screens, so as to persist the current state of the animation.
Hero transitions
Lots of Hero
transitions were used throughout the app to smoothly between screens containing the same puzzle.
Theming
The app is fully themed using Material You color palettes. The colors are generated by the flex_color_scheme
package, and are adapted to both light and dark themes. adaptive_theme
was to store user theme brightness (Light/Dark/System) preferences.
Editor
The level editor features drag-to-resize and snappy drag-to-move gestures. MouseRegion
was used to change cursor appearance, and GestureDetector
was used to detect drag/pan gestures.
Each resizable object is handled by a Resizable
widget, which keeps track of its internal size and offset whenever the user shifts or resizes it. It then snaps the "raw" size and offset to pre-defined intervals, so that the block/wall snaps to its appropriate size and position. Upon gesture completion, the internal size and offset are set to match the snapped size and offset.
Packages used
Blocked was mostly built from the ground up. Some packages were used for utility functions. These include:
- adaptive_theme used for storing theme choices
- archive used for map data compression when sharing urls
- equatable used for easy equality and comparison between objects
- flex_color_scheme used for color scheme generation
- flutter_bloc used for state management
- flutter_portal used for the pop-ups in the level editor
- google_fonts used for the app font
- material_design_icons_flutter used for additional icons
- shared_preferences used for level progress storage
- yaml used for reading map data
Challenges I ran into
- Creating levels by hand was difficult, and thus a more robust method of level creation was needed. This gave rise to the level editor.
FocusableActionDetector
kept unintentionally losing focus, which causes keyboard shortcuts to break in the level editor.- Testing levels was very time-consuming, and it was hard to tell whether a created level was solvable or not. This was solved with the introduction of a puzzle solver.
- Navigator 2.0 was hard to figure out, especially when dealing with the bidirectional changes between app-induced navigation and URL-based navigation. This was prevalent when working on loading level information from the URL for the editor and generated level pages.
Accomplishments that I'm proud of
- Creating a built-in level editor
- Creating a puzzle solver that uses A* Search with a simple heuristic
- Being able to make a fun game
What I learned
- How to chain various native Flutter widgets to create complex widgets like resizable containers.
- Getting a computer to solve a problem for you is almost always better than solving it yourself
What's next for blocked
Addition of complex mechanics, for example:
- Floor spaces that require blocks on top of them to open the exit.
- Blocks with durability (fixed number of moves or control transfers before disappearing)
Built With
- flutter
- flutter-bloc
Log in or sign up for Devpost to join the conversation.