Isaac rubs his back on non-existent doors
👋 This page was last updated ~13 years ago. Just so you know.
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.
Did you know I also make videos? Check them out on PeerTube and also YouTube!
Here's another article just for you:
A dynamic linker murder mystery
I write a ton of articles about rust. And in those articles, the main focus is about writing Rust code that compiles. Once it compiles, well, we’re basically in the clear! Especially if it compiles to a single executable, that’s made up entirely of Rust code.
That works great for short tutorials, or one-off explorations.
Unfortunately, “in the real world”, our code often has to share the stage with other code. And Rust is great at that. Compiling Go code to a static library, for example, is relatively finnicky. It insists on being built with GCC (and no other compiler), and linked with GNU ld (and no other linker).