Before we dive in, we’re Multithreaded Games and we’re working on a turn-based, gridless, tactical RPG called Wellspring: Altar of Roots—you might remember us from our Kickstarter campaign earlier this year, which by the way, SUCCEEDED HUGELY if anyone asks. I, uh.. er. Anyways, we’re back with another one of them gamedev posts, so stick around and learn about… The Anatomy of a Bug.
This all started on a crisp, cool day in November—yesterday, in fact. I’ve spent some time lately improving some of our cut-scene creation tools and scripts. I figured it’d be simple to pop in and add a bit of extra functionality. Mistake number one.
You see, I was testing a new, but long-overdue feature that allows characters to look at things during cut-scenes. While we’ve been able to do this for awhile during other parts of the game, our junk only needed one small tweak and our characters would be a little more lifelike during cut-scenes. All was working well… or so I thought. Or so anyone thinks right before a gameplay bug rears its ugly, diseased head.
A bug is simply some bit of undesirable behavior on the part of your program. Could be something that you accidentally introduced, some mistaken assumption or piece of faulty logic—it could be something out of your hands entirely. That’s what makes programming so fun! And demoralizing.
Here’s what this particular bug does: about 60 seconds into the cut-scene, all dialogue windows abruptly close, as shown above. No matter what’s written on the screen, or what the progress of the cut-scene is or where the characters are positioned—wham! Lights out, words.
Normally, this wouldn’t be as big of an issue, because a later part of the cut-scene would, in theory, display the dialogue windows again and allow me to at least go through the entire cut-scene. This time was different though—smelled different, even. Like anguish.
As for who was the responsible party, there were some obvious candidates (spoilers: it was actually me, the programmer) For starters, you have this punk-ass plant chillin’ near the end of the cutscene, who could somehow be shutting down the whole thing. Lots of potential causes, folks. (no there isn’t, it was my fault the whole time.)
When trying to hunt down a bug, it’s nice to rule out the obvious or even usual suspects, that way you don’t spend an embarrassing amount of time going down a rabbit hole when the real bug is just on the surface. Unfortunately for me, quickly ripping out all the newly-added code didn’t eliminate the bug, nor did harvesting that plant; the dialogue windows still kept closing themselves, which indicated that there might be a bit of work involved in figuring this one out. The next step, then, was to start… debugging.
Debugging is formally defined as follows: “the art of unbreaking the shit you just broke when you got cocky and inspired enough to work on your game again.” There are many ways to ‘de-bug’, from printing out stuff, to using tools to ‘step’ through your code, or even changing random stuff until it just works.
After changing stuff randomly for seven hours, I finally decided that I’d had enough: it was time to die, little insect. Out came the debugger, a nifty little tool that lets us examine the state of the program at the moment that the bad man started knocking our bytes around. The idea is to find a ‘place’ that I can point the debugger—this place is called a breakpoint, and when the game reaches the code that ‘contains’ the breakpoint, the program will stop running temporarily and let me see exactly what in the dick is happening. Or something like that.
The question then, of all these lines of code, is where do I put the breakpoint? In this case, the most ‘basic’ thing that is happening is that the window is closing or being ‘hidden’. In our codebase, windows can only close by being told to ‘hide’ themselves, and the part of the code that does that is exactly where the breakpoint goes.
So I fire this sucker up and see what happens. Sure enough, about a minute into the cut-scene, the program halts and I get to see what (or who, at this point) decided to hide these windows. While there’s only one way to hide a window, there are a lot of different components that are allowed to tell windows to hide themselves. Whether you’re trudging through dialogue, exiting to the title screen or making a choice given by an NPC, each of these can ‘close’ a window.
After a quick look, what had actually happened, is that the entire ‘interaction’ had been ‘ended’, which is, unsurprisingly, another mechanism by which windows can be hidden. In Wellspring, ‘interactions’ form the basis of well, interacting, bro. When you talk to a character, that’s an interaction; when a cut-scene starts, interaction. There are lots of types of interactions and ways that they can be triggered, but there’s only one function that can ‘end’ an interaction. Its name? EndInteraction.
So at this point, we know that something is prematurely ending our interaction (and therefore our cut-scene), but what? The cool thing about debugging, is that we can look at the call stack, and we can see exactly what object told us to end the interaction.
In this case, it inexplicably seems like the culprit is an… uh… Save Point. Not exactly what I expected, but it’s nice to now have a solid lead. In this region of the game, there are only two save points—one is on the other side of the map and the second one is still quite a ways from where our cutscene occurs.
Save points in Wellspring become ‘active’—and therefore usable—when a player character moves within a certain range of the save point and this range is given by a sphere collider that’s attached to the object. This also triggers a looped animation that rotates the save point and scales it up slightly. Save points become ‘inactive’ when a character moves a certain distance away from them—it’s this inactivity trigger that is calling EndInteraction, which serves a number of purposes—an obvious one is to hide the “Save Crystal” text, as shown above.
This function is responsible for more than just hiding windows, and it could almost be thought of as a reset of the game’s state (including GUI elements) back to what it was before the interaction had occurred. An unfortunate consequence of this is that, because we were kinda lazy, we just straight-up close all windows because a player in a cutscene isn’t ‘allowed’ to interact with anything anyhow. We made an assumption that any object tagged as a ‘player’ would have to be part of the cutscene, thus it seemed like an OK thing to do. When something seems like it’s OK to do, it’s 100% a red flag.
After a little more investigation, I figured out that the save point on the whole other side of the damn map is somehow (to recap), after a minute into the cut-scene, closing said cut-scene. Not cool.
“Why are you doing this to me, save point?!! I loved you!” I cried out. No response. Why was it happening after about a minute though, every time?
The next step was to just look at the state machine that dictates the save point’s behavior. To speed up development, we use a Unity tool called “Playmaker” (which we unfortunately discovered way too far into development) to quickly create behaviors that can be attached to objects. In this case, we can see that the save point is, in fact, being triggered by a ‘player’ moving within its proximity, which is what the “TRIGGER ENTER” and “TRIGGER EXIT” from above signify.
At first glance, this doesn’t seem like it should be able to occur, as all of the main characters are on the completely other side of the map. And main characters, as I’ve been assured (by myself) are the ONLY folks that can trigger Save Points.
I should also add that not only does this occur once after sixty seconds, but the save point actually gets triggered and then un-triggered roughly every sixty seconds. Hrm. Well, let’s just head on across the river and have a gander at this save point, shall we?
All right, we’re here, we’re settled in, got our NPC dude walking around and… NPC dude!? What are you doing with that save point, why is it…? No…
It’s true. It’s all true.
As it turns out, the lowly NPC, Dhorom, not a main character—not even a good non-playable character—is triggering the save point when he gets close enough to it. When he leaves, the save point automatically calls EndInteraction, which then terminates all the windows, including the ones opened by the cut-scene. But why? Why is Dhorom, the NPC, not a player, causing this?
I thought for a second about Westworld’ing Dhorom—send him to cold storage or give him a new narrative or something. But it turns out that this isn’t Dhorom’s first narrative.
Way back in the early days of Wellspring development, the character model that is now the NPC Dhorom used to be a placeholder for the main, playable character Moroch, before we had his finished model. He’s the purple-clad dude you saw in the cut-scene GIFs.
Apparently I had left his layer set to “Ally”, which is only really used to optimize and filter collisions (such as those that trigger the save point)—this wouldn’t matter normally, since I had only left the mesh GameObject (which does not have any colliders) as part of the “Ally” layer. However, when I first implemented ‘looking’ (nearly a month or so ago) I ended up changing the mesh GameObject (you can see it on the right side above), eventually adding a collider to it.
This collider is intended to represent the distance at which Dhorom will passively ‘look’ at the player character—so if the player runs into that sphere, Dhorom will look. However, this collider was now able to trigger the save point since it was under the “Ally” layer. Funny enough, this bug would not have been limited to the initial cut-scene and would have closed any other open windows, every sixty seconds or so. I just never noticed it since I’d been spending most of my time working with the cut-scene.
After all that, the above GIF shows the fix—simply change the layer to Default so that it no longer triggers the save point. Easy peasy. An hour or so of detective work culminating in three seconds worth of fixes—could definitely be worse.
Well, I think that about does it. Hopefully you enjoyed this tiny glimpse into the thrills and woes (mostly just the woes) of game development and how a small, unrelated change can have some pretty strange and far-reaching repercussions.
If you’d like to try our free demo, follow our progress or chat about the game, here are a few links: