Yes, you read the title right. I’ve created and shipped a game made purely in Dart and Flutter in about two months, carrying it out as a late-night coding project. Are you interested in how I made it happen? Then let’s continue.
Usually, when we think about game development, crazy things like C, C++, game engines etc. pop into our minds. So why did I create it in Flutter instead of the usual methods? The answer is simple. I already know Flutter and Dart, the game isn’t some advanced 3D Crysis look-a-like, and it was a great opportunity to improve at Flutter.
Let’s get to a little bit of the back story first. One day I decided I wanted to revisit a game I played when I was about 8-10 years old. The game was a turn-based space gravity simulation. Each player had a ship, and the goal was to hit the other player’s ships. Pretty simple right? The catch was that there were planets with some weight and gravitational pull along the projectile to the other ships. So the goal was to hit the other ships, dodging the planets and using their gravity to slingshot the projectile. The problem is that the game is really old, and I couldn’t find an executable that wasn’t a demo and worked on modern systems. So I decided to reimplement the game myself and make it more modern.
After quickly setting up the new Flutter project, a realization dawned upon me. How do I bring about the simulation of gravity? After reading a few Wiki articles, it turned out that it’s not that difficult. The problem was the granularity of the calculation. It needed to be done on a regular smartphone rather than a NASA supercomputer, and it needed to be done quickly. So many optimisations were done (even days before the game’s release).
The devised method for the gravity calculation is to combine the vector of each planet every single step of the calculation to get the final vector of the projectile. So in step one, we take the position and velocity of the projectile and combine them with the positions and vectors of the planets. The vectors of the planets are their relative position to the projectile and mass (gravitational pull). So after this, we have the projectile’s next position, which can start all over again.
This helped a lot in understanding how to do it: https://physics.stackexchange.com/questions/103411/calculate-trajectory-of-projectile-with-gravity-from-disk-planets-in-2d
And here’s some pseudo code for the planets segment. You also need to consider if the projectile collides with a planet, asteroid, or another player’s ship.
When it came to UI, I wanted it to be as clean as possible. This meant it began as Dart classes completely separate from any UI. So yeah, it can eventually be played as a CLI game if a new CLI renderer is implemented.
But let’s be honest, how can you be sure the gravity simulation is working correctly if you just know the coordinates and size of the ships and the coordinates, mass, and radius of the planets? I wanted to visualize the calculations at this point. So here are some early screenshots, just passed as a state of Widget in Flutter. At this point, every single element is a Flutter widget.
The projectile lines are just thousands of 1×1 pixel SizedBox 🤦♂️ The problem with this approach is that even with how fast Flutter is, the rendering of millions of lines by arranging SizedBoxes is just wrong. So I started re-implementing the rendering using Flutter’s CustomPainter.
But yeah, it’s still kind of ugly. Just lines, boxes, and circles. We need some pretty graphics. I decided to go with today’s popular and sometimes hated pixel art. For the planets, I used Pixel Planet Generator. The first problem with this was that I wanted the planets to be animated. The second was the scaling. I wanted pixelated graphics but for each pixel to be the same size and independent of the size/scale of the planet. So how did I solve it? There were many attempts at this. The first try was to just load a library to play animated gifs in Flutter. The problem was with the scaling of pixels. So to make a long story short, the first attempt was to load each animation frame as a separate file. That failed due to a huge and unnecessary overhead as there were thousands of files in the end. The second working approach was to create basically a sprite animation.
It contains all the frames of the planet rotation, 360 frames to be exact. It works by cropping part of the image each time a frame of animation changes. So you have just one bitmap in memory and iterate through it. There was also another problem. Although you can create thousands of pixel-wide images, not all mobile devices / GPUs can support it. But Flutter won’t tell you that and silently resizes the bitmap when loading the assets. So Google, please fix this 😁. That was a significant struggle because the animations are based on basically just cropping the correct frame from a huge image and because the resolution isn’t predictable and it won’t tell you the correct size before you cut it, I needed to create a multiple-line sprite animation.
Many of our developers have game development as a side project…
Are you going to be the next one?
Okay, so now that we have animations sorted, what about parallel pixel sizes? That’s kind of tricky. Before each Space (game level) in the game is available to the player, there’s a pre-renderer of the planet animations in correct sizes. It basically plays the sprite animation and resizes each frame to the correct sprite size.
To illustrate the look I wanted, here are some images. The first one is the desired look, different planet size but same pixel size. The second is basically just scaling the planet images resulting in different pixel sizes.
Here is the generation of one frame. Remember it needs to be done for each planet’s variation (skin), frame and size.
And here we go! First screenshot with planets!
To animate the projectile lines is a different beast. The calculation depends on two inputs from the user: angle and velocity, as well as on each planet. So with each one, the calculation becomes more CPU-heavy. We can calculate each step of the simulation in the main thread, but the speed of the calculation depends on the combination of planets, space, size, etc., so we need to do it asynchronously. This is done using Dart’s compute, to not block the UI. We need to animate the projectile after the computation is completed, which is done using another CustomPainter written especially for drawing a curve animated from one end to the other. You might think that it could be done using streams. It could, but again the problem is the unpredictable calculation time and needs to have the same animation starting speed across all possible combinations.
The game offers three types of UI: mobile, tablet, and desktop.
Mobile was the most difficult to develop since I needed to make some compromises to fit everything on the screen, or make it only visible when needed, like the custom keyboard widget when the player is inputting the degrees and velocity.
Regular UI on iPad
Table-Top UI will draw the controls for each player facing different directions, ideal for playing on a table, so you don’t need to pass the device to other players.
The playable Space itself is the same on every single platform or screen size. You can zoom or let it adjust automatically on each turn. For that, I’m using Flutter’s InteractiveViewer.
It should rather be named
DCO - Dumb Cheating Oponent, AI is too flashy for it. The AI does basically the same thing the player does but dumber. It starts by inputting a random angle and velocity within meaningful limits, saves the result of the shot, and then keeps doing this until it has enough samples to do something intelligent. The first three or so shots the AI makes are visible to the player as regular turns. After this, the AI had invisible turns before its real turn. Each invisible turn is based on the best shot from its memory, which is determined by having any point of the projectile trajectory as close as possible to any opponent. The AI then slightly modifies its input values for that shot and waits for the result. The AI player never executes more than five invisible shots.
So there you have it. It’s not that smart, but it gets the job done.
Maybe in the future, I’ll try to carry out some real machine learning and plug it into the game, we’ll see.
Sounds and Music
Sounds simple right? Just play some sound, and that’s it. To be honest, this aspect is really inadequate in Flutter. There are libraries for playing sound on Android and iOS, others for macOS, and others yet for Linux or Windows. Since GG is supposed to be cross-platform, I needed a library that would handle all of these platforms. Sadly, no luck there. So I ended up with three libraries. Each behaves differently, initializes differently etc. To make my life easier, I created a class which I called
ProxyAudioPlayer to handle some common actions. Here are some methods of this class:
Here are the three libraries I stuck with:
Just a side note, here’s the complete list of dependencies used. Yeah, pretty basic stuff.
You might wonder how I stayed sane working a regular developer job, doing this in the evenings and even into the late nights. To be honest, I’m asking myself the same question. It was mainly because of three things. At the time I started this project, I was after a breakup, living in a new apartment, and it was the middle of the second covid winter. So this was my way of staying sane. It was also nice to finally have my own project, which was reasonably scaled to be completed by one person in a relatively short period of time. Lastly, because my friends helped me with some basic testing and feedback, every time I released a new version on TestFlight or AppDistribution, I had some feedback to work with.
As mentioned previously, maybe I’ll try to plug in real machine learning for AI. It would also be nice to have a centralized way to share the levels created in the built-in level editor.
For example, sharing of the levels by using a QR code. Online or LAN multiplayer. As you can see, many compelling possibilities exist for expanding the project. But as of now, I’ve finished what I wanted to do the most, so it all depends on how interesting the game is to others.
Would I choose Flutter again for a game project? It depends. I needed to overcome a lot of obstacles, but I had everything in my own hands. That’s one thing I like about developing “from scratch”, without any game engine. I wouldn’t choose Flutter to develop the next triple-A game, but it’s ideal for casual, turn-based, app-like games. I’m actually working on another game already. It’s an entirely different beast, but Flutter is awesome for it. So stay tuned!
P.S. You can get the game here: https://gravitygame.space