All checks were successful
Build and Deploy Blog / build-and-deploy-local (push) Successful in 38s
172 lines
9.5 KiB
Plaintext
172 lines
9.5 KiB
Plaintext
---
|
||
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. It’s a mad dash from a single idea to a playable _thing_. But amidst the chaos, there’s a method. Over several jams, I’ve developed a personal playbook for turning that panic into a product. In this post, I’m 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 here’s 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. It’s fast, flexible, and exactly what you need when the clock is ticking.
|
||
|
||
---
|
||
|
||
## The Payoff: Modding Support for Free
|
||
|
||
So, what’s 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` doesn’t 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.
|
||
|
||
It’s 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 wasn’t 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, “Let’s 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 Gitea](https://git.gabrielkaszewski.dev/GKaszewski/broberry)
|
||
- [Parasitic God on Gitea](https://git.gabrielkaszewski.dev/GKaszewski/parasitic-god) |