Games in Flutter — Flame & Box2D Part 3

Christian Muehle
Dec 15, 2019 · 7 min read
Photo by Duncan Kidd on Unsplash

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:

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:

Start at the top left, finish at the bottom right

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://gph.is/1gCuUIh

https://medium.com/@c.muehle18/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.

Horizontal and vertical lines, showing how to know what wall it should be (highlighted)

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.

How the wall is made, sorry for the bad drawing :)

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:

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.

Maze cell with related bit written as 2^n

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.

Conclusion

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.

Up next

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.

Resources

Flutter Community

Articles and Stories from the Flutter Community

Christian Muehle

Written by

Software developer, hobby photograph. Working in the marine industry as a team lead with an international development team

Flutter Community

Articles and Stories from the Flutter Community

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade