I made a bad game
I made a bad game! I wanted to test the water on making a video game, but I knew that if I tried to make a Good game, despite all advice on “keeping it small” and “descoping”, I would just never finish it in pursuit of making it Good. So I gave myself the freedom of making something bad.
I’ll stop calling it bad, because that can get tiring - but just so you can see, here is a video of the end-to-end playthrough.
Okay - so let’s review the experience.
What did I set out to do vs. what I did do
I had in my mind from the get go, a platformer that relied on a single mechanic: teleporting. Originally I had this idea of an open world, where the player character (called a Mote) slowly encounters and gains “Motettes” which are charges for teleports. There would be 16 Motettes in total, over roughly four different areas. Each area would be soft blocked by a puzzle or challenge that would require a certain number of charges to get past. At it’s simplest you can put a path higher than the player can reach, until they can teleport high enough to reach that path. A more complicated variant is requiring the player to dodge/react to a certain number of hazards in a row, thus requiring a certain number of charges.
I had fun experimenting with different tilesets, different puzzles, even giving each little motette a personality of sorts. But... hand in hand, as I realized just how much work would be required to flesh everything out in a way that felt conceivably fun - I also started to lose motivation on the overall project (which I really wanted to reach “finished”). As I got demotivated, I ended up getting more and more side tracked - experimenting with custom lighting shaders (including building the engine from source), and playing with gdextensions so I could have realtime audio generators.
Once I realized what was happening, I reconfigured. I changed the format entirely, motettes were dropped in favor of infinite teleports. The open world was replaced with “rooms”, with a goal of having eight in total. From there, I don’t think there is any additional scope in the final product.
What are my general reflections?
Sounds silly to say, but making a game... is hard! I don’t think I necessarily had disillusions from the outset as to the difficulty, though I think what surprised me was the raw volume of work needed. Any single task might not be that big of a deal, but the volume is a killer.
For me, programming is already in my wheel house. Especially with Godot, I was pleased and delighted with how quickly a prototype could be whipped up. The art, sound and music aren’t in my wheel house - but I can cobble something together. The level design though felt completely foreign to me, and required more research than I was expecting to understand how people go about putting together levels that feel challenging and fun. Ultimately what worked to get me past the feeling of blank canvas syndrome, was to run through an exercise of putting together “throw away” levels based around a single pattern that I wanted to explore. I did end up throwing away a lot of those levels, but the patterns - I kept. I was also delighted to find that as I was trying to play a new design, I would often find unintended solutions, and side effects which I would further experiment with in another level.
It was also a good time! It feels good to make something, even if that thing is bad (yes I know I said I wouldn’t say it again). Early on, I added a musical element. Initially it started as just a sound effect for when you would teleport, but then trialed making it slightly more melodic to make it less irritating to hear. It was a really nice feeling when I found myself just teleporting around in a blank level because it was fun to hear where the melody would go next. I got that same warm fuzzy feeling when adding in music later: the music is tuned to the teleport sound effects, and though not the most revolutionary thing in generative music, was still delightful to me.
What are my thoughts on Godot?
So Godot was a pretty big part of the overall experience. There were ups and downs, the ups I think I can safely ascribe to Godot. The downs, though are variable - ranging from “I don’t know how other engines do this, to know whether this is status-quo or not” through to “I am learning a lot right now, and this challenge is more a function of Godot not matching my immediate mental model” through to “yeah, this seems like a straight bug”.
The goods first.
Stuff works as you would expect it to, and the documentation is pretty darned good. It really didn’t require me learning a whole bunch of new concepts before I was creating things that I had visualized. GDScript is a relief in terms of how quick it is to get an idea in code. I am certainly not using any AI for the project, so it was delightful to be able to guess “oh maybe I can do it like this” (filtering arrays with lambdas was one example), and it works as expected without reading docs at all.
It is easy to extend. Now this was a path I probably shouldn’t have gone down, if I wanted to get to done, but I don’t think I necessarily regret it either... Initially, I was interested in having real time sound synthesis. I especially wanted to vary the timbre of sounds dynamically in response to events within the game. Taking an open source project, really didn’t take that long to turn into a gdextension. There were some rough edges working with gdextension (user experience as well as documentation), but not enough to have forced me to give up. It probably took ~4h in total to get a working version including an extension to make it polyphonic.
Separately, one of the challenges I was having - was getting multiple lights blending as I wanted them. There are several live discussions about this on the Godot issue tracker, and several proposed solutions. I was able to pull down one of the proposed solutions, rebase it to the latest version, fix the bugs and build Godot from source, within an hour to make that feature available for me. Though the path was somewhat cleared for me, I still like to think that the speed I was able to put that together, generally bodes well for the community. My meager contribution is up here.
Now, the fact that I don’t have so many “goods” to list out, shouldn’t be interpreted as a bad thing at all. First off, I don’t have a great baseline to compare to other game engines. Then separately, it feels more indicative of “when it works, it gets out of the way and there isn’t as much to say”.
But now for the bads... Most of, if not all of the bads don’t feel like fundamental issues with Godot, they feel like quirks, or rough edges. Also, I am fully prepared that some of these might be me problem, not a Godot problem. In no particular order:
Getting collision information was often un-intuitive. I think I only ran into this with move_and_slide, where I ended up realizing that StaticBody or even RigidBodys aren’t going to technically collide with a CharacterBody if it is using move_and_slide, because it resolves collisions for you. I ended up using Area2D to detect whether an object was probably touching, but I would have loved to do get_colliding_bodies on a RigidBody and see my CharacterBody.
PathFollow2D doesn’t work as you would expect for PhysicsBodys. It seems like this is a known bug, but with a solution that is non-trivial. The hack around right now, is to either manually do
$my_node.global_position = $PathFollow2D.global_positionor use RemoteTransforms.
2DLighting modes are missing a clamped mode or equivalent! This was briefly mentioned above. The result is that if you have two lights overlapping one another, you either get some weird interactions with mix mode, or blown out lights with add mode.
Signals can be connected to any ol’ callable, but they will only call that callable if they have the right signature.
signal foo
func _ready():
foo.connect(func (): print("foo!"))
while true:
await create_timer(0.1).timeout
foo.emit(Time.get_ticks_msec()) # it will never get to the handler!I don’t know if this is intentional or not, but feels like it should emit a console warning perhaps?
Finally - node management is tricky! I initially started off with everything in a single scene and quickly realized the folly of that. I shifted over to breaking off chunks into scenes, but then found it difficult to have scenes do some light interaction with one another (e.g. referencing a node in another scene). Editable children almost solves for this, but is error prone. I think either this PR or this one, would suffice for my own mental model…
And that is it! The gripes feel minor in total. I absolutely would use Godot again, and am excited to see the continued improvement.
What would I do differently next time?
Finally, to close out this piece - what would I do differently next time?
Well, if I were to do it again, I would probably be aiming to make a game to release commercially. I know that my current experience is far far from being ready for a commercial release, nor do I expect my first release to be a commercial success. But everything written below is with that in mind.
Top of the list, is do it with a team. If you want to go fast, go alone, if you want to go far go together. It can be fun making something solo, but it is far more fun to me to make something with others. Creatively, I think there is so much more possible when you are cross pollinating, not to mention the quality improvements across each discipline.
Secondly - I would phase the project differently. This time, I did a lot of exploration up front, which also included exploring visual styles. I think I went too deep on the wrong components, and not deep enough on others. Loosely, I am thinking something like:
Mechanics exploration + iteration - find something that feels fun, and has enough depth to be expanded upon into a full game
Polish a tiny slice - get a sense of the visual and artistic style you want, because that is going to inform and dictate how much work there is going to be overall
Level design - block everything out, placeholder art everywhere. Don’t worry about the tuning of the levels yet, volume is better than quality at this point and can be edited down later
Art + music pass - get first draft content in, to give the world atmosphere
Polish, polish, polish
Thirdly - I would get some architectural components in place earlier on, e.g. level loading / resetting, UIs, global game state. What I found, was that once I realized I needed an architectural thing, or an overarching mechanic - retrofitting it later meant a lot of going back and redoing how I had set up other components.
Finally, as a way of signing off - the source code is available here!
👋