Dear reader, this post was originally written as a part of course work for a Game Project Course in university. As such it makes references to the course and its parameters, which likely isn't relevant to you. However, I have decided to publish this on the blog because I was asked to share it and I also think it makes for good content, but I don't want to rewrite the entire thing purely out of laziness.
Incoming! was a game I wrote for this particular course. It was also an exercise for me to learn more about Godot engine, and some of those learnings are documented here.
You can download the game from here: https://samsai.itch.io/incoming
I've also made the project files available at https://codeberg.org/Samsai/incoming
Coming up with the idea
I had a vague idea of what I wanted to do on the Game Project Course already before I signed up for the course. During the Game Engine Architecture course it was pointed out in the material that the GPC had added Godot Engine on its list of officially sanctioned game engines. I've played around with Godot Engine here and there on my own and I figured working with it on a project with actual deadlines would be a good way to commit to learning it more properly.
The idea for the game concept I had also come up with previously, and it was sitting in my unimplemented projects folder. When I come up with a project I think would be fun to create, I add a short description of it into a text file so that I can come back to them in the future. This course provided and opportunity to implement one of them, and the idea for a CIWS shoot-em up seemed like something with a reasonable scope for a project like this without too many unknowns.
Essentially the only thing that I had to come up with for the course (in addition to all the assets and code), was the name. In this department the outcome wasn't all that creative, but it is at least descriptive and short.
I've made a couple of small things in Godot in the past, so I had a grasp of the basics. However, developing Incoming! allowed me to learn more about the various systems that Godot provides and the previous projects I'd done were largely just hacked together without much thought about maintainability and good practices.
All this meant that I had to spend a while looking at the docs to learn how the engine developers intend the engine to be used in various situations. Fortunately the Godot Engine documentation is fantastic and both the API reference and the manual turned out to be accurate and extremely helpful for writing the game. The manual provides many very handy examples, and often I was able to directly apply much of the tutorials to my project without having to alter too many things.
As an example, the spawning system Incoming! uses a method I wouldn't have thought of without reading the Godot Engine tutorial on making your first engine. Both Incoming! and the example game use Godot's pathfinding system to define spawn paths and then picking random points along that path to spawn enemies. This meant that I could very easily define a Spawner entity, give it a path along which it could spawn a specific type of an enemy and then apply a few timers and counters to control spawn rate and enemy number.
Another aspect of Godot I hadn't explored too much in the past was the signal/event system that Godot provides. The signaling system allowed me to decouple some entity interactions such that entities wouldn't need to know about each other's composition. The main interaction entities have in the case of Incoming! is relaying damage information, so in that sense I would have gotten away with just calling functions from other entities just fine, but using signals feels more clean.
Sometimes more art than code
One of the biggest slowdowns during the development process wasn't coming up with code or wiring together different entities. Instead an aspect that I ended up spending a lot of time on wasn't really software engineering related at all.
While coding is definitely an integral part of making a game, there is also a graphical aspect to the process. This means that you end up needing some art, even if temporary programmer art, during various parts of the process.
Now, I could have indeed decided to just use some basic programmer art to represent the different entities in the game and then pulled in pre-made public domain or Creative Commons art from OpenGameArt for example. The problem there was that I had a vision for how I wanted the game to look, and in the past when I've tried to find pre-made art for my projects that matches my vision, I've usually given up and ended up just drawing art myself. So, I decided to skip looking for sprites entirely and just draw them myself from the start.
This meant that some parts of development were blocked until I got at least some art together. The drawing itself didn't necessarily take that long and I was actually quite happy with how the visual side of things turned out, but the fact that implementing some part of the game would first require me drawing the art caused me to procrastinate on getting started every now and then. Having had a separate artist or a good set of pre-made art would have turned my focus more towards coding and I imagine it would have been easier to maintain momentum. In hindsight though, I think getting some practice in doing my own sprite work was beneficial to me, even if it isn't accounted for in the course grading.
Benefits and quirks of the Godot workflow
Most of the games I've written in the past have been written using more low-level frameworks, like SDL2 and that's quite different from how developing a game with Godot Engine works.
Godot Engine's vision is relatively graphical and centrally built on the node hierarchy both on the level of how scenes are constructed in the editor and how the native scripting language, GDScript, works. The benefit of this system is that it allows you to very quickly put stuff together. Godot Engine provides a number of very handy nodes, and with a Sprite, a KinematicBody2D and a CollisionShape you can get a playable character with just a few lines of script.
The Godot Engine animation and particle systems are also superb and I managed to pull off most of the visual flair of the game by just configuring some parameters in the particle emitters. Rocket trails, spawning clouds and explosions and fire were very quick to prototype and implement. The animation system I only managed to try very briefly for the intro text animation, but it seems very simple and is very well integrated with node properties, allowing you to easily change all sorts of parameters and keyframe them with easy interpolation.
However, this style of development also clashed with my norms and understanding of software development and I found that I ended up drifting towards patterns that I would consider code smells. The ease of creating node hierarchies for my entities meant that I often skipped architecting these hierarchies into potentially shareable components. Godot does support scene inheritance, so it would have been possible for me to define a shared behaviour for different enemies for example, since many of them just travel in a straight line from one side of the screen to the other. But in practice I mostly didn't do that, because I focused on making whole node trees and code sharing became an after-thought. I also found refactoring these hierarchies a bit clunky, which further pushed me towards copy-pasting code. In a more code-focused software development method, I think I would have realized the need to refactor much earlier and would have been better equipped to execute that refactor either through class hierarchies, interfaces or ECS-style component composition.
Another quirk of Godot Engine is particular to the GDScript scripting language. GDScript was originally with a vision of dynamic typing and a rather unusual modularization system in mind. It has since gained support for explicit typing, but that system doesn't seem to have been fully realized yet. I ran into an annoyance relatively early on, when I started implementing multiple weapon systems. My initial idea was for the player to be able to switch between the miniguns and the flak gun and thus I decided that representing the weapon selection would be best represented as an enum. But when it came time to read the weapon selection from other files I couldn't figure out how to do it. GDScript lacks traditional methods to import functions and variables from other files, instead relying on the node system to read and write to properties of another entity. I for the life of me couldn't figure out how you were supposed to move type information across the file boundaries and thus ended up ditching the idea of enums entirely.
This also makes me a bit suspicious about how well suited GDScript would be to bigger and more type-intensive code bases. At least I would feel more comfortable being able to split scripts into multiple files without having to store each file into the node tree or loading them as resource files.
Sometimes the biggest problems happen at the end
Ultimately most of my annoyances with Godot Engine were pretty minor and easy to work around and thanks to a pretty clear vision most of the development was a breeze. That is, until I ran into a very annoying issue when the submission deadline was just around the corner.
When I was getting the final submission together, I decided I'd package the project files and source code into a Git repository for the project to be checked easily. After committing the files I decided I'd make sure the project would open by pulling the files onto my laptop and testing Godot there.
Immediately when I had imported the project and opened it, Godot froze. I deleted the folder and tried again, another freeze. I started getting pretty worried, so I contacted some online developer friends and told them to try. Same results. This started a 24 hour bug hunt. I tried deleting various files, including cached files in the repository, deleting certain nodes and singletons but to no avail. Eventually I managed to narrow it down by making a project that didn't have the issue, but that didn't really help figure out the issue.
Eventually I was getting quite frustrated with the situation and I decided on a bit of a hail mary. Still assuming this was my fault somehow, a friend of mine who has a bit of an online presence among open source and Linux gaming crowds offered to send out a tweet requesting help from people more familiar with Godot Engine. This eventually got the attention of a Godot Engine developer, who in a matter of hours had identified the issue and connected it to a previously closed bug report, created a preliminary fix and assigned it for release for the next point release of Godot.
This means that my project has been immortalized as a bug reproduction case on the Godot Engine GitHub bug tracker: https://github.com/godotengine/godot/issues/42785#issuecomment-829329174.
In the end this bug meant that I had to ship the project with a known bug, but at least it wasn't my bug and further releases of Godot would open the project correctly. I was also mighty impressed with how quickly the bug was identified and fixed, even though the report came through unofficial channels.
I am incredibly happy with the outcome of this project. I feel like I managed to learn a lot about Godot Engine through this project and the resulting game is quite possibly my best work thus far in terms of game development. I am not a very artsy person, but I feel like I managed to pull off a cohesive and aesthetically pleasing game that implements a core gameplay concept in a way that works well and feels good.
That said, the core gameplay concept is quite simple and the game in its current state is definitely not very content rich. But I think it would be a passable jam game. I think the game could be extended pretty easily, merely adding more levels and potentially an infinite mode for chasing high scores would take the game further along.
I did ultimately also cut some ideas that I was playing around with. My original vision for the game included the possibility of a third weapon, an anti-aircraft missile that would need to lock on to a target and then track it. I was planning on adding a new enemy type as well: a plane that drops chaff and flares to cause the missiles to lose their lock. In the end I decided not to implement this because it would have taken additional time and I wasn't sure how I'd balance the missiles to make them both worthwhile but also not too powerful. A third weapon type would have also meant sacrificing the simple mouse-only gameplay, and I would have had to introduce keyboard buttons into the mix.
In the most optimistic version I would have even allowed the player to customize the weapons loadout on their ship. Technically speaking the functionality could be implemented very easily: each weapon system on the ship is already its own separately instanced scene and thus you could just as easily have 5 turrets as 3. Most of the work would have been in implementing a method for unlocking and placing down weapons and making sure that the unlocks were balanced. The biggest problem was that I only had two weapon types implemented and thus the upgrade system would have been a bit boring. With more time more weapon systems could have been implemented and that would have made a loadout system more sensible.
I might explore both of these ideas in a post-course update to the game if I decide to put it up on Itch.io for example, but in the context of this course these features were left out because I didn't feel there was adequate time to develop and integrate them well.