Godot Game Architecture Tool Kit

Aeris
The BKPT
Published in
4 min readMay 17, 2023
Screenshot of our jam game, The Gardener

I’ve crawled out of my game jam hole. The light of day is hard to bare. My sleep schedule is in shambles. But we do have a game to present!

Taadaaa. This time around, my team and I made a horror visual novel about the grief of losing a trans friend to violence and transphobia. Sad stuff. I want to share this story so more people can understand the pain transphobia causes. Or perhaps so others can find solace when reckoning with similar grief.

I hope you’ll give the game a shot!

With that preamble out of the way, we can jump into the meat of this blog post. For the last two jams, I’ve been rolling my own visual novel (VN) framework. Our team wants to build dynamic and unique layouts that break the assumptions of existing VN libraries. For example, we created a parallax stage to give each scene a slight 3D effect (akin to a pop up book) in our December jam. And in this jam, we went for a comic-book inspired look with image panels. Learned quite a bit through all of this so I figured I’d hit the breaks and write down some tips and tricks I’ve learned.

Information Hiding

This particular principle is something I’ve learned to appreciate more over time. I used to think of encapsulation as getters or setter wrapping access to class variables. That’s only one aspect of encapsulation. In the expanded definition, encapsulation hides complexity. It does so by subdividing your larger problem into smaller, much more manageable subproblems. The subproblems are much easier to reason about and thus make the overall programming experience smoother.

That’s nice and all, but let’s see it in practice. Here’s a few examples of how I applied this principle in the jam game.

Scene isolation

I designed each major scene (such as the title screen, credits screen, settings scene, etc.) to operate independently of each other. The scenes don’t know about each other and instead will announce when an event has happened via a centralized signal bus. Scenes connect to the global signals they care about and implement handlers. So a good ol’ reliable pub sub system. In Godot, I implemented the centralized signal bus using an autoload that defines all the global signals. The signals are named after the source scene + past tense verb of the event that happened.

I also only spawn one major scene at a time. I will despawn the previous scene on scene transition even if the user will navigate back to the previous scene later on. This means I have to do extra work in saving the state of the scene when despawning the scene, but it saves me the headache of managing signals across scenes. So the despawned scenes won’t react to the globally emitted signals. This makes signal management much cleaner and I don’t have to worry about event propagation across two scenes.

These techniques lead to less cognitive complexity when designing a scene. I only have to worry about the internal functions of a single scenes and not how the signals / events in one scene could affect another. I’ve got to say, I found programming in my lil VN framework a smooth and enjoyable experience. I’m looking forward to extending it further.

Asserting in Prod

I find myself drastically swinging back and forth on this topic. In my day job, for our particular application, asserting in prod is good. In fact, it’s a reliable get-out-of-jail-free card. Our microcontroller can reboot within a few hundred milliseconds and very little progress is lost. In game dev, I’ve come to learn that asserting is treated as a massive taboo—crashing the game is really bad. I come to like this mentality. It recognizes that the player can lose a lot of progress if the game crashes so the programmer should so everything in their power to handle the error as gracefully as possible. In embedded, we’re a lot more blasé about losing state because it’s easy to return to that state.

Anyways, this is a lot of words to say nuance matters. “It just depends.” The only dogmatic opinion I have is to not to have one.

I will say that it is annoying that Godot compiles out all asserts in prod builds (they discard the condition statement in the assert call along with forgoing the exception). This led to a really subtle bug that was hard to debug because it only happened in prod… To avoid this issue in the future while being able to call assert directly on a statement with a side effect, I’ll add on this function into my utility kit:

func my_assert(condition: bool, msg: String) -> void:
assert(condition, msg)

A wrapper around assert that will evaluate the condition statement.

One last thought on asserts, assert (in debug builds) liberally. Asserts capture assumptions, making your code easier to read. Asserts allows you to catch bugs earlier, by crashing the program and raising a red flag at the error site.

Strict Typing

Strict typing good. Code is easier to maintain, errors can be caught at compile time, compiler can make better code optimizations, etc. But I like how Gdscript has the flexibility to infer types on assignment (:=) or leave out types for when you need the flexibility of multiple return types.

Conclusion

I’ll keep this list updated over time. That’s all I have to say for now. Thanks for reading!

--

--

Aeris
The BKPT

Will probably use this blog to write about video games.