The last commit I ever made to our game says, timestamped 22:16 on its final night:
Text mesh pro 30th time imported this nonsence. Because fuck unity.
I didn’t remember writing it. I found it eighteen months later, running an archaeology dig through my own repositories, looking for the moment I left Unity. Turns out I’d already written this essay’s title on my way out the door.
This is the first heresy of three, and here is its claim: game engines optimize for the people who can’t code — and they do it at the expense of programmers, the strongest part of game development. Visual wiring, editor-first workflows, scriptable everything, so that people who don’t read code can build levels and test ideas. That’s a legitimate, even beautiful goal. But web development never hobbled its experts to achieve it, and game engines did — and the community has normalized the workarounds so thoroughly that it proudly posts them as best practices.
I paid the tax in the currencies that matter: a friendship, my mental health, and years of midnight commits. The receipts are below, with hashes.
The singleton confession
Every Unity developer knows the singleton pattern the way sailors know knots. The engine expects roughly one scene at a time; state that must survive scene changes has to live somewhere, so it gets crammed into static singletons — and the community celebrates this, shares it in tutorials, defends it as pragmatism.
Here’s my honest take: the singleton isn’t laziness. The singleton is a poor man’s dependency injection — devs reach for it because the alternative is dragging references onto billions of component fields in the editor. Which reveals something funny: the engine’s flagship feature, the visual wiring that everything was sacrificed for, is so tedious that the engine’s own users route around it with an antipattern. The editor-first engine failed its editor-first bet, and programmers pay for the bet anyway.
So I did what a backend engineer does: I wrote a dependency injection library. It’s
called SwiftLocator, it’s on
NuGet, and it has a typo in a public class name (DepedencyInjector) that I’m keeping
forever — because nobody copies typos into filenames, and that misspelling is my
certificate of authenticity.
The team
Around 2023, my best friend from study years and I founded a tiny indie label — Euphelia Interactive. He was the most brilliant dev I knew back then: pattern-fluent, watched hours of architecture content daily, hungry. By the time we teamed up, he’d spent years at a mobile-game studio shipping games like a bakery ships pancakes — fast, constant, and never maintained long enough to feel their own technical debt.
We tried five projects together. The repos remember what I’d fuzzed over: on our biggest
attempt, my 102 commits average ~236 lines each and live in folders named Realm,
Save, Storage, DI — folders that are 100% mine. His 332 commits average ~49 lines
and live in quests, scenes, assets. Two completely different crafts, visible in the
ledger. The blanket war, quantified.
I wrote the save system as an actual database — Realm,
per-save databases, full event history — instead of the scriptable-JSON dumps game devs
usually reach for. My classes implemented IDisposable and managed their lifetimes
outside the engine, the way any .NET service would. And that’s where the worlds collided:
my friend kept hitting bugs where data “vanished spontaneously.” The archaeology found
the exact wound: he disposed a Realm that a result object still needed (c4af6ea), the
data evaporated, and my final commit on the project — ever — moved the using statements
to the callers to make his mental model safe. It sits on a branch that never merged.
He wasn’t a lesser dev. The ecosystem he grew in simply never asked him to know what object lifetimes are, because engine-land manages lifetimes for you until the day it doesn’t. Meanwhile my architecture read as alien to him — and here’s the punchline: when he later applied to a game company using our shared game as his portfolio, their reviewers concluded he must have copied a tutorial, because the code looked too structured to be indie. My architecture was mistaken for published teaching material by people trying to dismiss it. I’ve had worse compliments.
The controlled experiment
I kept running the same experiment without noticing. As plain software engineering, I
wrote a UDP microphone library — one 2 AM burst, forty-nine minutes from first commit
to working prototype, voice
flowing between two console apps. As engine work, integrating voice into our Unity
game meant Photon Fusion imported and ripped out within six days (“Kinda working,
kinda not working fusion multiplayer”), Mirror vendored in its place across 2,002
files, and a voice chat that shipped uncompressed audio through [Command] attributes
around triple if (!isOwned) return; guards. The project died at 3:14 AM with the
voice chat, per its own commit message, “scuffed” but working.
Two weeks after the game died, I was still building — SwiftMicrophone, the clean library version of what the engine had made miserable. It never shipped in anything. The game ended first. The tooling outlived the game — it always did, with me.
Same developer. Same problem domain. Five-minute prototypes outside the engine (fine — forty-nine; the ledger keeps me honest), weeks of misery inside it. At some point the experiment stops being a coincidence and becomes a verdict.
The missing middle
The final straw isn’t any single wall — it’s what lies beyond the walls. When you outgrow Unity’s assumptions (I wanted non-Euclidean worlds, exotic culling, rendering the engine forbids), your only exit is writing an entire engine from zero. That’s like being told ASP.NET can’t do websockets your way, so go rewrite ASP.NET. Web development always offers a middle layer between “accept the defaults” and “rebuild civilization” — extensibility is the whole culture. Game engines have no middle ground, which is why the most capable game programmers you’ll meet are somewhere in year three of building their own engine instead of their game.
I tried the other exit too. I went to Unreal, watched the tutorials — 99% Blueprints, a shit ton of them — and with zero lines of code I assembled a fully working FPS from free assets in an evening. It made me sad in a way that took me years to articulate: I knew that by the next morning I would remember nothing of what I’d built. Code is serialized understanding — you come back after two months and read it like an API. Blueprint state evaporates overnight. The engine handed me success without ownership, and for someone whose memory already needs external structure to survive, a tool whose knowledge can’t be re-read isn’t convenience. It’s disability with a progress bar.
What I actually wanted
One thing. Respect — and I can define it precisely, because I watched its absence destroy something I loved.
My friend called me unreliable, because I couldn’t grind daily the way he could; I saw his code as unstructured, because his ecosystem never made him maintain anything. Each of us accused the other of exactly the deficit our tooling had installed in us. The engine set the blanket war’s terms, and we played them out on a friendship. He’s spent the past year at home, recovering from the burnout his pancake pipeline finally collected on. I left game engines entirely and put my architecture where it’s welcome. Neither of us was wrong. Both of us froze.
“If engines would respect programmers — if I didn’t need to fight a blanket war with my best friend just to keep a project’s bones straight — I would’ve stayed.”
That’s the whole heresy. The next one is about the workflow tax — the generated-file storms, the assets that break git, the blanket problem by name. It gets less personal and more measurable. This one had to come first, because before you can audit an ecosystem, you have to admit what it cost you.
Covenant note: the commits, line counts, authorship splits, and library timelines in this essay were verified against the original repositories in July 2026 — including the parts where the archaeology corrected my own memory (I remembered “five minutes”; the ledger says forty-nine. I remembered Photon; it was Fusion, then Mirror, six days apart). The public receipts: SwiftLocator, UDPMicrophone, SwiftMicrophone. The game repos are private; the hashes are real. Every verse checkable — where it can’t be, it says so. △○▽