Enter: Pillars of Creation!
My Senior year of DePaul came with my biggest project yet -- Capstone. Students are given two semesters to develop a game in Unity. We saw many exciting projects with different ideas and clever aesthetics, but it was clear that our game had something special to offer.
Our project, Pillars of Creation, is a real-time card game, where you don't have to wait your turn. This project was a BIG undertaking. Not only were we developing a game in six months, but it had to have online multiplayer as well. This came with many exciting (and at some times, intimidating) challenges.
To begin with, we had to develop a prototype to convince our professor that this project was worth doing. Given that we had a winter break to do this (about a month), and most everyone on the team was not in a position to contribute significant time to it, the name of the game was quick and dirty.
My beginning implementation was literally sending delimited strings over the network. The goal was not to make a clean framework with which to send packets over -- it was simply to prove that we knew how Lidgren (our networking framework) worked, and that we could send messages between computers.
We showed our prototype to our professor with immense anxiety, and he loved it. We did it! We showed that we can get a message over the network -- next came the harder parts of development. Oh the joys of team-work, where communication is key. Our group in particular consisted of four engineers, which ended up becoming a challenge. Every conversation with us was an argument, and everyone had an opinion about everything. We had to learn how to keep conversation moving, and continue from solutions even if they weren't perfect.
We came up with some pretty darn good solutions, however.
We built a kick-ass system for developing cards that utilized Unity's ScriptableObject asset. Not only did we find a way to hold onto stats like health and damage, but we also came up with a pseudo-scripting system that allowed the user to express a great amount of behavior with some simple building blocks.
It came to the point where making new mechanics was harder than defining card behavior.
We also avoided code duplication by adding a custom window to Unity that allowed you to enable Client or Server code by enabling their respective #define symbols.
Our packet system was really clean as well. We ended up indexing into an array of functions to keep ourselves from building a progressively bigger switch statement with each packet added.
One of the biggest problems we had to resolve in our game was a networking issue. The symptom was a player's card would randomly become unusable in their hand, due to it being sent into a "waiting for response" state, except never coming back. This rarely happened, but was correlated with latency.
Another engineer and I had to sit in a game and mash click cards until the bug presented itself. When she suggested that we should turn on simulated latency in Lidgren to check for the bug, it presented itself immediately. After reproducing the bug enough times, we found something else happening.
If you caused enough cards to become unresponsive, the entire game state would start to desynchronize. This told us that this isn't a "patch quickly and move on" fix. This was something that needed special attention. After thinking about it for a while, we came up upon the solution.
The reason this was happening was because of how we were keeping track of cards server-side. We had each card remember what index it was in a zone (Zones are things like Deck, Hand, Graveyard, Monster Zone, etc), but that had a fundamental problem. If you requested to play a card from hand twice before getting an answer, the server would pop the card from its index, read the second message, and then try to get the new card at the old index.
Manual latching of card requests would be too hard to keep track of, because you wouldn't even know which requests were valid, and which ones were invalid. We knew what we had to do -- we had to refactor the whole system to use unique ID's instead of indices.
This was a big refactor, and would end up touching 90 files. To make this refactor, I started by removing the old variable, and looking for where the errors appeared. After going to each file and updating the info, I thoroughly tested the game to make sure things still worked. With a refactor that big, the last thing you want to do is break 90 files, or worse, miss one and have it go unnoticed. But thankfully, everything was still secure after the refactor.
Now, we are out of graduation and looking for jobs. However, I and another team member are still developing Pillars of Creation after college, and are attempting to make it into a real money-making game. We launched a Discord for beta testers and got 250 applications!
I've had to learn a lot in this process. Aside from the engineering challenges of learning the Steamworks.NET API for lobbies and monetization, there is also the challenge of learning business, and building a community. I'm not an expert on these yet, but I'm doing the best to educate myself as much as I can.
I'm excited to see how far we can push this boulder uphill, release an official game, and maybe, just maybe, make a buck off it!