Postmortem: Detectivania
This blog post is about the following game, which you can play right now:
SPOILER WARNING
This post contains structural and mechanical spoilers for Detectivania. The game is about half an hour long and you can play it right in your browser.
Project Goals
- Learn Twine. I ended up using Twine 2.2.1 and Harlowe 2.1.0.
- Publish on Itch.io. I set up a profile and published Detectivania there as well as here.
Game Concept
Since this is my first game, I chose an idea that seemed relatively simple - what I termed the “conversational Metroidvania.”
I was explaining to a friend what a Metroidvania is, and I defined it as a platformer where you get new abilities that recontextualize previous content and allow you to access new content or handle new challenges. And I thought - why does it have to be a platformer in particular? Couldn’t you swap in other genres and turn them into Metroidvanias? And for whatever reason, the first one that came to mind was dialog-based games.
In particular, I thought of the focus point system from Subsurface Circular, in which interesting topics mentioned by characters become available to ask other characters about. This is used for flavor and worldbuilding, but also progression - sometimes, in order to proceed you must get a focus point from one character and then go back to a character you already talked to and ask about it.
But this doesn’t mechanically recontextualize conversations with those characters. You still talk to them the same way. It’s less like getting the Morph Ball or Ice Beam than it’s like finding a series of keys and taking them back to their corresponding locked doors. To really be a Metroidvania, the player character should get new abilities that change the way they interact with the old content. So that’s what I set out to do in Detectivania.
Was It Successful?
As a proof of concept, yes. Detectivania is, in fact, a dialog-based game in which you gain abilities that recontextualize previous content (in this case, interrogations) and allow you to access new content (the truth about your suspects' alibis and motives). However, it barely scratches the surface. Only a couple of abilities actually mechanically change your interactions while others are more like the focus points - a series of keys that differ only cosmetically.
There’s a lot more room for exploring the concept of a conversational Metroidvania - one possibility would be to take inspiration from the Ace Attorney franchise and gain abilities like noticing physical tells or emotional contradictions on top of the spoken dialog. Things like this could genuinely reframe conversations in surprising ways, and not just feel like you’re finding and following a trail of breadcrumbs.
Also, my playtesters seemed to find the game amusing and enjoyable to play, so I’m calling that a win.
Time Spent
I used Toggl to track my time spent on this project (and Online Chart Tool to make the below chart based on the data). Times are approximate, as I’m not used to time trackers and I sometimes forgot to hit the button and had to estimate after the fact.
In total, it took me about thirty-nine hours to go from zero to release. That time was broken down as follows:
- Research: Three hours. Learning about the technologies I planned to use (Twine and Harlowe in this case) and getting set up with them without working on anything specific to Detectivania.
- Planning: Two and a half hours. Designing the game’s structure and plot elements, outside of directly working on them within the game itself. (In this case that meant figuring out everyone’s personality, alibi, and motive, along with where the player would learn and use each skill.)
- Development: Twenty-nine hours. Working directly on game code. This bucket includes research or testing that was directly part of development - looking up how to do specific things or quick tests of new features or refactors.
- Assets: One hour. Creating images, music, or other resources to be used in the game. (In this case, that meant creating the logo seen on the splash screen as well as a wood grain background image that I did not end up using.)
- Testing: Two hours. Having other people play through the game start to finish and getting their feedback.
- Infrastructure: One and a half hours. Setting up external systems related to the game - a git repo, an Itch.io page and profile, etc.
I don’t find anything surprising about this breakdown, but I’ll see if it changes for future projects.
Technical Challenges and Lessons Learned
Twine isn’t a language.
Twine is basically an IDE that compiles any of several different “story formats” (read: languages) to HTML/CSS/JavaScript. Support for a few story formats is built-in; custom story formats can be added easily. Story formats tend to be similar-ish since they are all built to create similar-ish types of stories/games, but they vary unpredictably in syntax, terminology, and capabilities.
I don’t know why Twine was set up this way and it might have major upsides that I’m not yet seeing, but as a newcomer it seems like a really bad decision. It means that before you can even get started developing with Twine, you have to pick a story format to work with. It means that when you ask for help online, you have to remember to specify which story format (and for some of them, which version of it) you’re working in before anyone can provide any help. It means that when you search online for tutorials or documentation, what you find might be useless for your own project. (I found a tutorial for implementing an inventory system written for one format (SugarCube) that said you could create the menu item to open the inventory by creating a passage called StoryMenu
; it took me some time to discover that the equivalent of this in the format I was using (Harlowe) is updating a built-in hook called ?Sidebar
.) It also means that when someone wants to make it easier to create certain kinds of content in Twine, they don’t make a plugin or library; they make a custom story format. So if you want to use one, you can’t just drop it in; you have to switch to writing for it instead and adopt whatever syntax and terminology it uses.
This is all made somewhat worse by the fact that there are also two quite-different versions of Twine itself, each supporting a different set of built-in story formats. Twine 2 is not a strict upgrade over Twine 1 but makes some substantially different design and implementation decisions, and as a result there are still Twine 1 stalwarts who recommend against using Twine 2. However, development ceased on Twine 1 some years ago while Twine 2 continues to improve, so the community seems to be increasingly standardizing on Twine 2.
I ended up going with Twine 2 and its default format Harlowe which is supposed to be concise, powerful, and beginner-friendly. Because it’s the default, there’s some good getting started guides for it, along with an increasing number of tutorials. For example, once I knew to add “harlowe” to my search for “twine inventory tutorial”, I easily found a great one.
Twine (the IDE) has limitations.
Twine 2.2.1’s UI has several flaws that resulted in me supplementing it with Notepad++. You can only open one passage (the units of a Twine story, corresponding roughly to a screen or page) at a time, so there were times I’d copy the contents of one passage into Notepad++ for reference and then open a second passage in Twine for editing. Sometimes I’d even do the editing in Notepad++ because Twine’s undo/redo was very flaky, there’s no Find feature within a passage, and the cursor would be displayed in the wrong place when next to or inside of paired quotes.
It also would have been nice to have autocomplete suggestions for passage tags and variable names (on multiple occasions, I caused bugs by referencing variables named like $norfair_motive_known
when the correct name was $motive_norfair_known
). Tags could have been more useful too - I didn’t find a way to search for or filter to passages containing a given tag, or get tags to display in the storyboard view. And it aggravated me that selecting multiple passages and dragging them together on the storyboard did not always move them all the same amount, messing up my careful arrangement.
I know there are other frontends for Twine development - I ran into a few references to one called Twee. If I make another Twine game, I’ll look into this and see if it provides a better experience.
Harlowe has limitations.
Having used several other languages, I was surprised by some of the behavior and limitations of Harlowe. For example, there doesn’t appear to be a supported way to check whether a variable is defined. Uninitialized variables are equal to 0
, but this is undocumented and could change at any time. So if you’re doing anything complicated and trying to be future-proof, you need to initialize all variables up front. At one point I tried to just guard against uninitialized variables by checking whether they were equal to 0
, but even this is harder than it should be because there isn’t any short-circuit evaluation. In my first version of the game’s Skills menu, I was checking on the $skills
variable with something like this:
(if: $skills is not 0 and $skills's length is not 0)
When this code is evaluated before $skills
is defined, $skills
is equal to 0
and the first part of the conditional evaluates to false
. But the second part gets evaluated anyway, resulting an an error reading You can't get data values from the number 0.
There were a number of quirks like this that never stopped me but did surprise me and required me to adjust my approach. It’s unclear to me how typical this is of Twine story formats. If I make another Twine game, I will likely check out SugarCube, a different story format that seems to be more popular and might be more powerful.
Harlowe’s “Undo” erases more than it says it does.
My single biggest challenge with Harlowe came from its built-in undo
command. It acts like a Back button to return to the previous passage - the documentation says it “send[s] the player to the previous visited passage and forget[s] any variable changes that occurred in this passage.” But based on my testing, it seems to also forget changes that occurred in the previous passage, restoring the state as it was when the player started that passage rather than when they left it. It undoes two turns.
If the player clicks a link to, say, pick up a key in the Hallway passage, the inventory variable gets updated. If the player then goes to the Inventory menu passage, they will see the key listed. But if they then go back to the Hallway passage via undo
(which is the standard way to handle that kind of thing), the inventory variable will be reset to when the player entered the hallway and they won’t have the key anymore.
By default, games written in Harlowe provide the player with Undo and Redo buttons at all times. I’m always reluctant to take options away from the player, so I tried to design Detectivania around the presence of the Undo button. Variable manipulation mostly happens on transition between passages rather than within a passage. When the player gains a skill, they are taken to a short intermediary passage describing the new skill where the variable updates occur, and then taken back to the passage they were in before. With this setup, any Undos still make sense.
This mostly worked, and I was able to avoid bloating the storyboard by making a generic “Skill Get” passage and just setting some variables before sending the player to it (you unfortunately cannot pass variables to a passage, so they had to be global). So, in an interrogation passage there’d be a line like this:
"Are you trying to (link:"bluff")[(set: $skill to "bluff")(goto: "Skill Get")] me, Detective?"
That line creates a clickable link on the word “bluff” in the line of dialog. Clicking that link sets the variable $skill
to the value "bluff"
and then moves to the “Skill Get” passage, which is coded like this:
(set: $skills to it + (ds: $skill))
SKILL REMEMBERED!
You remembered the ''$skill'' skill! (text: $skill_descriptions's $skill)
(link:"Return to the interrogation.")[(goto: $last_interrogation)]
This code adds the new skill (“bluff” in this example) to the $skills
data set, tells the player they’ve gotten the specified skill, and then spits out the skill’s description (stored in a map called $skill_descriptions
keyed by skill names). Then it shows a clickable link reading “Return to the interrogation” which sends the player back to the last interrogation passage they were in. (That variable gets updated every time the player enters a passage tagged ‘interrogation’, thanks to (if: (passage:)'s tags contains "interrogation")[(set: $last_interrogation to (passage:)'s name)]
executed in a Header passage.)
Thus, even though there are about a dozen skills the player can get throughout the game, I was able to handle them all with a single passage. I used a similar technique in the passages seen below whose names start with “Macro” - there is no way in Harlowe to create custom macros (Harlowe’s term for a function or method), so instead I created reusable passages which I called with the (display:)
macro, which is essentially an eval
command.
Unfortunately, there were a few places where I could not find as clean a solution to the Undo problem. At one point in the game, when the player attempts to return to the Parlor passage they are interrupted and sent to a different passage first. This is accomplished via a check at the beginning of the Parlor passage which sees that the game is in a certain state, redirects to the interrupting passage, and flags the interruption as having occurred so it doesn’t happen again. However, if the player attempts to Undo past the interrupting passage, they are taken back to the Parlor passage with the flag cleared - so it sees that the interruption is needed and sends the player right back to the interrupting passage. This completely breaks the Undo button.
In hindsight, I suppose I could have moved the check to the previous passage, hijacking the links that normally return to the parlor and having them instead go to the interrupting passage if the game is in the right state. But I noticed the problem shortly before my release deadline and Detectivania is carefully designed such that you should never need to Undo, so I simply removed the Undo/Redo buttons after all.
Make a Test Start passage.
While editing a game, Twine allows you to start from any passage. In my testing and debugging, I found it incredibly useful to have an inaccessible “Test Start” passage that just contained links to set the game to various states and then a link to the Parlor passage that serves as the game’s hub. It looks basically like this:
(link:"Get Basic Skills.")[(set: $skills to it + (ds: "speak", "word", "sentence", "question"))]
(link:"Get Press Skills.")[(set: $skills to it + (ds: "detect lie", "read mood", "intimidate", "charm", "contradict", "bluff"))]
(link:"Get Accusation Skill.")[(set: $skills to it + (ds: "accusation"))]
(link:"Learn Alibis.")[(set: $known_alibis to (ds: $norfair, $brinstar, $tourian, $maridia))]
(link:"Learn Motives.")[(set: $known_motives to (ds: $norfair, $brinstar, $tourian, $maridia))]
[[Parlor]]
I could easily test late-game interrogations without having to play through the first half of the game by just starting from this passage, setting the right state, and entering the parlor. I used this many, many times.
Design Challenges and Lessons Learned
Play testing is huge.
Detectivania would be far worse without the help of my two playtesters, Allie and Senpai-chan. They both played the game twice at various stages of completion and provided incredibly helpful feedback and suggestions. I’m indebted to them both and intend to continue to hound them to test my games in the future.
Even without the things that they said, just watching them play was significant. It’s amazing to take a game that seems clear to you because you’ve been staring at its innards for a dozen hours, plop it down in front of someone else, and immediately see how opaque it actually is. I’m so glad I didn’t try to release this game without showing it to people first.
Constraints breed creativity… and frustration
Detectivania was written structure-first. I started with the idea of the “conversational Metroidvania,” which led me to the mystery genre to justify having many lies and reveals which seemed like an effective and understandable way to recontextualize dialog. I decided four suspects was a good number - small enough to easily keep them all straight, but large enough to provide some variety - and that led me to three cycles of four skills each that would take you through each suspect’s motive and alibi before you could finally solve the case. Only once that was all sketched out did I nail down the setting, let alone the characters.
Since the player character would be picking up skills that a normal detective would already have, I realized I could do a Metroidvania-style A Taste of Power / Bag of Spilling opening where you start with all the skills and then immediately lose them. With that in mind, my first concept was that the game would take place on a spaceship (or possibly a fantasy setting) so that someone could hit the player with a “deskilling ray” (or an amnesia spell) to sabotage the investigation. I rejected this pretty quickly, though, because I wanted the game to be about the Metroidvania structure. I wanted the player to not have to spend any time or energy understanding the rules of the setting so that it could all be spent understanding the rules of the game. So I went with a bog-standard Agatha Christie knockoff setting instead and a very simple crime.
To populate the game, I took inspiration from Super Metroid. The lord of the manor’s name, Arasmus, is a play on Samus Aran while the missing heirloom is based on a Chozo statue. Each NPC’s name and personality is based on a region from that game, and in fact the code refers to the characters by the names of the regions - I didn’t pick their real names until the very end of development.
- The butler Terrance is named for Crateria. It’s the rocky surface region that you start with and return to, so Terrance is professional but distant and is where the game starts and ends.
- Fabien is named for Norfair. It’s filled with heat and lava, so Fabien is angry.
- Brianna is named for Brinstar. It’s easy to navigate and full of small creatures, so Brianna is polite and friendly.
- O’Riain is named for Tourian. It’s metallic and technological, so O’Riain is coldly logical.
- Merida is named for Maridia. It’s a lively aquatic region that’s difficult to navigate, so Merida is enthusiastic but scatterbrained.
- Bonus - Brianna needed to reference a relative, so I invented Great Uncle Lacour, named for Alucard from the other most famous Metroidvania, Castlevania: Symphony of the Night.
Once I had these personalities, filling in their dialog as dictated by the game’s structure was simultaneously quite easy and quite difficult. I knew that each suspect’s discussion of both motive and alibi needed a response to each of the four skills that the player gets for uncovering lies, only one of which would actually work, and that each skill had to work on exactly one person’s motive and one person’s alibi. Furthermore, since uncovering motives is how you get those lie-uncovering skills, the distribution had to loop through all the suspects so the player couldn’t get locked out of any of them.
Let’s say the player starts out by getting Intimidate from Fabien’s motive interrogation. (You get one free skill from whoever you start with, otherwise there’d be no way to proceed.) They can then use it when interrogating Merida about her motive, resulting in them getting the Contradict skill, which they can use on O’Riain to get the Charm skill, which can be used on Brianna to get the Bluff skill, which can then be used on Fabien to get his motive (which would also reward the Intimidate skill if the player started elsewhere in the loop).
Skill | Fabien | Brianna | O’Riain | Merida |
---|---|---|---|---|
Intimidate | Get from Motive | N/A | Use on Alibi | Use on Motive |
Charm | Use on Alibi | Use on Motive | Get from Motive | N/A |
Contradict | N/A | Use on Alibi | Use on Motive | Get from Motive |
Bluff | Use on Motive | Get from Motive | N/A | Use on Alibi |
I actually wrote myself into a corner initially where two characters formed their own mini-loop, which meant it would be impossible to get all four skills. After that I populated the above table so I wouldn’t screw up again, and then had to write dialog to support it. This was frustrating as a writer - I didn’t get to decide which skill would work on which suspect based organically on the character. It had to follow the game’s structure.
I also believe it made the interrogations less interesting to the player. In the game as written, it’s not really feasible to predict which skill is needed based on your understanding of each character’s personality and situation. It brings the characters and dialog closer to being placeholders, and you get to the truth not by understanding people but by following the mechanical trail. This is absolutely something I’d want to improve in any followup.
Areas for Improvement
There are several features and improvements that were cut from the initial release due to time constraints. If I return to this game to polish it up, I’ll likely implement the following (in roughly decreasing order of importance):
- Interrogation UI improvements. It’s confusing that when you pick an interrogation option that fails, the resulting dialog appears above the options. I’m not sure what the best way to deal with this in a Twine game is, but I’d want to experiment with putting player options in a non-scrolling pane at the bottom while narrative and spoken dialog go in a separately-scrolling pane above it (roughly analogous to a typewriter and the paper scrolling out of it). And possibly player dialog would be styled differently - maybe put in a speech balloon or similar.
- Save game management. I know Harlowe provides a mechanism for this, but I haven’t looked into it yet. Detectivania is short enough that I’m not too worried about launching without it, but it does trouble me that if you accidentally navigate away from the page, refresh it, or close the browser tab you lose all your progress.
- Motive discussion in final reveal. Without getting into plot spoilers, this is an element that’s missing and I think the ending suffers for it. The player is trained to consider everyone’s alibi and motive; the final reveal covers the former and not the latter which could leave them wondering.
- Better menus. I’m not totally sure what the Notes menu should look like, but I’m picturing something like the mission briefings in GoldenEye 007, possibly with character portraits for the dossiers. And I’d love for the Skills menu to look like the equipment screen in Super Metroid.
- Optional skills. Right now, every skill aside from “read mood” must be collected to complete the game. I had ideas for two more optional skills that didn’t make it into the game: “shout” and “whisper”. You could toggle between these or normal speech in the Skills menu, and they would make your dialog uppercased or lowercased respectively. They’d also trigger a few extra reactions from suspects, making them wince and pull back or strain to hear and lean in respectively. They’d have no actual effect on gameplay.
- Music. This would be a fairly easy way to enhance the mood. Given the generic setting, I think I could probably find fittingly generic royalty-free music to go along with it as well.
- Minor visual and textual improvements. It bugs me that the sidebar menu items are not all the same width, and the menu should probably be positioned statically so that it stays accessible even as you scroll the page. The stars in the “SKILL REMEMBERED” message should be vertically centered (and the text on the “SKILL… well, hmm” screen could be clearer about what’s happening there). There’s a semi-inconsistent delay on the appearance of some text, most noticeable on the “SKILL REMEMBERED” screens, that would be good to figure out (it seems like HTML and Markdown content get displayed instantly while other text appears gradually, but I haven’t looked into it yet). The color changes on visited links are confusing since they don’t reflect whether you’ve interrogated a given suspect with your current set of skills.
In Conclusion
As the game that kicks off my learn-to-make-games-in-2019 project, as well as my first game ever, I am really happy with how Detectivania turned out. It was a proof of concept for conversational Metroidvanias, but it was also proof that learning a game development platform and making a game with it in less than a month is possible.
I’m proud of what I’ve built here and excited to move on to the next game and all I’ll learn from it.