Part 3: Coding and Crunch
WARNING: There are definitely spoilers beyond this point!
So December 20th, 2022 rolls around, everything has finally calmed down, and I decide I’m not worthy of rest, and I should take the project document I wrote in November, and get right into making this game.
The first thing that I set my sights on was the parkour system, and there was a specific logic I was approaching this with. I knew that the schedule was going to get a bit tight at the end, so I tried to arrange the list of tasks in a way that could create defined boundaries for other tasks, or allow tasks to be done in parallel.
My plan was the following:
- Create the parkour system, which would inform the possibilities of the map.
- Create the map, which would inform the bugfixing required for parkour, and set boundaries for the AI antagonist.
- Send what I had so far to the testers, who would help sand down any navigation quirks of the parkour system.
- While the testers did that, I would develop the antagonist AI, who did not depend on the parkour system.
- Once the AI was done, I would fix the really serious bugs, send another version to testers, and then fix the rest of the bugs.
- As the AI was being tested, I would implement any remaining game mechanics, descriptions, and other details.
- Testing results would come back for the AI, and at this point I should be doing the final process of bugfixing.
- Send the final result to Nightshademaker (my professional game tester friend), who already agreed to do intensive testing of the AI and parkour within two days of the SpringThing deadline.
- I expected Nightshademaker to basically find the last few rare crashes, which meant bugfixing and writing tutorial materials should only take the last two days.
The old phrase of “no plan survives first contact with the enemy” held true. Stuff went very awry at the end, and I am extremely lucky to have gotten the testers I did, and also have finished anything like a working game by the end.
Parkour!
The “parkour” system was correctly pointed out by a few reviewers as “simply climbing”. There’s a reason for this. The original parkour system was going to have a lot more going on, and I Am Prey was meant to use a lot more of the system.
However, it turns out that when a crazed maniac is hunting you down to kill you, you don’t actually have the luxury of time to perform complex parkour maneuvers from A to B. As a result, most routes between rooms usually involved climbing on one object, maybe jumping to another, and crawling into the vents of the facility. I kinda kept calling it a “parkour system” because I still have plans to make it a standalone extension, which has quite a few more features than what were used in the game.
Perhaps I should have swapped steps 1 and 2…
Anyway, I got to work making a parkour system that had a way larger scope than what was really necessary. More on this is detailed in this thread.
Around January 18th, 2023, disaster struck. You can see me panic here and here.
Okay, so what happened? Long-story-short, I had accidentally made 3 different use cases of what was effectively the same logic, but cloned 3 times. It didn’t occur to me that it could have been refactored until I had tweaked and refined some stuff near the end, and then I realized what a bugfixing nightmare I had created for myself. However, the act of consolidating everything would have required me to refactor…well…everything. So it was, by definition, a total rewrite.
At least once a week, until the deadline, I had spent at least 30 minutes cursing myself for needing to do this. This rewrite cost me a lot of time, and I was a moron for not seeing the patterns earlier. It wasn’t even a situation where I didn’t plan it out first. I did. That’s the issue. I didn’t recognize the patterns in the plan until I was almost done with the parkour code, and the patterns suddenly appeared in the code itself, and not in the abstract plan I was working from.
Another unexpected challenge was adding some new logic, which evaluated if something was in touch range of the player or not, but with the parkour rules in mind. This alone carried at least 95% of the bugs found by testers. It turns out that the logic for creating and enforcing routes was the easy part!
Oh yeah, and also rewriting the actorInStagingLocation
logic, which could automatically find a short route to a nearby object, within certain complexity constraints.
The parkour system was actually only 80% done before I decided to make the map. Functionally, the system was done, but my own bug hunts were surface-level at best. I decided that if I made the map now, then I would know what parts of the parkour system needed most of my attention, and then I could focus my efforts when the real testers got their hands on this.
The Map
So here’s a fun fact, which I don’t know if anyone has picked up on yet. The core design of the map is based entirely on how many turns it would take to get from A to B. Basically, the design was to link a couple loops together, to create the best chase experience, should the player get caught. From this structure, I figured out the parkour routes that would make for excellent shortcuts.
Once I had all of that figured out, I went in and did some (literal) worldbuilding to figure out how the life support systems work, how the air flows, how the plumbing works, how heat is managed, how logistics are optimized, and then how the history of the facility dictated where some remaining rooms were placed. Then, I added some extra history of what our dear antagonist did to the place, once everyone else was gone. Of course, 90% of this work would not show up in the final product, but having this information figured out would inform how I would write things that did show up in the game!
Additionally, I’m certain there would be players who might go around, collect clues, and try to figure out how this stuff would have worked.
The best part about having an antagonist who is literally making an arena to chase the player in is…if something seems weird, it’s probably Skashek’s fault.
Mostly-empty industrial kitchen? Skashek didn’t want the player improvising much. Mostly-empty assembly shop? One of the Prey clones printed a weapon once, and ruined it for everyone else. Missing furniture, or furniture in strange spots? Skashek did some renovations. It’s incredibly convenient when a major character in your game is just as invested in making a game-ified area as you are. In particular, a lot of people asked me why the locked doors around the utility corridor were so obviously terrible at providing security. Well, it’s simple: Two of the doors are locked to create a dead-end. If Skashek can funnel the player into that room, then victory is ensured. (Or is it? Maybe he built a secret exit, which only the most clever of Prey could find…)
This is where quite a bit of Akira’s writing seeped in. I was really good at creating the systems and history, but she was good at taking that information and making it seem lived in. There are plenty of little details in the map that never would have occurred to me, but she suggested. For instance, the remains in the central ventilation node, or the design of the barren industrial kitchen, the obsessive level of cleanliness in the restrooms, and more. In fact, the post-comp version is going to see a lot more of her handiwork, as I got really short on time near the end.
Oh yeah, that’s another thing: My poor testers! Woe upon them! Only 3 or so rooms had any kind of description for almost the entirety of testing. Everything else was simply:
TODO: Add description.
They had to rely entirely on the included image of the facility map, because the descriptions had nothing for them!
In fact, this is an excellent time to credit some more members of my team! Absolutely incredible round of applause to the testers: @mathbrush, Nightshademaker, @Piergiorgio_d_errico, @pinkunz, and @rovarsson!
The bugs, crashes, and strange behaviors these people found were legion.
It was a two-front war on the bugs, really. Nightshademaker was on a dozen phone calls with me—for hours at a time—while he did his absolute best to break EVERYTHING. While his unmitigated (but thorough) chaos would catch quite a lot, it was also Mathbrush, Piergiorgio, Pinkunz, and Rovarsson who caught not just bugs, crashes, and clunky mechanics, but also gave me stacks of transcripts, providing valuable insights on the thought patterns, habits, critiques, and intuitions of experienced parser players.
REMEMBER: Nightshademaker and I both come from visual indie gamedev! There were amazing data in those transcripts, which would not have occurred to either of us!
I would absolutely recommend all five of these people as testers for anyone else!
10 out of 10!
I repeat: These five people get all the credit from every reviewer who said the game was smooth. Yeah!! That was all you! I cannot possibly take credit for that!
Y’all found bugs and crashes that I didn’t even know were possible. Some of you put in commands that I didn’t even know Adv3Lite could handle, and revealed entire subsystems that I hadn’t accounted for. Honestly, the best part was that a lot of you were bringing your habits and assumptions from Inform games, which gave me a tonne of data on what TADS and Adv3Lite did differently, and what needed to be added or adjusted to welcome the Inform side of the forum to the game, too!
This is probably the most fun I ever had in gamedev. Y’all were wonderful, patient, thorough, and had amazing feedback. In the moment, I would wake up and brace myself for messages containing bug reports, but that was entirely due to the stress of the deadline. Bugfixing is actually a blast, lol. It makes me feel like a detective!
Also: Holy cow?? Gathering bug reports in parsers is so streamlined??? What?! If I were trying to gather and replicate bugs in a visual game, I would be making a custom logging system, and would need to figure out how to not spam thousands of lines from every update frame. And if I missed something, then I would be absolutely screwed trying to figure out what exactly the tester did, but it’s not like the tester can sense what’s happening on the RAM chips or anything.
Absolutely wild.
Antagonist AI
Unfortunately, by time these testers first received the first test version, March was already half over. I was receiving transcripts every day—from this day until submission time—and I was industriously fixing bugs and crashes, while also developing the AI antagonist.
Now, if it weren’t for the fact that game AI is a special interest of mine, I would not have gotten the antagonist done in time. At all. This was the point where I went from spending 48 hours a week on this, to spending 84 hours a week on it.
Skashek would be governed by a finite state machine, where each state rerouted various methods and functions to nuance his behaviors appropriately. Arranged around the core AI object, there was the…
- Intro State: Governing his behaviors before the chase begins
- Lurk State: Governing his behaviors while he gathers clues and searches the facility
- Chase State: Governing his behaviors after the player is caught
- Reacquire State: Governing his behaviors when the player escapes the Chase State
- Ambush State: Governing his ability to sneak away and hide somewhere, waiting to jump out
- Cat Mode State: Simply, a convenient “do-nothing” state for Cat Mode, to reduce possible bugs
I didn’t have time to do the Ambush State, and I quickly decided to save that for post-comp.
It turns out, too, that the logic used in the Chase State actually performs the tasks for the Reacquire State for free, so I crossed that off the list as well.
However, before these victories, there was one silly little problem I had to deal with first:
NPCs in Adv3Lite only perform true actions when commanded to do so, by the player.
Yeah.
At the end of the day, every action comes from the player, somehow. Anything else done by NPCs in any sort of Daemon
seems to be faked, if implemented at all. I couldn’t find any stuff in Adv3Lite that allowed an NPC to be truly autonomous, and perform actions using the same systems as the player. So, I had to implement a turn-taking system for Skashek, as well as a small series of hooks that would allow him to reuse the same logic that was already in place for player actions, which would help reduce complexity and future bugs.
Reduce, reuse, recycle!
However, this caused me to dive DEEP into the madness that is the verify stage of actions, as implemented in the Action
class itself. And let me tell you, there might have been dragons there.
Pathing was another challenge. Adv3Lite comes with pathfinding, already, but Skashek would need to do a lot of planning, calculating, and theorizing, which could really slow down the responsiveness of the game if I used the built-in pathfinding.
To solve this, I took some advice from @jbg and created a pre-cached data table. Using this table, every room would know what exit to use to get one step closer to any other room. This way, I would effectively need to say “Get closer to the library. Okay, now get closer to the library. Okay, now get another step closer to the library.” and the pathfinding system would string together the correct series of exits.
A bonus feature I added for the reacquiring behavior was to have every parkour exit also store what room it leads to, so when the player tries to escape, then Skashek knows where to look for them next.
Also, another neat thing (which I don’t know if anybody noticed) is there are specific opportunities where Skashek will actually lunge to catch up with the player. Specifically, when the player uses parkour to escape to a nearby room, Skashek will sometimes get a burst of speed to arrive just in time to resume the chase. The player still buys some extra time in the process, though. This little behavior was added in to create some rather movie-esque chase sequence moments.
Final Moments
I was working on stuff, right up until the last available moment. Four hours before the deadline, I was on a Discord call with Nightshademaker. Both he and Akira (who sat next to me on the laptop) playtested the game while I wrote the survival guide. The moment any of them encountered a bug or a crash, I went into the code, fixed it, and emailed them the patched version immediately, and testing would resume.
It was super intense, and there were multiple overlapping conversations going on, while I was trying to type up another document.
Within the final hour, Akira took action. There were still things that needed fixing and implementing, and she quickly assessed the situation, and suggested what should be cut (saved for post-comp), what should be wrapped up, and what should be simplified (again, with the more complex version being saved for post-comp).
At this point, she had been following along with what I had accomplished each day, giving occasional input, and seeing what the testers where finding. She knew the project well enough to make informed decisions, and I trusted her direction. After all, when things got really stressful, she could often summon the clarity of a starship captain.
So I followed her recommendations, wrapped the game up, packaged it, and sent it on its way, with 10 minutes to spare.
Thus concludes the Era of Crunch.
I’m really tired, lol.
I’m pretty sure this is all that remains of what I wanted to share in the post-mortem. If I have any lingering thoughts later, I’ll add them, but this should be it!
Thank you again to everyone who joined me on this crazy journey! I had a lot of fun!!
And, as always: Thank you for playing I Am Prey, and giving me a chance!