Isaac rubs his back on non-existent doors
Mar 20, 2013
4 minute read

Haven’t blogged in a while. Life’s fine, project are a-plenty, but I just wanted to make a more lasting post about one particular issue that struck me as funny when programming Paper Isaac.

Bugs, bugs, bugs

What’s infuriating when letting others play an early prototype is that you hear constantly the same things. Some bugs are non-trivial to fix, some you’re just not motivated to fix now… sometimes you just have your head elsewhere, gotta focus, or are elbow-deep in some other piece of code and the damn walls can wait.

But one particular issue became painful in testing recently against some tough enemies. When sliding along walls, Isaac got stuck on non-existent doors.

What?

Everything is better with a schematic

Here, Isaac (in cyan) tries to escape from some danger on the right by carefully walking along the left wall. Unfortunately he got stuck around the middle of the room. The only way to make him move again would be to stop pressure against the wall, take a small sidestep, and keep going straight down.

As you can see, the walls are actually made of three parts, even when there’s no door. There are two line segments (red), and a rectangle (orange). The rectangle corresponds to a door. If there’s no door at this place in the room, or it’s closed, or locked, or in anyway non-walkable, the shape is solid, and Isaac can’t walk through it.

However, if it’s open, Isaac can walk through it, the collision between types HERO and DOOR will be detected, we’ll get the userData of the shape of type DOOR, figure out which direction we’re trying to go, and then finally tell the game “Hey buddy, we’re changing rooms, so get busy loading the state of the next room”.

Solutions, solutions, solutions

Like on many occasions in programming, you have two opportunities: do it right, or make it work. I chose to make it work. The “right” way, I think, would have been to have only one line segment when there’s no door at all, and then our usual two segments and one rectangle when there’s a door.

But wait! There are secret rooms and super-secret rooms, that you can open by bombing the wall where there’s supposed to be a passage. And then we have to change the geometry of the wall, destroying the unique segment, recreating the two segments + rectangle, etc., etc. - we can get it right for example with a state machine and carefully controlled state changes, but is it worth the headache? Nah.

Instead, I did something much simpler… that I beat myself for not doing earlier. Notice how Isaac uses a square shape for collision detection up there? Well, why not use a circle instead? And indeed, using a circle does the job. Sure, Isaac bumps a little when he runs into the edges of doors (even nonexistent ones) - but it’s not disruptive to the gameplay, and I’m pretty sure the geometry is always right.

But wait, there’s more!

While we have successfully fixed our above problem, our doors still kinda suck. Right now, even touching ever-so-slightly the door takes us to the next room. Sometimes it’s really not what you want to do. When you end a combat and just happen to be next to a door, it doesn’t mean you want to go through there.

What we need is to be much smarter at detecting when the player wants to change room. I haven’t implemented that yet, but I have an idea… every frame, we can test if a player is in contact with a door, and actively moving in that direction (for example, pressing the S key while in contact with the bottom door). If that happens for more than, say, 15 consecutive frames (which would be 250ms), then we’re pretty confident the player wants to change room.

The same logic can be applied to unlocking doors, but we can use a higher threshold here (e.g. 600ms) because the player does not want to waste a key - passing along a door doesn’t mean you want to spend a key opening it.

Conclusion

Anyway I’m going back to hacking more enemies in there, doing more drawing, and perhaps more music before the end of the month, that was fun, see you at the finish line.