Why did I make this game?
I’ve been interested in game design for a long time. I have a software background, but until this project I had never built a game.
There’s a lot that sits outside a traditional software engineering role: art direction, design decisions, balancing economies and player incentives, and more math than I expected (ray tracing, for example).
I wanted a first step into that space. From past experience learning new domains, I know it isn’t just drawing the rest of the owl.
Starting simple
This project acts as a playground in which I can experiment with game mechanics, balance, and player incentives.
The core is AI-generated and art is from sites like SketchFab. The code is developed in the Babylon.js engine.
That setup still gave me room to learn about:
- Game mechanics: I added a shop and basic progression through harder enemies, milestone rewards, and new upgrades as runs go on.
- Balance: I’m still tweaking this. The core tension is between earning enough gold for upgrades and keeping enemy difficulty high enough that the horde still feels rewarding.
- In-game economies: I’ve adjusted the gold economy several times: kill rewards, upgrade costs, and how quickly income scales.
- Performance: I’d heard a lot about pooling, draw-call reduction, and keeping fewer models on screen. This project was a place to try those tradeoffs firsthand.
The goal wasn’t to ship something incredible, or even something I built entirely myself. It was to learn part of what goes into making a game playable and fun.
Game Mechanics & Balance
The first version had no upgrades, no shop, and no progression system. It was lane switching and shooting zombies.
I knew early on that the game needed a shop and some form of progression. I wasn’t sure what should go into either, so I spent time thinking through upgrades that fit the core loop and implemented the obvious candidates.
After each batch of changes, I playtested to see whether an upgrade felt overpowered, underpowered, or like it didn’t matter. I repeated that loop until each addition felt like it contributed something real.
Upgrades were the first progression layer, but the game also needed enemy variety. I sourced new models, imported them, and gave each one different abilities.
The new models are still rough. For example, some animations don’t work (e.g. walking animations) for some models. They’re definitely a first pass, but they’re part of what makes later waves feel harder.
Ranged enemies have been the hardest to balance. Once they show up, your frontline can’t hit them without clearing the zombies directly in front of you first. I don’t think I would have predicted that interaction without building it and playing through it.
As runs got longer, I added milestone rewards. They’ve worked well enough that I still rely on them to keep upgrades affordable (more on that below). I also ramped zombie speed over the course of a run so you have less time to react. Speed increases a lot around wave 20, and by wave 30 or so the pace starts to feel overwhelming.
With the current balance, most players stall around level 30–35 when ranged pressure gets overwhelming. There’s still work to do so upgrades stay purchasable and the horde feels threatening without becoming unfair.
Simulations
In university, one topic I kept coming back to was simulating real-world systems. I’ve used that approach throughout my career to work through balance problems in algorithms and tooling.
When balancing this game, the AI reached for the same technique: simulate what a fully upgraded player can do before a run falls apart.
It hasn’t been perfect, as the level cap above suggests. The model still needs tuning. But it was a useful check, and something I’ll probably use again when I build a game from scratch.
In-game economies
When I first added the shop, the economy was simple: a few hundred gold per upgrade, with roughly linear progression. It felt fine in veryearly playtests, but it didn’t add much difficulty to the game or tension around which upgrade to buy. In most games with shops I’ve played, that tension is part of the fun.
After the first couple of upgrades, I moved toward stepwise cost increases that were sometimes linear, sometimes exponential, sometimes something else. By the end, pricing was mostly case by case based on the upgrade. I sometimes priced things cheaper than the curve from other upgrades would suggest because it would help the balance of the game. I’ve also cut prices several times so kill income can properly funds upgrades without not being able to afford them. On the flip side, I’ve had to increase costs of some items like turrets because I kept being able to upgrade all of the ones on the map at once which felt too easy.
Today the shop opens at the end of every wave. You spend gold earned from zombie kills. It isn’t obvious that different zombies pay different amounts until you hit a boss and get a jump of about 500 gold. The “kill gold” is the main source of income. Milestone rewards help too where you get a larger lump sum after a certain number of waves. Later in the game you can buy war bonds, which pay interest on stored gold.
That said, I don’t think war bonds are balanced yet. The return isn’t worth it, and you can’t really make your money back. That’s on the list as I keep tuning the economy.
Performance
I wanted to experiment with game performance on this project. Most of the code is AI-generated, so there’s a limit to how much you can really, but I’d read and heard about pooling, draw-call reduction, and similar techniques. I added an FPS counter in the bottom right as a starting point.
The FPS counter helped pinpoint when things slowed down, but you could usually see the lag visually too. It also surfaced the first real bottleneck: the game wants hundreds of zombies on screen, but rendering that many without stuttering isn’t realistic… at least out of the box in Google Chrome.
Zombie spawning moved toward heavier object pooling. I wasn’t deeply involved in that change, but the FPS improvement—and the drop in lag—was obvious.
Bullets were the next problem. Up to six turrets plus three columns of soldiers shooting can put a lot of projectiles on screen at once. Those got pooled and tuned as well.
The change I implemented myself was combining soldier models. The squad scales from one soldier up to eighteen, which meant eighteen different layouts to render. I merged the soldier mesh into groups of two and three so one draw could represent up to three soldiers. That cut the worst case from eighteen models down to six.
I could have kept merging—five-, six-, or seven-soldier meshes until the whole squad was always a single model, but the gains felt pretty negligible. Soldier count is relatively low compared to zombies anyway. It was still worth trying, especially with a lone soldier running in from the left to join the squad once you got barracks.
Conclusion
This project was a great learning experience. I got to try out a lot of game design techniques and mechanics that I wouldn’t have had the chance to try otherwise.
It was a lot of fun to build, and I’d like to tweak the balance a bit more before moving onto the next game design experiment.
Conversation
Add a note.
Questions, corrections, and useful tangents are welcome. Sign in to join the conversation.