I always was amazed by how easy it was to build apps using Flutter. I developed some games and already made 3D like buttons for them. However, the announcement video of this Hackathon included a 3D board with 3D tiles! It was nice looking so I decided to try to make something similar.
I searched for ways of doing it in Flutter with low impact on performances. I had no luck but I found ztext.js for the Web, a library which stacks text elements on top of each other with some matrix transformations to simulate volumes.
Since everything is a widget in Flutter, I figured that I could use that same principle to Widgets! It lead me to create ZWidget which is used on ZPuzzle, my submission for the Flutter Puzzle Hack.
What it does
ZPuzzle core gameplay is the same as the base puzzle but the tile numbers are replaced with correct position indicators. Additional features make it unique:
- 3D effect: 3D like animations using the package made for this hackathon, ZWidget
- Widget as background: Use almost any Widget as background of the puzzle. You can also pick your own images, including Gif.
- Correct position indicators: They point towards the correct position of each tile.
- Auto-solve: An easy to learn approach is used so the player can reproduce it.
- Responsive design: Looks well on almost any screen size.
- Multi-Platform: Works on Mobile, Desktop and Web (with limitations on the Web).
- Inputs: Play the puzzle with your mouse or keyboard on Desktop and Web or by tapping on the tiles on Mobile.
- Gyroscope: Use the gyroscope to see the 3D effect in action on Mobile.
The app supports English and French languages via
flutter_localizations, the recommended package in the official docs.
How I built it
Thanks to the work done by Very Good Ventures, the main logic of the game was already done and I could focus on the UX. I took their base Puzzle class and added a few features to make my own version of the game. Rest of the app is done by myself. I took inspiration from Liquid Studio to make the app's background.
Below is a detail of the ZPuzzle core features.
Overlapping widgets with some matrix transformations can lead to a 3D like effect. That's what the package
ZWidget uses to make this effect.
It was done for this hackathon and more improvements are coming. Find more on the Github repo of ZWidget or on pub.dev.
Widget as background
The use of the
OverflowBox widget with an alignment calculated using a
FractionalOffset is the key for this feature. This is what it looks like:
Stack(children: [ Positioned.fill( child: ClipRRect( borderRadius: widget.borderRadius, child: OverflowBox( maxWidth: double.infinity, maxHeight: double.infinity, alignment: fracOff, child: SizedBox( child: widget.child, height: nbTiles * widget.tileSize, width: nbTiles * widget.tileSize, ), ), ), ), Center(child: indicator), ])
Once this was done, I just had to place the tiles in a
Correct position indicators
MaybeShowIndicator determines if it should show and eventually animate our indicator or not.
It should show the indicator if it's on a tile that is not in its correct position.
It should appear/disappear with an animation if:
- it moved from its correct position to an incorrect position or the reverse,
- the user asked to show/hide it.
I use the
didUpdateWidget() to check if the user changed the
showIndicator setting and I animate the appearance or disappearance of the indicator accordingly.
It should also animate the rotation of the indicator if the angle between its current position and its correct position has changed.
Instead of taking an approach where the AI would find the best or one of the best set of moves to solve the puzzle by trying a lot of moves, I decided to use a more human approach that I found on wikiHow.
Thanks to this, the player might learn the AI technique by looking at how it solves the puzzle. This method is not the best in terms of moves or time it takes to complete the puzzle so the player might still beat the AI score which is good for the ego. 😎
To make this feature, I used unit tests to iterate quickly on the solve feature and make sure that everything kept working after changes.
I used the
LayoutBuilder a lot to determine my Widgets maxWidth and maxHeight and made their children depend on it. Font sizes are based on these measures.
If the screen size reaches a minimal width and height I scale the whole interface instead. This way, the UI keeps looking well on almost any screen size. I made a specific widget for the scale feature:
FitOrScaleWidget that again also uses
ZPuzzle works well on Mobile, Desktop and Web.
Mobile platform has one feature that others don't have: it can use the device's gyroscope to move the tiles and see the 3D effect in motion.
Desktop support has been well tested on MacOS since I own a Mac. However, I could not test it on Windows and Linux since I didn't have devices set with a development environment yet. These platforms should work out of the box but I didn't test them so I didn't include them in the project.
Working with the Web platform was the most painful point for me in this project. I expected it to work similarly to the Desktop platform, but instead I ran into several performance problems which cost me a lot of time.
The use of a lot of animations and/or a lot of widgets made the app become unresponsive. Even displaying a simple Gif could lead to unresponsiveness without much logs. It seemed to be (at least partially) related to an issue on the Skia engine.
As a workaround, I finally disabled the use of my package
ZWidget on the Web since it draws a lot of widgets and uses shadows instead where applicable. My tests ran well after that. A later fix to the Skia engine might allow me to enable
ZWidget again on the Web.
In the meantime, ZPuzzle looks better on Desktop and Mobile than on Web.
The Web platform has other problems: for instance, we can't use
Isolates. I also had to use the CanvasKit renderer to prevent issues with the HTML renderer and general performances are lower than on other platforms.
Despite the issues on the Web, I was quite happy and proud to be able to make one app for every platform with only occasional changes for each. I will definitively consider building more apps for Desktop and Web.
Flutter handles the use of the mouse in addition to touch controls with the
InkWell widget. It just worked out of the box which is really nice!
I added the use of the arrow keys to move the tiles on platforms that support it (mostly Desktop and Web). The process was quite easy and it ran well quickly.
I added a gyroscope feature to be able to rotate the board and see the 3D tiles. However, I am not fully satisfied with this feature, especially with getting the current x, y and z values of the gyroscope. More work needs to be done to improve it.
Accomplishments that I'm proud of
The package ZWidget that I developed for this hackathon is also my first package. It was really nice to learn how to do it and I find the result pretty well looking. It still has room for improvements, but for simple cases it works well!
My first attempt for the tiles was to use a simple
Image as a background. This was not very original but now that I can use almost any
Widget instead I am pretty proud!
I also included an AI to solve the puzzle, inspired by an article on wikiHow. It has not been trivial to do since I had to translate the method into an algorithm that works all the time, but unit tests helped me to do it faster and double check it works well. During my test, I ran the tests thousands of times on randomly generated puzzles to verify it works every time.
Overall, ZPuzzle includes several features that make it quite complete.
Also, this is not specific to my project but it was really nice to be able to target Mobile, Desktop and Web with one codebase.
What I learned
This hackathon gave me the opportunity to improve my Flutter knowledge.
I experienced a lot with animations, responsive design in Flutter and the use of widgets I didn’t know yet like
I also made my first MacOS app thanks to Flutter! I would probably never have done one without this amazing framework. There were almost no adaptations to do compared to Mobile which is really nice.
I faced several challenges on the Web, especially with performances. I hope that this platform will be better supported but it is still nice for some projects and I already plan to use it for one of my own.
Iterating on my AI logic through unit tests was also a very satisfying way of working. It was faster than what I did on my previous game made with Flutter and I wish I knew about it earlier!
I tried Rive but unfortunately my art skills are not as good as I would hope. I need to work more on it to be able to include amazing animations in my projects.
What's next for ZPuzzle
I will be looking for the improvements on Flutter Web to see if I can achieve the same results on this platform than on MacOS.
I didn't have the time to finish the Rive implementation. I planned to be able to use a Rive animation as a background but I didn't finish it. I need to use a single
RiveAnimationController to sync the tiles animations. I explored the idea of importing Rive files directly from the file picker but it was not ready for the deadline. I may work on it later.
Some components I used might have a place in their own packages, like
FitOrScaleWidget, others might fit well in a quick tutorial. Depending on feedback, I might make them.
ZWidget package needs improvements in several areas.
ZWidget constructor can be improved as well as the README file. There is probably a better way of rendering a 3D effect than this package using maths but I could not do it. The community might get inspiration and make something great in this area and I would be happy to help.
Log in or sign up for Devpost to join the conversation.