Slide Puzzle: Escape of Cao Cao
This is a puzzle game made on live-stream, demonstrating various animation techniques in Flutter with just Dart code - no dependencies or asset files. It can target mobile, desktop, and the web from a single code base.
The goal of the game is to move "the biggest puzzle piece (Cao Cao)" to "the exit" at the bottom, in as few steps as possible.
A playable web version is available. For the best experience, you can also try out native desktop or mobile apps.
Releases
v1.2.1 (2022-03-14)
- Added an animation when the game launches.
v1.2.0 (2022-03-12)
- Added a hint button.
- Minor changes to the reset button.
The new hint feature spawns an isolate
to find an optimal solution for the current state using
Breadth-First Search. Once the result comes back, an OverlayEntry
is created precisely on top of
the hinted piece, with the help of a CompositedTransformFollower
widget and LayerLink
s embedded
in the pieces.
v1.1.0 (2022-03-06)
- Added more levels.
- Added a new 3D animation during level transitions.
- Added an option to hide decorative texts on puzzle pieces.
- Various minor UI enhancements and performance improvements.
This is my first time trying to simulate 3D objects without 3rd party tools or plugins in Flutter.
It's made using Transform
widgets to translate and rotate 6 rectangles to correct places to form a
cuboid.
Further optimizations were made so only up to 3 faces (visible to the viewers) are rendered at a
time, and a glare effect was added by changing stops
on a LinearGradient
. This formed the basis
for a new transition animation when a level is completed.
I've published a video tutorial on this topic, watch it in English | Chinese.
v1.0.1 (2022-02-26)
- Added a simple tutorial screen.
- Fixed a bug where the web version was not rendering correctly on mobile browsers.
The tutorial screen is a responsive dialog made with LayoutBuilder
and ConstrainedBox
, and
decorated by CupertinoPopupSurface
. It also includes a WillPopScope
to allow Android users to
dismiss it with the back button.
It provides an option to hide decorative texts (v1.1.0) in case they don't render properly, e.g. when there are no international fonts installed.
When building for web, make sure to use canvaskit
renderer for better experience across devices.
You can do so with flutter build web --web-renderer canvaskit
command. Otherwise, the puzzle might
not render correctly on mobile browsers. I've
created an issue for this and looks like the
Flutter team is already on it.
v1.0.0 (2022-02-22)
- Initial release for all platforms.
The initial version of the app was created as "live coding" stream sessions. With discussions and voting polls on design choices such as the color palette, together we built the game in 12 live-stream sessions.
The support for more platforms were added after the live stream ended.
The video overview (see below) was released at the same time as the initial version, so new features mentioned above are not included in the video.
Video Overview
A short video (less than 3 min) is available, providing a brief overview of version 1.0.0.
You can choose to watch the video in English | Chinese.
At a high level, the app can be divided into 3 parts: the background layer, the game board, and the puzzle pieces. Each puzzle piece also comes with a pair of interlocking attachments and a shadow for some added depth. These components are stacked as 3 different layers, to make sure they always appear in the correct order when being moved.
The color palette is configured as an InheritedWidget
so its values can be modified in one place,
and easily accessed elsewhere in the widget tree.
The puzzle pieces are made from AnimatedPositioned
. For the duration, I pass in zero if the window
size is different from before. This is to skip the unwanted animation when the app is being resized.
And for the curve, I use EaseOut
to slow them down naturally.
To handle user input, I added GestureDetector
. I used the onPanUpdate
event instead of
the onPanEnd
event, to reduce input lag.
From the picture above you can see that the user has not finished the action (finger is still
touching the screen), but with onPanUpdate
event, the game has already responded.
For the reset button, I added a MouseRegion
widget to make it glow when being hovered.
The step counter on the side is another example of utilizing implicit animations in Flutter. It’s
also wrapped in a ValueListenableBuilder
, so it can always keep up with the steps.
I’ve published the counter as a package on pub
and made a video explaining how it was made. If you
like this animation, you can import it to your own project.
The game board is mostly a Container
with some decorations and clipping. I also used
a BackdropFilter
here to blur everything behind it, and a ShaderMask
to create a beam effect.
The beam created by ShaderMask
is an explicit animation with its controller set to repeat, so it
can keep dancing around in the game.
Similarly, the decorative texts puzzle pieces are also animated to have a subtle glare effect.
Lastly, I used a CustomPaint
for the app background. It efficiently draws colorful rectangles
every frame and runs well even on low-end devices.
What's Next
- Resolve web rendering issue for mobile browsers
- Add a tutorial screen to explain the game
- Add more levels to the game
- Add an automatic solver or a hint system
- Allow users to select levels
- Add keyboard support
- Further improve swiping gestures
Extra Resources
I've been making lots of video tutorials (mostly in Chinese) on Flutter, covering a wide range of topics and popular questions from viewers. If you are interested, please follow me on "bilibili" so you won't miss the next live stream or video from me.
Animations
I've made a total of 19 video tutorials on Flutter Animations, divided into 3 sections: implicit, explicit and others.
Implicit Animations
- Get moving with just 2 lines of code
- Smooth transitioning between different widgets
- Curves, and more animation widgets
- DIY with a TweenAnimationBuilder
- Case study: make a flip counter
- Case study: flip counter continued
Explicit Animations
- A repeating animation
- What is an AnimationController
- Curves and Tween
- Staggered animations with intervals
- DIY with an AnimatedBuilder
- Case study: coordinating multiple animations
- Case study: multiple animation controllers
Other types of Animations
- Under the hood: animations and tickers
- Hero animations
- CustomPaint: do you wanna build a snowman
- Animate with Rive/Flare
- Bonus: create an asset with Rive tool
Keys
I've made 7 video tutorials on keys, covering basic concepts such as widgets and elements, 3 types of local keys, 2 different uses of global keys, and more.
- Spooky stuff when you forget to use keys
- Widgets, Elements and their States
- Three types of LocalKeys
- Two purposes of GlobalKeys
- Case study: make a color sorting game
- Case study: use LocalKey in the game
- Case study: use GlobalKey in the game
Scrollable
- ListView and lazy loading
- Deep dive into the ListView widget
- RefreshIndicator and NotificationListener
- Swipe away with the Dismissible widget
- Case study: a GitHub repo browser
- GridView widget
- Even more scrollable widgets
Asynchronous
- Event loop, queues, and microtasks
- Deep dive into the Future type
- FutureBuilder widget
- Stream and StreamBuilder widget
- Case study: generate stream from user actions
- Case study: listen to our event stream
- Case study: a good use for StreamTransformer
Layout
- Constraints, size, and positions
- LayoutBuilder and ConstraintBox
- Flex: Flexible and non-flexible
- Stack: Positioned and non-positioned
- What is a Container, really
- CustomMultiChildLayout widget
- Let's make a RenderObject
Slivers
- Welcome to the world of Slivers
- All sorts of sliver lists
- SliverAppBar widget
- More sliver widgets and SliverLayoutBuilder
- Case study: convert a ListView into a sliver
- Case study: SliverPersistentHeader
- Case study: Design a page with a SliverAppBar
Coding challenge and follow-ups
(Sometimes I post questions for my viewers and then do follow-up videos to discuss all the creative solutions I receive, so we can learn from each other.)
- Pinch-to-zoom gallery with smooth transitions
- A button that counts down with its border
- Adaptive watermark overlay with FittedBox
- Different ways to implement Hollowed Text
- Ink, InkWell, and Material
- Creative ways to achieve diagonal layout
- Adaptive banner made with Pythagorean Theorem
- The new and the old material buttons
- Different ways to implement the same animation
- Different ways to detect screen rotation
Other popular topics
- What is BuildContext?!
- Flutter Web: common problems and solutions
- What is SOUND null safety?
- Some super useful widgets in Flutter
- Some less known widgets in Flutter
- Flutter 2.0 is here!
- WillPopScope and iOS swiping gesture
- Weird tricks about Flutter Hot Reload
- Hotkeys in Android Studio
- Publish a package: animated flip counter
- Publish a package: interactive chart