Files
blog/posts/beyond-it-works.mdx
Gabriel Kaszewski ca7bc4c030
All checks were successful
Build and Deploy Blog / build-and-deploy-local (push) Successful in 41s
Add blog post on game jam architecture and development practices
2025-09-05 01:34:17 +02:00

172 lines
9.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "Beyond 'It Works': A Deep Dive into Game Jam Architecture"
date: "2025-09-04"
description: "Exploring the importance of good architecture in game jams and how it can lead to powerful features like modding support."
---
# Beyond "It Works": A Deep Dive into Game Jam Architecture
## The Countdown Begins
The timer starts. 48 hours. That seems like a lot of time, but when you have a goal at the end, it turns into a frantic sprint. The theme is announced: **"Your cure is your curse."** My mind draws a blank. Tick... tock... A mix of excitement and a little bit of panic takes over.
Every game jam begins this way for me. Its a mad dash from a single idea to a playable _thing_. But amidst the chaos, theres a method. Over several jams, Ive developed a personal playbook for turning that panic into a product. In this post, Im going to pull back the curtain on my process, focusing on the backbone of it all: the **code**, the **structure**, and the **composition** that holds the game together.
---
## My Golden Rule: Scope, Scope, and Scope It Down Again
Before I write a single line of code, I have to talk about the most critical skill in a game jam: **managing scope**.
You're not going to complete your entry if you try to recreate _World of Warcraft_. Your idea must be small enough to be completed and polished in about **60%** of the allotted time. The other **40%** will be devoured by bugs, unforeseen problems (like exporting builds—web builds take a _really_ long time, apparently), and the desperate need for sleep. Every technical decision I make is guided by this principle.
---
## The "Just Make it Work!" Counter-Argument
This summer, I started doing game jams with my friends. The goal was simple: actually finish and release small games. I've now managed to release two ([Broberry](https://gabrielkaszewski.itch.io/broberry) and [Parasitic God](https://gabrielkaszewski.itch.io/parasitic-god)) and, in the process, I've found my own development rhythm. My mindset is always to keep the code as clean and maintainable as possible, even under pressure.
This approach isn't always popular.
When I talk to my friends, they often say, "Proper architecture takes too much time! Players don't care about code; they care about the game." They have a point. A beautiful codebase is worthless if you don't have a game to show for it.
But heres my counter-argument: **good architecture doesn't slow you down; it speeds you up.** A clean, modular structure isn't a chore. It's a powerful tool that allows you to iterate, debug, and add features faster than you could with messy "spaghetti code."
Let me show you how.
---
## My Process: From Theme to Systems
When the theme drops, I spend a few minutes thinking about what kind of game I want to create. Once I have a rough idea, I immediately start thinking about the **systems**. What modular pieces will I need? Can I build a weapon system where I can easily create a sword that deals poison damage or a gun that shoots healing bullets?
Thinking in terms of systems and reusable parts is the key. This naturally leads to my favorite design pattern for game development.
### My Secret Weapon: Building with LEGOs (Composition)
In programming, there's a principle: **"prefer composition over inheritance."** In a game jam, I treat this as law. For those who aren't familiar with the terms, here's a quick breakdown.
#### The Inheritance Trap
Inheritance is what we're often taught first. It's an "**is-a**" relationship. A `Cat` **is an** `Animal`. A `Dog` **is an** `Animal`. This creates a rigid family tree.
```csharp
// The "is-a" relationship
public class Dog : Animal { ... }
```
This works for simple things, but it falls apart with complex game mechanics. Let's use a weapon example. You might start with this:
```csharp
public class Weapon { ... }
public class MeleeWeapon : Weapon { ... }
public class RangedWeapon : Weapon { ... }
```
Easy, right? But now you want a **sword that also shoots projectiles**. Uh oh. Does it inherit from `MeleeWeapon` or `RangedWeapon`? What if you want a **gun that sets enemies on fire**? Do you make a `FieryRangedWeapon` class? What about a **healing, fiery, projectile-shooting sword**?
You end up in a tangled mess of classes. This is the inheritance trap, and it's a nightmare under a 48-hour time limit.
#### The Composition Solution: Think LEGOs
Composition is a "**has-a**" relationship. Instead of saying an object _is_ something, you say it _has_ functionalities.
Think of a `GameObject` in Unity or a `Node` in Godot as a single LEGO brick. On its own, it does nothing. The magic happens when you start snapping other bricks—**components**—onto it. A "weapon" in my game isn't a complex class; it's an empty object that **has** a collection of scripts defining its behavior.
Let's rebuild our crazy weapon idea with composition:
- We create small, single-purpose scripts (our LEGOs):
- `DealDamageOnHit.cs`: **Has** a damage value.
- `SwingAttack.cs`: **Has** a swing animation.
- `ShootProjectile.cs`: **Has** a bullet prefab.
- `ApplyFireEffect.cs`: **Has** a burn duration.
Now, we can build any weapon just by mixing and matching:
- **Simple Sword**: `GameObject` + `DealDamageOnHit.cs` + `SwingAttack.cs`
- **Flamethrower**: `GameObject` + `DealDamageOnHit.cs` + `ShootProjectile.cs` + `ApplyFireEffect.cs`
Suddenly, you're not writing new classes; you're just assembling new objects. Its fast, flexible, and exactly what you need when the clock is ticking.
---
## The Payoff: Modding Support for Free
So, whats the ultimate benefit of this LEGO-based approach? You get powerful features that seem impossible in a 48-hour jam. For my game _Parasitic God_, this architecture meant I could implement a full-blown **modding system** with very little extra effort.
It wasn't a feature I planned for. It was a natural, almost free, consequence of the data-driven design I had already built.
### The Core Idea: Game Content is Just Data
The central principle is that the "things" in my game—miracles, events, and upgrades—are not hardcoded. They're defined in simple **JSON text files**.
The C# code is just an **engine** that reads and interprets that data. My `GameLogic.cs` doesnt know what a "Miracle of Growth" is; it only knows how to process a list of abstract `Effect` resources. This separation is what unlocks modding.
Its built on three pillars:
#### Pillar 1: JSON as the Blueprint
Instead of coding a miracle's properties, I define its structure in a "Data Transfer Object" (DTO) like `MiracleDto.cs`. This means anyone can define a new miracle in a simple `.json` file—no compiler needed.
```json
{
"Name": "Gift of Life",
"FaithCost": 25.0,
"Effects": [
{
"Type": "AddResource",
"TargetResource": "Followers",
"Value": 50.0
}
]
}
```
#### Pillar 2: The Universal Loader
The game's `MiracleLoader.cs` finds and loads these JSON files. The trick is that it looks in two places: the game's internal `res://` directory for my default files and the player's special `user://` data folder for mods. The loader code simply loads the defaults and then loads the user's mods, allowing players to add new content or overwrite existing files.
```csharp
// From Core/MiracleLoader.cs
public static Dictionary<string, MiracleDefinition> LoadAllMiracles()
{
var loadedMiracles = new Dictionary<string, MiracleDefinition>();
// 1. Load the default game content
LoadMiraclesFromPath("res://Mods/Miracles", loadedMiracles);
// 2. Load the user's mods, overwriting defaults if names match
LoadMiraclesFromPath("user://Mods/Miracles", loadedMiracles);
return loadedMiracles;
}
```
#### Pillar 3: The Reusable LEGOs (Effect System)
This is where it all ties back to composition. How can a JSON file actually _do_ things? The `"Effects"` array in the JSON is a list of commands. Each command tells my C# engine which LEGO brick to use. The `MiracleLoader.cs` reads the `"Type"` string (e.g., `"AddResource"`) and creates an instance of the corresponding C# `Effect` class.
A modder can create a complex new miracle—modifying stats, applying buffs, and unlocking other content—all without writing a single line of C# code. They just assemble the available `Effect` LEGOs in a JSON file.
---
## The Final Result & What's Next
This system is the ultimate proof that good architecture pays for itself. The time I spent separating data from logic wasnt a detour; it was a direct path to a more robust, flexible, and feature-rich game.
So when my friends say, “Just make it work,” I say, “Lets make it work _right_.” Because sometimes, working right from the beginning means you get incredible features like a modding system, not as a massive time sink, but as a happy accident.
Going forward, I'm definitely going to take part in more game jams. While I'm happy with my coding practices, I've realized I need to grow as a **game designer**. My games work, but they often lack that certain _oomph_. That's the crucial skill I need to develop to create better experiences for players.
In a perfect world, I'd find a team where I could focus on what I love: coding and creating tools for designers to bring their visions to life.
---
### References
- [**Itch.io**](http://Itch.io) **Pages:**
- [Broberry](https://gabrielkaszewski.itch.io/broberry)
- [Parasitic God](https://gabrielkaszewski.itch.io/parasitic-god)
- **Source Code:**
- [Broberry on GitLab](https://git.gabrielkaszewski.dev/GKaszewski/broberry)
- [Parasitic God on GitLab](https://git.gabrielkaszewski.dev/GKaszewski/parasitic-god)