Purgatory Purgers: Version 1.5 Released!

Update Summary

  • Collecting 100% of the souls with Bob (the demon) is now possible!
  • Resolved clipping issues in the levels “Stilted Pond” and “Toad Hall.”
  • Gamepads, including PlayStation, Xbox, and Super Nintendo-style controllers, are now generally supported*. See the notes below for additional information.
  • Updated the Purgatory Purgers Manual to include gamepad button mappings

Special thanks to @ABitNosthalgic for suggesting that we make it possible to collect 100% of the souls with one character or another!

Gamepad/Controller Support Notes

*Most PC-compatible controllers with a Direction Pad (D-Pad), Start and Select buttons, and four face buttons should work. The game has been tested on the following devices:

  • Logitech F310
  • X-Box Elite Series 2 Wireless
  • Megafire 412-NO5 (generic PlayStation 2 Style Controller)
  • 8BitDo SN30 Pro (SNES-style controller)

@ABitNosthalgic reported that the Suily brand NES-style controllers did not work for him. While I haven’t tested this myself, the reviews seem to suggest that problems, particularly with the D-Pad, aren’t that uncommon.

Purgatory Purgers: Version 1.4 Released!

Update Summary

  • Resolved a bug (warning, spoilers!) that would destroy objects unintentionally when two blocks collided
  • Added a spawn/respawn animation and sound effect
  • Reworked the Passphrase mechanic (used to continue to a previously visited level) to display the Passphrase on the pause screen under the main menu as well as the Game Over screen.
  • Updated the Purgatory Purgers Game Manual to better describe the Continue/Passphrase mechanic and other menu features.

Special thanks to @ABitNosthalgic for catching the bug and Jason D for passphrase feedback!

 

Purgatory Purgers: Buggery

Warning: Spoilers Ahead!

This post discloses the locations of some of the secret rooms found within Purgatory Purgers. If you’d rather discover these surprises for yourself while playing the game, you may want to avoid reading further!

A Fresh Perspective

Players are often your best resource for identifying possible bugs, exploits, and missed opportunities (read: feature requests). The week Purgatory was released, I received a lot of feedback and, with it, fresh eyes to point out my mistakes.

I addressed what I could and tabled the rest, save for one elusive bug I could never pin down but appeared so infrequently that I couldn’t get a bead on it. Occasionally, an object or group of objects (e.g., a gem block, ethereal coins, etc.) would go missing from a room, and I couldn’t explain why nor duplicate the behavior. Unable to identify the culprit, coupled with the sporadic nature of the bug, I decided to leave well enough alone…

Recently, a new YouTube channel, “Back to the Past Gaming,” started a playthrough series for Purgatory Purgers. While watching ABitNosthaligic‘s playthrough of the fourth level of Purgatory Purgers, “Stilted Pond,” I was surprised to see that the secret room he discovered was empty:

He suggested it might have been an oversight, and while it’s certainly possible that I’d forgotten to populate the room, the problem was (unfortunately) not that simple… And so, after a 3-month hiatus, I decided to tear back into the code to under, once and for all, why this was happening!

First, Check the Obvious

Looking at the room layout in the GameMaker IDE, I could see the objects had been placed:

So why were they missing during his playthrough? I fired up the game, went straight to the offending level, and made a beeline for the secret room. Upon entering, there were the Ethereal Coins right where I expected them to be:

To try to duplicate the [unwanted] behavior, I did a normal playthrough, and lo behold, the coins were missing for me, too! Progress, at last! But why? What exactly was happening to the coins? Were they failing to render? Were they being destroyed?

So, to begin my troubleshooting, I added the following code to the “obj_oneup” object’s Draw GUI Event:

// Am I here?
draw_self();
draw_text(20,20,"I'm still here!")

Whenever you use custom draw code (e.g., draw_text), you must precede it with “draw_self(),” or the sprite won’t be drawn. The purpose of the draw_text message is to print the string “I’m still here!” in the upper left-hand corner to let me know that at least one instance of the object still exists on the map. Simply put, these two lines of code tell me that the object is both visible and present.

I saved my work, then launched the game, and upon entering the first level, I could see the message right where I expected it:

After collecting the Ethereal Coins, the message disappeared as expected. So, I continued playing until I reached the offending level (Stilted Pond), and after some experimentation, I was finally able to observe what was happening…

Next, Eliminate the Impossible

“When you have eliminated the impossible, whatever remains, however improbable, must be the truth.”
– Sherlock Holmes, The Sign of Four

Whenever I pushed one block into another block, all of the Ethereal Coins in the current room were instantly destroyed. This was true, regardless of level, but I couldn’t yet understand why…

The way I coded movement for blocks, enemies, and the player in Purgatory Purgers was a modified version of my grid movement system, explained here and here. In short, here’s what happens when you “push” a block:

  1. An instance of an invisible 16x16px target object is created 32 pixels ahead.
  2. The block moves toward the target object.
  3. Upon collision, the target object is destroyed, and the block’s movement speed is set to zero, stopping it.

Because we’re using an Instance Variable, it has to be declared in the Create Event, e.g.:

block_target=0;

This variable would later be defined in the Step Event whenever the block was moved, something to the effect of,

block_target=instance_create_layer(x-bdist,y,"Instances",obj_block_target);

In the example above, “block_target” is the variable that refers to the Instance ID of the specific instance of the obj_block_target object we’re dealing with and nothing else! So when obj_block collides with the instance of obj_block_target that it created, i.e., “block_target,” only that instance should be destroyed, but somehow, the obj_oneup was being [mis]associated with “block_target.”

Block movement was one of the first features I coded, and later implementations used the place_meeting function in the End Step Event. So, simply recording it to do that fixed the issue.

I still don’t fully understand this, and after hours of troubleshooting, I am relieved that this was resolved. I have since posted the fix to itch.io as version 1.4.

Purgatory Purgers: Playthrough!

I am pleased to share that @BacktothePastABitNostalgic, a new YouTube channel specializing in retro-style content, has started a playthrough series on Purgatory Purgers!

You can access the Playlist here or by clicking the image above.

Thanks again, ABitNostalgic, and I look forward to seeing the rest of the series!

Purgatory Purgers: Simple Music Control

Background

In past projects, I controlled music using the room creation code, e.g.:

audio_stop_all();
audio_play_sound(snd_music1,100,true);

This would stop all previously playing audio and start the new music track, looping it until the next room was created.

Functionally, this works fine, but I wanted a more centralized way of handling this so that if I wanted to make a change, I didn’t have to do it N number of places, equal to the number of rooms I created.

How It Works

I created an object called “obj_sound” and placed it in the first room, then I flagged it as ‘Persistent’ so that it would carry forward from room to room.

I added a Room Start event with the following code:

if room==rm_level1 {
audio_play_sound(snd_lvl1,100,true);
}

if room==rm_level2 {
audio_play_sound(snd_lvl2,100,true);
}

At the start of the room, it checks to see if the name of the room matches “rm_level1” (the name of the first room/level), and if it does, it plays the sound “snd_lvl1” at a priority of “100”, and sets looping to “true.”

Once that was working, we needed to be able to stop the previous music track before loading the next one. This can be done using a Room End event:

audio_stop_all();

This stops all audio before the next room is loaded, preventing tracks from playing over each other.

Conclusions

There are other ways this could be handled, however…

  • It works for what I need it to do (starts a looping music track that’s definable by room name).
  • It’s centrally controlled.
  • It does this in the simplest way I know of, and more importantly, I understand it!