Welcome to the 3rd episode of Games in Flutter — this time it’s all about walls. In case you now think: “Wait, walls?” have a look at the previous parts to get up to speed:
Games in Flutter — Flame & Box2D Part 1
I really like video games and I like to create and develop software. So I set down and checkout Flutter for this. 2D &…
Games in Flutter — Flame & Box2D Part 2
Welcome to the second round of Games in Flutter, all about physics movements and how to get the ball rolling.
As always the code can be found on GitHub, the code could be ahead of this post. During writing, the game should look like this after starting:
You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or…
Let’s build a wall
To get a maze on the screen we have to first work on the basics. The last article covered the player/ball and explained a lot about physics and how to set up objects the right way. I will not explain all parts here again just point out the important things for our walls.
Each wall needs the same physic objects as the ball, but we will use a different shape in this case. Keep in mind a shape is the description of the body appearance. For our wall we will make use of the class PolygonShape, this shape is build with a list of points which describe the shape. The goal is to have a single Wall class that can handle horizontal and vertical walls. To describe a wall we will need in total 4 points (or Vector2 objects), two of them are provided from the caller via the constructor (Vector2 startPoint, Vector2 endPoint) and the other two are calculated. With the list of vectors we can not only describe the shape for Box2D but we can also create a Path object to draw the walls (later more about this).
https://email@example.com/games-in-flutter-flame-box2d-part-3-dc36e90ca1abShapes and path — measure our wall
Inside the method _buildShapVectorList the shape vectors are generated, all walls will start at (0,0). If you now ask yourself “Wait, why don’t we start at the startPoint vector?!” the answer is actually very simple: the shape will start at the point where our body object is set to and this is in fact the startPoint vector provided. With one point covered and three left, the next one depends on the orientation of the wall. If the start point Y is smaller than the end Y we have a vertical wall, if the X point is smaller, it’s a horizontal wall.
Depending on the wall type, we either use the X/Y value to calculate the end point. The end point will always be on the same line as the start point. The fourth point however will be moved by the wallWidth to generate the wall corner. Our last point will go back to the start (either X or Y will be 0) but will stay inline with the fourth point.
The tricky part in the above drawing is getting the right end point; the one that was sent in, is “wrong” as we moved our drawing to (0/0) it no longer fits. We need to calculate the actual distance from start to end and use this as our new endpoint, this is stored either in endX or endY depending on the wall type.
The above sounds trivial but it took me a good time to figure out why my maze went wrong, sometimes the small things can be the hardest to find and understand.
This four points are returned as a list (of course they are scaled by our scaling factor to have the right size) and used to build up the shape using the set method of the PolygonShape class. Contra to our Ball class our Wall is a STATIC body type, it will not move but still interact with other elements. I mentioned before that we can reuse our four element vector list to generate a path and this is done with a simple call to Darts (by the way super handy) map method. Darts Path class needs a list of Offsets, so we simple map our vectors to this class.
Render the wall
Drawing our walls is super easy (again thanks to Dart), with the Path we created above and our Paint object we can call the build in function drawPath() offered by the canvas object. Same as for the Ball, before we draw something, we move the canvas to the startPoint (stored in the body) of the wall using the translate method.
The maze — part by part
To generate the maze I use the theseus package:
theseus | Dart Package
Theseus port https://github.com/jamis/theseus version 1.0.2 Theseus is a library for generating and solving mazes. It…
Don’t worry about the bad rating, most points will not interrupt us and are more related to documentation and code cleaning. To understand how this code works I generated a simple console application with Dart:
If you want to give it a try, just create a new console app and add theseus to your pubspec.yaml file. The code will generate the following output:
First part of the output is the complete maze as ASCII art, followed by the first line cell by cell. Each cell is using a bit field to indicate if a wall is present or not, this can be checked by using boolean logic. If a given bit is set, this means a passage exists, leading away from the cell, no wall. The below image shows the related bit and side of the cell.
The library we use here offers far more than the simple maze we are using, it’s a great project and I can just encourage everybody to play around with it for a while.
Walls! All we want are walls!
So we know the following:
- How to generate a maze
- How to “read” a maze cell by cell
- How to draw walls
That’s more or less all we need to draw the maze, welcome to our new friend called “MazeBuilder”. This class will make use of all our knowledge and generate a wall based maze for us.
The maze builder will generate a maze that fits our screen and loops over each cell to generate the walls needed for it. Each cell has up to 3 walls and of course space between them to allow our ball passing by. All magic is applied inside the generateMaze and generateCell methods.
First we generate the maze as shown in the previous section and add our first wall to close the entrance of the maze. All walls are stored in a List<Wall> the first wall will be from (0/0) to (0/ 0 + cellSize.height). As we learned before a different Y value will result in a vertical wall.
All other walls will be generated inside the for loops, going through each cell, line by line, from the top left corner to the bottom right.
The cells itself will make use of the bit field discussed before and render a wall if required — the starting point can be easily calculated by using the X and Y value times the cell width/height. Each cell will have the same size.
Thanks to static types we can create walls that interact with our ball but will not move on impact. Using the already existing library theseus saved a lot of time and allowed us to focus on our game itself. Thanks to bit fields it’s easy to check what a maze cell should look like, every required information can be found in a single integer value.
Due to scaling and transformation of our canvas we now know that our game has actually two layers — one that calculates values and one that transforms them to match the real values on the screen.
Of course, we could stop here with our game but there is still some parts left to do. The next post will take care of game states (check the repo, they are in already) to add elements like menus and winning conditions and high scores.
Games in Flutter — Flame & Box2D Final
Last one in a series, escape the maze and let’s finish our Flutter game. Everything is a widget even our game, super…
- Bit fields https://en.wikipedia.org/wiki/Bit_field
- Maze library https://pub.dev/packages/theseus
- Drawing a polygon using a path https://api.flutter.dev/flutter/dart-ui/Canvas/drawPath.html
- PolygonShape (C++ code)https://www.iforce2d.net/b2dtut/fixtures