2020 has surely been a year full of surprises for everyone (except virologists who had been warning people about pandemic readiness for decades, or pessimists, who knew things would likely take way longer to "get back to normal").
Back in December, I was out of ideas for #procjam, but I had some holidays that coincided with it, and if there’s one thing I needed was something to fill up my time during the days off (before you look at me like that, it was a tough, tough year and I needed to do something fun).
"I don't even know you, why are you being mean to me?"
Turns out that living in a mild post-truth dystopia is all the inspiration you need, especially spending 98% of your time inside a London flat (where the only thing as high as rent price is the chance of something malfunctioning). My partner and I, being a pair of introverts who got relatively comfortable with the lockdown hermit life, had our first glance at social contact in months with a plumber fixing our broken heating. Oh yeah, the plumber seemed to have a mild cold. Also, it was on the same week in which we got a new oven delivered to replace our broken one, by a nice gentleman with a cough.
A lot of people would immediately get angry. However, one must r/AmItheAsshole with oneself every now and then: I’m comfortably working from home and have tons of support from my company if I need time out. Do these guys have that option?
But it’s kind of comedic really: you might spend months sanitizing your groceries and quarantining your mail, and then one random encounter might get you sick. So how do you balance out your pyramid of needs between “having warm showers”, “being able to cook” and “avoiding getting sick”? How does the social element fit into all of this?
That scenario just clicked in my head: it would be my #procjam game. You’re fighting an unknown plague, trying not to get infected yourself. Originally, the game was going to be way more of a “social scenario” simulation: you’re an apothecary who has to deal with the customers and equipment breaking. Can you afford to have a a customer giving you a bad review because you sent them out for not wearing a mask? If your equipment breaks and the person doing the repairs has a symptom, do you send them away and risk not being able to craft medicine? That guy who just showed up with the latest rumors about how to treat the plague… can you really trust that information? But most of all, how do you deal with being sick with no one but yourself to rely on?
It’s funny how none of those mechanics ended up in the final game, but they’re essentially the root of it all. Speaking of roots, the name, Pestis Apotheca isn’t supposed to mean “plague pharmacy” or something like that; it’s supposed to be “plague repository“: there was going to be an underlying mechanic in which people would get infected by visiting your shop at the same time as someone else who had the plague.
(Yes, this is me insidiously inoculating the original idea into your mind without actually having to implement it. Take that, @PeterMolydeux.)
In 2019, one of the best things about Vortex was that I was essentially just polishing an incomplete prototype, with lots of UI eye candy provided by thia9uera. Since that worked out well and this game would need quite a bit of UI, I asked if he was interested in another collab, and he was. Huzzah!
But as you can tell by the paragraphs above, my process on game jams tends to be very open ended: there’s a general idea, then I just start trying to throw parts of it to the wall and seeing what has enough time to stick before starting with the next coat of Jackson Pollocking the code. To attempt shaping things a bit more so I wouldn’t make Thiago do throwaway work, the first thing I did was a simple conversation system, and implementing a draft of the game’s tutorial with it. This was really good because it gave us a general design we would aim towards and showed some edge cases on the conversation UI. Mentioning this on twitter, I got this reply:
I’m not smart enough to have thought this through, so it was not intentional, but “tutorial driven game design” is not only a great assessment of what I did, but also highlights what could be an interesting workflow for future projects! Thanks, @kchplr!
We also used Notion which seemed to work well for getting everything written down in a single place. I've been using it ever since, even in solo projects!
LET’S TALK THIS THROUGH
The conversation system was pretty simple: dialogues were defined in ConversationData ScriptableObjects, which had an opening sentence and a list of options, containing the NPC’s reply or a link to another conversation. I added a flag to only show certain prompts after all others were explored to control timing (i.e.: force you to read my crappy jokes).
The only reason why a possibly cyclic graph didn’t get out of hand is because the depth of conversation options was really small. If you’re doing a full blown conversation-based game, it might be worth it using some FSM solution (like we did in Blood Runs Cold)
That’s easy for static conversations, but what about the patients? The game is also partially inspired by the fact that whenever I go to the doctor, I tend to list all the information I can. If you’ve ever had to talk to me in person, you know that I might cram a lot of information in the same long-winded sentence, including useless information; i.e.: doctors probably hate me. Sorry docs!
But I imagine it might be a bit like listening to a bug report: you want as much information as you can get, and you kind of automatically filter things out that you know are unrelated. This was easily represented by the mechanic of clicking certain words to highlight symptoms: unless you “actively listen” to the patient, you won’t uncover what they’re feeling. Fortunately, TextMeshPro has a handy hyperlink tag that I could use to trigger those clicks.
To generate a “natural” sounding conversation, I used simple grammars. If you’re not familiar with the concept, it’s essentially defining the overall “format” of a piece of text via tags, then replacing the tags with words or chunks of text. Kate Compton and Emily Short have lots of great material on this approach.
Ironically, in all this time doing procgen this was actually the first time I wrote a grammar system – not just because I was never concerned with text, but also because it’s a considerable challenge to write the text in a way where everything fits and still feels varied. I probably should have gone for GalaxyKate’s Tracery instead of rolling out my own, but it was one of those “too busy chopping wood to sharpen the axe” moments.
Grammars were defined by tags ([starter] [primary] [finisher]) and tags were replaced by elements ([starter]=Good Day!;Hello!;Well met!)
The system is just recursively going through all words between brackets and replacing them with content. Here’s the gist of it (this was all written in game jam mode and never revised, so proceed with caution if you want to use it).
For the patients, a ConversationData file with default parameters was instanced, then the contents of the relevant answers was replaced with something out of the grammar. The only tag left after the grammar processing was ‘[symptom]‘, which was then replaced by the patient’s actual symptoms based on convoluted rules that I wrote with a melted brain and I’m glad I don’t have to touch anymore.
The last detail was a hardcoded processing of “command” tags: if a tag like “[!EXIT_SHOP]” was present in the response text, I’d fire a Signal and the relevant code would take it from there.
If you read the gist above, you probably noticed the System.Random being passed around. That’s because each NPC has its own random number generator, as does the world. A good side effect of that is having deterministic results that allow easier debugging, but the main intent was sharing the world among players. This means that you could, in theory, help somebody cure poor Coilbrit Millard, the apprentice gunsmith, or share the symptoms of THE PLAGUE you found out via the comments in the itch.io page. Sure, this depends on having an active playerbase, but shhhh… only dreams now.
Another (existential dread inducing) side effect for NPCs is that the mere act of choosing which shelves they were walking towards next would impact their chance of survival later on – which given the world I wanted to build with Bestiarium, is hell of on point.
The World RNG is used to create ids, which are then used as seeds on the individual RNGs of NPCs, diseases and herbs. Some of those elements require storing entire data instances (e.g.: disease state at day 3 for some NPC), but others can simply be restored from their id (e.g.: the effects and side effects of a given herb id). And before you ask, yes, the World RNG seed is, in fact, 2020.
All diseases are generated by selecting r unique entries from a pool of n possible symptoms, with r=3 for default diseases. The game has 12 symptoms, so the good old n! / r! (n – r)! tells us that there should be then 220 unique diseases in the game, plus THE PLAGUE which has r=4. Symptoms are defined by an int-based enum and an associated configuration ScriptableObject.
After selecting a set of symptoms, their enum values are combined into a seed, which is then used to start up the RNG that generates disease name and any additional data. It’s a bit convoluted (World RNG selects N numbers -> mix those numbers into a hash -> use this hash as the seed to a new RNG), but it guarantees the determinism and that equal sets of symptoms don’t generate different diseases. I had just imported a xxHash for something else, so it was quicker to just use that to generate the hash from the set of enums rather than… you know, thinking of adding numbers multiplied by powers of 10.
TO RID THE DISEASE
In real life, there’s 2 main ways medications tend to work: treating the symptoms or treating the causes. For design purposes, in the game, treating the symptoms always cures diseases. Treating a patient down to level 0 in 60% or more of their symptoms causes them to heal completely. The diseases have a maximum of 3 levels and if any symptom reaches level 4, the NPC buttons up the wooden coat, as we say in Brazil.
With every passing day, symptoms that received medication go down a level, and symptoms that haven’t go up a level. To treat diseases, the player must concoct potions using different medicinal herbs.
Each flower treats 2 symptoms and causes 1 side effect which can happen with a given percentage of chance. This means that it’s possible that you can cure regular diseases with a single plant, but you can never cure THE PLAGUE (as it requires at least 3 symptoms being treated).
To increase the amount of symptoms that are treated, you need to mix 2 different plants. The mixture’s effect will be semi-random and given by the percentage of each plant used, as well as the delta in the pH: if the difference in acidity is above 5, you’ll get no side effects, with a chance of having an extra positive effect – which is a mechanic I don’t expect anyone to infer but didn’t bother teaching as people just seemed to get the hang of playing around with different plants and percentages, and accept that sometimes they get a “critical hit”. I did get people trying to counter side effects from one plant with the positive effects from another, which was pretty cool.
The same herb combination, when mixed < 15% has a different set of effects and a side effect.
The mix itself is also kept deterministic using the same methodology as the diseases: creating a hash from the 2 ingredient ids, then using that as a seed for the mixture’s RNG.
I also wanted to bring back the organs from the Dissection prototype but I wouldn’t have time to add the proper mechanics: what I wanted to do is slot in a little imp coffin that you’d buy from the traveling doctors, along a shopping list and that would give you special ingredients. I did keep the tower model in because a) I had spent precious jam time doing a crappy tower model and b) I could use it as a worldbuilding nudge to Bestiarium as a whole and give a little smile to anyone who got the reference (and by that I mean me).
WORKPLACE PLATFORM HAZARDS
As awesome as Unity WebGL is, it’s still bound to browser sandboxing. The main issue I faced was that when releasing the mouse via CursorLockMode, the canvas must be clicked again. Since the game would keep alternating between locked cursor (for the FPS controls) and cursor based input (for the UI), having to click the canvas again would make it pretty unplayable. I ended up implementing a software mouse.
I wish I could give you the ultimate solution but I ended up literally duplicating the default input modules, commented stuff out and manually sanitized a pair of HashSets to generate IPointerEnter and IPointerExit events. It is, however, very possible a proper solution exists out there.
RANDOM, BUT EXPECTED
With this game, I yet again attempted to focus a bit more into polish and making sure people understood how to play. There are some solutions that were planned to be elegant (within their limitations of me being neither a designer nor an artist, of course), like delivering the tutorial via the dialogue of a friendly character right at the beginning, but there was also some degree of serendipity: I didn’t calculate the coefficients (number of symptoms and rate of symptoms treated for cure vs number of effects treated), I just typed in whatever seemed to make sense at the time, and lo and behold, things just clicked in the first attempt!
Because all the herbs and diseases are entirely random, and the dialogues usually only show you 2 symptoms, I didn’t think that you’d actually be able to cure THE PLAGUE without doing a bunch of experimentation, buying info and swapping herbs until you got a perfect pH match. Turns out that in the initial herb inventory, there is a combination that can cure it, and I found it entirely by accident!
But the biggest help I got to make the game flow smoothly was from Thiago: by having to organize my thoughts to communicate them to him and having someone who is an expert in UI and UX looking at the mechanics on a “sketch” state, we could iron out a bunch of design questions at the concept level before starting to implement them.
At the end of the day, I make these games for myself, really, so the lack of an audience doesn’t bother me that much. However, I do post it around so I can get some feedback for the concepts in general. The funniest thing happened when posting to r/proceduralgeneration: folks there are very partial to content that is very directly related to procgen (e.g.: terrain generators, generative art etc) and don’t care much about generative game mechanics. My post got around 14 upvotes, and a comment from one user suggesting to post this on r/medievaldoctor, which is a thing that exists apparently. The result? ~1.6k karma, even with the automod bots removing my post with the game’s link. In hindsight, it’s a quite obvious result, but would probably be something obvious from the get go to someone who is specialized in digital/game marketing.
So I guess that even with none of the social simulation mechanics implemented, the game did end up being an analogy for 2020 as I perceived it: certain things are only surprising if you’re not an expert... or not a pessimist!