How Donkey Kong Country 2’s Movement Feels Amazing (Part 2)

This is Part 2 of a 3-part series. Check out all the parts here!

Part 1: Here!
Part 2: This post.
Part 3: Here!

As a child, I loved to play Donkey Kong Country 2: Diddy’s Kong Quest; as an adult in my early thirties, I still love playing it. One of the reasons, among many, why is the game’s character movement feels almost perfect. Never do I feel as though I can’t adequately control Diddy or Dixie Kong as I navigate them through many levels of perilous adventure. As a game designer, I found myself wondering: what is it, exactly, that makes me love DKC2’s movement so much?

To answer that question, I’ll take a close look at the action verbs DKC2 employs related to the player characters. It’s not just a matter of analyzing the actions and movements in and of themselves, however; the level and enemy design of DKC2 are intentionally crafted to augment those basic and special actions. As is undoubtedly true for many games, it is the whole package together which creates that feel I love so much.

In the previous part, we took a look at the specific actions the Kongs can make, from walking and running to jumping and throwing, as well as the various special forms of movement, like climbing and swimming, and the animal buddies which modify how basic movements work. The actual abilities are only part of the overall story, though; just as critical as how you can move is where you can move. Without level design that compliments the possible actions you can take, a game won’t be as fun as it could be.

Exploration Evaluation

There are 40 regular levels, excluding boss stages. Many challenging sections are included in those levels, often increasing in difficulty as you progress from the early stages to the later ones. While some stages involve special gimmicks, a lot of times the challenge is derived simply from taking the actions you are familiar with and asking you to execute them well to progress. Let’s break down some of the ways this is accomplished.

Basic Navigation

First of all, because the Kongs’ movement is so well-tuned, it’s fun on its own to just move your Kong throughout the level. The short accelerations make the movement feel more smooth while not taking away from your control, like sometimes happens when using long acceleration times. The minimal jump scaling gives you just the right amount of height, and the timing on how much you scale seems perfectly balanced so that you can get the exact jump height you want. The spin move feels fluid and lasts just the right length of time so that you don’t feel like you lose control when you use it. All in all, simply moving through the level is fun.

Beyond moving around, the level structure itself is used to make it easy to figure out where you need to go. In many cases, you are asked to move right (for horizontal levels) or up (for vertical levels). By employing a consistent pattern for where you are expected to move, it makes it hard to get yourself lost in any given level. Some levels are “square”, and in these you start traversing from right to left (or, sometimes, right to left); you eventually reach a vertical section of the level, either up or down, and you then traverse the horizontal plane in the opposite direction from which you came, until you reach the next horizontal section. There are a few cases where this formula is played with, for the sake of variety, but it is never done in a confusing way; you still always know where you should be going.

This is an underrated part of feeling good about your movement; even if it’s fun to move your character around, it’s a lot less fun to not know where you need to go to make progress, and getting lost repeatedly is quite the powerful buzzkill. By making it hard to get lost in any given level, Donkey Kong Country 2 removes a potential source of frustration, and thereby makes everything feel more fun.

Collectibles

Each level also contains sections that have goodies in them, places you are meant to discover and be rewarded for the effort of doing so. Some of these are sprinkled through the course of the main path. Oftentimes, though, these goodie sections are placed in locations that go against the grain of expectations, such as to the left of the level start. A lot of times, these places aren’t particularly hard to reach, and they are intended to reward you for exploring outside of the conventional expectations.

Sometimes, though, collectibles are placed in ways that challenge you to show mastery of your moves. Kong letters are often used in this way, being positioned in such a way that picking them up involves doing some difficult maneuver, like spinning off a platform and jumping at just the right time to both collect the letter and avoid falling to your death.

The ultimate collectible is the DK Coin (also known as a Hero Coin). These big, shiny coins, with the letters “DK” embossed on them, are used as an ultimate test of movement, observation, and/or timing. There isn’t a gameplay advantage to collecting them; your reward is the thrill of finding the coin and procuring it through perilous platforming and top-notch timing.

Checkpoint Barrels

Each level in Donkey Kong Country 2 has a single checkpoint barrel, roughly halfway through the level. When a checkpoint barrel is broken, if you die later on in the level you will start the level from this checkpoint barrel’s location instead of the beginning of the level.

How does this tie into movement, precisely? It adds a reprieve from having to play perfectly without dying; if you had to play an entire level without any checkpoints, it would make the game more difficult and tense to play, and thus it would be harder to justify using difficult action sequences later on in the stage. By only having one checkpoint, levels have a clear first-half/second-half dynamic that often allows for introducing level gimmicks and gameplay in the first half, and following them up with more challenging versions of those mechanics in the second half; having more checkpoints would either make individual sections feel too short, and thus not interesting enough, or too difficult, making the whole level feel more like a slog. One checkpoint barrel per level feels like just the right amount, to me.

That analysis on checkpoint segments applies to the 3D Donkey Kong Country games, which use multiple checkpoints per level. It’s one of the reasons I prefer the older games to the modern ones.

Finding Bonus Areas

Each level contains at least one bonus area to find. These bonus areas have entrances that are hidden or obscured in a few different ways. The most obvious ones are bonus barrels, which function like blast barrels that shoot you into the bonus area; while a few are placed in obvious locations, most of these bonus barrels are placed such that they either aren’t visible or are placed in difficult to reach locations, requiring skill to reach. Some bonus areas have entrances which are hidden inside of the level walls, and you need to use a barrel or Rambi’s charge move to break open the entrance.

The final way to enter bonus areas is through Kannons, which require being loaded with cannonballs in order to shoot the Kongs into the bonus area. Whenever you find a cannonball, it always means there’s a Kannon to find in the level, and vice versa, which adds an element of expectation to the gameplay; finding the cannonball first means you need to ensure you keep hauling the cannonball with you until you can reach the Kannon, often requiring agile platforming to avoid dropping it into a chasm; finding the Kannon first means you need to search the level for the corresponding cannonball, and then bring it back to the Kannon.

The ways required to get to bonus areas often involve challenging bits of action use. For instance, a bonus barrel that is too high to jump to means you need to either have both Kongs, so you can team throw into it, or you need to have an animal buddy that can be used to maneuver the required vertical distance (such as Rattly’s jump or Squitter’s platform webs), and consequently that means you need to play well enough to keep your partner or animal buddy around long enough to reach the bonus barrel’s location. Same is true for bonus entrances hidden behind walls; you need to lug a projectile around, or find Rambi and ride him to the bonus area entrance, so that you have something to open the wall with. With projectiles, in particular, that will mean not using them on other baddies along the way, lest you sacrifice your only means of breaking open the wall. Such gameplay subtly adds difficulty in executing moves you are already familiar with, rewarding partaking in these optional challenges with the right to enter a bonus area.

Bonus areas themselves often have gameplay that challenges your mastery of the game’s movement mechanics, but since these are the same as what you encounter in normal level gameplay, just harder, I won’t go into detail on them.

End Level Targets

To wrap up our look into Donkey Kong Country 2’s level navigation, let’s investigate the end-of-level mechanic that is used. It consists of a target and pole, akin to hammer games you might see at a fair or carnival. The mechanics of how it works matches those counterparts as well: if you hit the target with enough force, you cause the barrel on the pole to shoot up and knock down the reward on the top; otherwise, it simply gives a lackluster bump as the Kongs scamper off stage. What reward you get is randomized between 3-4 options of differing value, from single bananas to banana coins to extra life balloons (and, in a few cases, Kong letters or Hero Coins).

Simply jumping from ground level onto the target isn’t enough to claim the reward; you need to find a height tall enough to jump from, or you need to find a blast barrel that will shoot you into the target. While it’s usually pretty obvious where you’ll need to go to get the necessary force to break the target, it’s still a small test of observational skills nonetheless.

Most of the challenge of these end-level targets, however, comes from timing your jump or blast to coincide with when the reward you want to claim appears. The cycle time is relatively short, so usually waiting to launch at the target until your desired reward is displayed results in not hitting the target until the next reward has been cycled in. Thus, you need to pay attention to the pattern to figure out when the reward you want appears in the cycle of items, and then launch shortly after the prior reward appears, ensuring that you’ll arrive on target at the correct moment.

There’s usually no threat of death during these sections, so it’s merely a matter of finding the right place to launch from, and then timing it right to get the reward you intend. It’s a simple little challenge, adding a less tense form of skillful challenge to wrap up the more difficult sections of level preceding it.

Platforming Perils

There’s more to Donkey Kong 2’s level design than just navigating the levels. Often, you the player are given challenging sequences to get past as part of level progression, using both the regular movements given to you and special movement mechanics. The degree of challenge expected from each varies, generally starting with simple asks in the first worlds, to downright difficult sequences in the final worlds. Let’s examine the various ways the game presents these challenges to you.

Level Architecture

Oftentimes, the challenges are introduced through the level’s architecture. A frequent case is the use of gaps between platforms, where you must often use running jumps or spin jumps to get enough speed to clear the gaps. The ground heights themselves are varied throughout an individual level, and those too can be used to introduce challenge, such as having to time your jump onto a higher or lower platform while avoiding a baddie shooting projectiles across that plane.

Speaking of enemy placements, those too are used to introduce platforming challenges. For instance, you’ll be asked sometimes to navigate a huge platform gap by jumping on the backs of flying enemies; failure to do this successfully results in falling downward, often to your death. Even when it isn’t required as part of level progression, jumping off of enemies is also used as a means to access goodie areas or bonus entrances.

The responsiveness and control make it feel as though you can easily pull off whatever moves you need to; there are rarely any sections that are so maddeningly precise that you have to spend an hour or more just to execute the moves needed. That doesn’t mean the challenging sections of the game are easy to pull off; you still need to be precise with your button presses. Rarely, however, will you find yourself stuck on a level section because the actions needed to get past it are borderline impossible.

There are a few exceptions. Looking at you, Animal Antics!

Climbing Sections

Ropes restrict your movement to specific planes, and enemies are placed along the ropes to force you to jump across with good timing to get past them. Taking away some of your freedom of movement makes it clear what you need to do to move on. Sometimes, you encounter “rope net” sections which give you climbing ability across both vertical and horizontal planes, at the expense of reduced horizontal climbing speed; these are a great mechanic to take the rope climbing experience and introduce a different flavor of it.

Another climbing mechanic is the use of hooks. These objects hold your Kong in place when you jump into them, until you jump away from them. Unlike ropes, hooks are all about asking you to move aerially, and oftentimes multiple hooks are placed in succession, requiring you to jump from hook to hook to continue onward. It’s a bit of a combination between jump movement and rope movement.

Blast Barrel Sections

Blast barrels, unlike their projectile counterparts, are not thrown. Instead, you jump into one of these barrels, and while in the barrel your normal movement is taken away. By “firing” the blast barrel (by tapping the jump button), you are launched out of the barrel’s open top, regaining air movement shortly afterwards. With your normal movement taken away, you instead need to rely on aiming the barrels in the correct direction before blasting out.

There are many kinds of blast barrel to play with throughout the game. Some let you rotate the barrel by pressing the directional buttons. Some spin automatically, requiring you to time your blast right when the barrel is facing the direction you want to go. Some don’t rotate at all; these are often aimed straight up, and are meant for you to use your aerial control after being fired to get where you need to go. Alternatively, they can be aimed in a fixed direction, with baddies between you and the next section, again making it a matter of timing to blast yourself out when the enemy is not in the line of fire. Occasionally, you’ll encounter automatic blast barrels, that shoot almost as soon as you jump into them; these are most often used to take you through a predetermined sequence of barrels, or to get you out of one level area to the next one.

There are a few levels which introduce special blast barrel mechanics. Timed barrels are regular blast barrels that automatically fire after an on-barrel timer expires, giving you a short amount of time to align the barrel correctly before getting shot into an enemy or abyss. Tracker barrels, instead of rotating the barrel, instead let you move the barrel itself across the level, and you’ll need to use them to get across dangerous sections of the level before blasting out. Kong Blast Barrels, marked with Diddy or Dixie heads, will only operate if the Kong you currently control matches the head shown on the barrel, requiring you to keep that Kong alive in order to use them.

Why use blast barrels at all, when you have normal movements that feel great to use? An obvious answer is to provide another change of pace to level mechanics, which in general helps with preventing things from getting too stale. Also, since barrels fire the Kongs at high velocities, it adds a speed thrill to the game while controlling it enough to not make it maddening; if this fast speed was your default movement speed, it would be very hard to control what you were doing. Finally, it provides a new way to take skills you’ve needed to use through normal movement (such as timing moves to defeat or avoid enemies) and gives you a new context to use them in. These things combine to make blast barrels interesting and fun, good qualities to add to any game!

Animal Buddy Sections

Some levels give you access to an animal buddy, and while you have the animal buddy you have to use its different movement to get through the level. Oftentimes, the level is designed with this in mind. For example, when having Rattly, the levels subsequently are designed with vertical traversal and jumping as central components for sections of that level. You usually don’t need to use the animal buddy for the entire level, however, and signposts with “no animal” signs tell you at what point the animal buddy will be taken away from you; crossing this signpost causes the animal buddy to disappear in a poof of clouds, along with some reward for keeping the animal buddy up to that point in the level.

Not all levels with animal buddies require you to use them to get through the level. Sometimes, the animal buddies are hidden somewhere in the level, and usually not in obvious places. In cases like these, you can beat the level without them, although doing so is usually more difficult; having the buddy makes the level easier to beat, but places a challenge on locating the animal buddy in the first place. This encourages you to search around to see if there are hidden or obscured level sections that would lead you to finding an animal buddy. This reveals another function of the no-animal signposts: if you see one for a particular animal buddy that you don’t currently have, that’s a sign that one was hidden somewhere in the level for you to find.

Some levels do require the animal buddy to proceed. Most of the time, in these cases, you’ll jump into a special Animal Barrel that transforms the Kongs into the animal buddy pictured on the side. The levels which do this are subsequently designed with far more challenging sections that can only be defeated through good use of that animal buddy’s movements and skills. By forcing you to become the animal buddy, you’re prevented from getting yourself in a position where you lose the animal buddy in a level section that requires their movements to get past. It’s a great way of employing user experience to enhance the level design.

Level Gimmicks

Some levels have their own special mechanics that you have to deal with. For example, in one level, the level is filled with lava, until you jump on Clapper the Seal, who temporarily transforms that lava into water which can be safely traversed. Another level has a wind mechanic, where every so often the wind blows strongly enough to affect all Kong movement, forcing you to have to plan your jumps when the wind is blowing in the direction you want to move. Yet another level features a rising floor, forcing you to make quick decisions about where to go and how to defeat enemies, before running out of room and getting crushed. Most of the time, such gimmicks are only used in that specific level.

How do these gimmicks impact movement? It’s a clever way to test your knowledge and mastery of the various movement mechanics you’ve been introduced to. By adding a special mechanic that changes normal level gameplay, you need to understand how these gimmicks impact your abilities. In cases where your movement itself is directly affected by the gimmick, you also need to apply that understanding to counteract the gimmick’s impact to continue making progress through the level.

Beyond mechanics, special level gimmicks help those levels feel more memorable!

Camera Concoctions

A critical, yet often overlooked, aspect of movement is the game’s camera. After all, it’s a lot harder to make precise movements if the camera isn’t framing the action well! Thankfully, Donkey Kong Country 2’s camera is well designed to enable making your movements with confidence that you won’t get screwed over by it. It’s also used as a way to focus your attention on where you need to be going, as well as hiding goodie sections and bonus areas.

Framing

Let’s start with looking at the basic camera framing. When moving through a horizontal stage, the camera is placed mostly behind you, leaving you a little sight of what’s happening behind you, but giving most of its real estate on the challenges that are immediately in front of you, the direction you need to be going. If you turn around and move leftwards in a horizontal level, however, the camera position shifts to put the Kongs at the center, giving you more sight into what’s happening behind you, without making it feel like this is the “correct” direction to be moving in. It’s one way the game helps prevent you from getting lost in a level.

What about vertical or square levels, where progression can include moving leftwards or rightwards? Here, the camera sits in such a way that you can see more of the area in the direction the Kongs are facing than the direction behind them. This is a good compromise, though it does sacrifice some of the camera’s ability to tell the player where to go. Such instances are usually paired with level design that doesn’t let you move too far in a horizontal direction, however, so if you do end up accidentally backtracking, you won’t lose much time over it before figuring it out and turning to go the correct way.

A technique that is also used is placing sections of level that are impossible to navigate backwards through once you go through them. There are also a few instances where the camera itself is prevented from moving backwards, forcing you to keep moving in the intended level direction.

Motion

Something that isn’t immediately obvious, until you start paying attention to it, is that the camera’s vertical height is precisely controlled. Depending on where you are in the level, the camera’s vertical height is fixed at a predetermined coordinate; as you move through the stage horizontally, the camera’s height is automatically adjusted depending on the height of the terrain you are walking across. When you reach vertical level sections, or swimming and flying areas that involve vertical movement, then the camera follows the player’s movement.

There are certain areas of a level where the camera’s vertical and horizontal movement are stopped. In cases like these, it makes it clear where the intended progress of a level is, and it also prevents the camera from clipping too far off the edge of a level (a place you don’t need to see to make any gameplay decisions). Interestingly, this technique is sometimes used to obscure entrances to goodie or bonus areas, requiring you to watch carefully for telltales signs (such as the lone banana near the edge of the screen) and rewarding you for your observational skills.

Importantly, the camera does not move vertically to follow Diddy and Dixie when they jump. This allows you to not lose sight of the ground, and anything on it you might be jumping towards or away from. It is a subtle way of allowing you to maintain precise control over where you are moving. If this camera mechanic weren’t in place, it’d be easy for enemies to drop out of frame while you’re in mid-air. That would make it much harder to plot your movements, since you’d have to rely on memory to know where you should and shouldn’t jump. It’s a crucial component to making the Kongs’ movements feel good.

Aspect Ratio

A huge difference with Donkey Kong Country 2, compared to modern platformers, is the use of the squarish 4:3 aspect ratio. This was how most TVs were built back in the days when this game was released, so that was the only supported resolution the game had. Unlike the rectangular 16:9 aspect ratio, which is what computer monitors and TVs commonly use these days, 4:3 gives you much less horizontal screen real estate, so there’s less room to tell you what’s coming up.

Having to account for this limited width of frame has a lot of influence on the game’s design. For example, rarely do you see long and wide sections of challenging gameplay, and the times you do encounter them are specifically designed with the obscured view in mind, forcing you to react quickly to what you see as the camera reveals what’s coming next; consequently, you need to know your movement well so you don’t have to think about what to do when you see the next challenge in frame; you can just instinctively react to it. We don’t often think of aspect ratio as a part of the game designer’s arsenal, but the limited nature of the aspect ratio in DKC2 is clear evidence that it does, in fact, play an important part of how a game’s design feels.

To Be Kong-tinued

In this part, we examined many different ways the level design of Donkey Kong Country 2 takes the movement mechanics of the player and provides exciting, challenging ways to use them. We also examined the various special forms of gameplay, which either incorporate special movements or enhance the challenge of your regular movements. Finally, we took a look at how the game’s camera itself plays an important role in how the movement feels. No matter how good a game’s character feels to move, without good level design those good movements won’t feel as fun as they could be.

There is one more component that makes the movement feel good: the enemy design. In the final part of this three-part series, we’ll examine the baddies of Donkey Kong Country 2 and how they, too, play a crucial role in making the movement feel good. Stay tuned!

This is Part 2 of a 3-part series. Check out all the parts here!

Part 1: Here!
Part 2: This post.
Part 3: Here!

How Donkey Kong Country 2’s Movement Feels Amazing (Part 1)

This is Part 1 of a 3-part series. Check out all the parts here!

Part 1: This post.
Part 2: Here!
Part 3: Here!

As a child, I loved to play Donkey Kong Country 2: Diddy’s Kong Quest; as an adult in my early thirties, I still love playing it. One of the reasons, among many, why is the game’s character movement feels almost perfect. Never do I feel as though I can’t adequately control Diddy or Dixie Kong as I navigate them through many levels of perilous adventure. As a game designer, I found myself wondering: what is it, exactly, that makes me love DKC2’s movement so much?

To begin answering that question, I’ll take a close look at the action verbs DKC2 employs related to the player characters. It’s not just a matter of analyzing the actions and movements in and of themselves, however; the level and enemy design of DKC2 are intentionally crafted to augment those basic actions. As is undoubtedly true for many games, it is the whole package together which creates that feel I love so much.

To break this lengthy dissertation into digestible chunks, this will be a three-part series. We’ll open up with a look at the basic movements and abilities of the Kongs themselves, as well as some special moves that are used to spice up the normal gameplay. After that, we’ll examine how the levels are designed to take advantage of the basic and special moves. Finally, we’ll look at the enemy designs and how those compliment the basic moves and abilities as well.

Kong Kapabilities

To begin this design dissection, we’ll look at the many forms of movement present for the Kongs in DKC2. First up: the core action verbs. Each Kong possess a base set of moves that you’ll be employing throughout the majority of the game.

Walk and Run

You have two basic ground movements, one for walking, and one for running. Diddy’s speed is slightly faster than Dixie’s, for both walking and running, but both feel fast enough that I don’t feel particularly disadvantaged when using the slower Kong. The actual movement speeds themselves feel just right to me; I’ve never found myself wishing that I could move faster or slower.

Triggering the run and walk actions are simple: hold the Y button and a direction to run in said direction; don’t hold Y, and that same directional input is a walk. Personally, I always appreciate it when games give me the ability to walk when I want to, even if most of the time I’m probably going to run. Having an easy-to-use slow option gives me that more precise control when I feel like I need it.

For 3D games, having easy walking also lets me get more immersed in the fantasy of inhabiting a character in a world. No one runs all the time in real life!

Accelerating from a standstill to a walk is near-instantaneous, which further helps with making the Kongs easy to control, but there is just enough acceleration that you don’t feel the characters instantly transition from idle to walk. Running, on the other hand, only happens after you press the Y button, which triggers the controlled Kong’s spin move before transitioning to the run. That animation helps convey a reason for why the character is suddenly transitioning to a run speed, while also giving you an attack move which is easy to trigger. If you were holding Y and start moving from idle, though, you just accelerate briefly into the run speed; again, just enough acceleration to make it not instantaneous, but short enough that it doesn’t impact your control over the character.

Speaking of spin maneuvers…

Spin

Both Kongs have a spinning maneuver, but each does it differently. The execution of both spin maneuvers result in the same outcome, however: pressing the Y button causes the Kong to perform a spinning attack move, which is capable of defeating most ground enemies; if Y is still held, then any directional ground movement will subsequently result in the Kong moving at a running speed.

As mentioned, the Kongs each have their own spin on the spin maneuver. Diddy’s is a cartwheel, looking quite acrobatic, while Dixie’s is a body spin, twirling her pony tail (termed by the game as a “helicopter spin”). Interestingly, Diddy’s cartwheel looks like a more natural action than Dixie’s, which can contribute to his move feeling faster than Dixies, but in fact both spin at the same speed and move the same distance, which means you aren’t disadvantaged when faced with level sections which ask you to use the maneuver.

Dixie’s helicopter spin serves a second purpose while in air: pressing Y while in midair causes Dixie to spin constantly, giving her a slower fall speed, and thereby giving the player greater control over where they want to land. Diddy does not have this ability, which helps give both Kongs distinct roles: Dixie is useful for air control, while Diddy is useful for moving more quickly.

Jump

An essential ingredient to most 2D platformers is the jumping action, and both Diddy and Dixie are more than capable of performing this function. Pressing the B button causes the selected Kong to jump into the air, up to approximately three times their own height. Tapping B causes a short jump, while holding B slightly longer results in a higher jump. While it may seem pointless to have this scaled jumping, having some control over your jump height makes you feel more in control than you would if you only had a single jump height.

There is more to the feel of the jump than the upward movement. Running and jumping allows you to cover more horizontal distance than walking and jumping, providing more encouragement to run through the levels. While in the air, you have full control over horizontal direction, giving you the ability to precisely place where you want to land and making air control feel responsive. When you fall, you accelerate to a constant speed, which you maintain until you hit the ground (or the enemy on it), which makes it easier to predict where your jumps will land.

Interestingly, it seems that your fall speed is the same as your jump speed; it takes the same amount of time to fall from the apex of your jump as it does to jump up to it. However, if you are moving in a horizontal direction while in midair, it seems to slightly increase the amount of time the Kong stays at the apex of the jump. This is something I only noticed when going through my recorded footage frame-by-frame to try and get ballpark estimates, but now that I think about it, this gives you a little more time to figure out where you want to land before you start falling. It’s a clever design, one that I hadn’t thought about before.

Most enemies can be defeated by jumping on them, and this jump attack is something you’re going to do a lot throughout the course of the game. The game encourages you to do this by giving you a greater jump height when you jump off the top of an enemy than when you jump from the ground, allowing you to cover greater distances. Even if you don’t actually jump off the enemy, landing on one grants you a small bounce acceleration that still lets you move up and onward. These movements help make bouncing off an enemy feel good.

The spin moves also have a role to play in performing jumps. While spinning off of a ledge, you can press the jump button (B) to cause the Kong to jump while in mid-air. This allows the Kong to cover more horizontal distance than they’d be able to cover by simply running and jumping. The timing on this is fairly forgiving; the game doesn’t want to impede you from pulling this maneuver off.

Duck

It’s easy to forget about the ability to duck, as it’s a passive move compared to walking, running, spinning and jumping. This move causes your Kong to duck down and hold still, effectively decreasing their vertical hitbox size by about half. This is used to less effect than other moves, but there are still places that will require you to duck under passing enemies, so it has a part to play in the movement design.

Team Throw

Finally, you can pick up your partner to put them on your Kong’s shoulders, then subsequently throw them by pressing Y (the normal throw button). If you hold the up direction as you throw, this results in launching your partner up into the air in a slow, controllable way; without this, your partner Kong is simply thrown to the ground, remaining where they fell until you come back for them (or they go off-screen, in which case they run back to you automatically). The horizontal throw can also be used to defeat enemies that could be defeated with normal attacks.

Special Skills

Now that we’ve discussed the basic Kong moveset, let’s examine cases where the game modifies these default maneuvers to introduce other forms of gameplay. These serve as an occasional change-up to how the game flows. Just as with basic movement, the feel of these special movements is important.

Climbing

Throughout various levels, ropes are placed, which the Kongs can climb when you leap into them. While on a rope, your movement is restricted to a single plane, either horizontal or vertical (depending on which way the rope faces). Diddy and Dixie’s different movement speeds factor here as well, with Diddy being able to traverse ropes slightly faster than Dixie.

Keeping the rope climbing speeds fast, close to their walking speeds, means that it doesn’t feel like the game slows down during the climbing sections, so they still feel good to move around on. This, in turn, makes it fun to navigate through the various challenges the designers place along the paths the ropes and chains force you to traverse.

Swimming

In some levels, there are water sections that the Kongs can swim in. Jump and spin attacks can’t be performed underwater, so the gameplay changes from defeating enemies to avoiding them. While underwater, you are able to move in all directions, but you sink downward unless you tap the B (jump) button to swim upward.

Like the climbing sections, your movement speed underwater is close to your regular movement speed, although the requirement of tapping the B button makes swimming more skillful than simple navigation; you have to time button presses to keep yourself afloat while swimming past enemies. Unlike the climbing sections, swimming sections allow you unrestricted horizontal and vertical movement, adding yet another flavor of movement variety to break up the rote platforming that dominates most of the game.

Flying

Some levels give you access to the Squawks animal buddy, a parrot that grants the Kongs the ability to fly. The flying sections are similar to swimming sections; you can move in all directions, but have to continuously tap B to move up or you float back down to the ground.

The similarity in how swimming and flying sections work from a movement standpoint allows for the designers to reuse player expectations for how those mechanics work, and avoid having to teach yet another new mechanic.

Speaking of animal buddies…

Animal Buddies

These are animals that the Kongs can ride (or, in some cases, transform into), and using an animal buddy changes what moves the player can perform. With all the animal buddies, there are similar base movement motions: there is a slower “normal” speed, and then a faster “running/swimming/flying” speed when moving while holding down Y. Beyond that, however, each animal introduces different changes to the movement equation.

Animals can be accessed in two ways. One is through a crate, in which case the Kongs ride the animal. While riding the animal, taking damage causes the Kong to fall off and the animal to run away; you can also choose to dismount the animal willingly. The other is by transforming into the animal itself. Dismounting the animal is no longer an option, so you are stuck with that animal’s movement changes. Usually, transformed mode is given when the level’s design explicitly requires that particular animal’s movements to progress through the level.

Either way, at some point in the level you’ll reach a “no-animal” sign, which removes the animal buddy and gives you some kind of reward. By giving an end to the animal buddy use, this allows for only having some parts of the level be designed around the use of the animal, and in some cases even have multiple animals within the same level. It makes the animal buddies another tool in the player’s movement arsenal.

Let’s take a look at the animals and what their movements add to the gameplay.

Rambi

A rhinocerous that gives you a faster running speed. Rambi is impervious to damage when jumping on enemies or running into them with its horn, only being vulnerable to attacks from behind (and certain projectiles). Holding down A prepares an even faster run in the direction Rambi is facing, plowing through enemies and breaking through hidden entrances to bonus areas.

Riding Rambi makes you feel almost invincible, so you feel more willing to move fast and make risky decisions. Enemies are mostly no longer a challenge for you. These things combined make Rambi feel amazing to use. To account for this, oftentimes Rambi’s location in a level is hidden, making finding him a reward for looking around the levels, rather than blazing through them.

Enguarde

A swordfish that gives you full movement control underwater. No longer do you need to tap B to stay afloat; Enguarde hovers in place when idle, and moves in whatever direction you input. The sharp-looking bill on its face can defeat any underwater enemy, and clearing enemies makes the underwater sections that much easier.

Having Enguarde removes a lot of the challenge from swimming movement, which means riding Enguarde feels similar to riding Rambi, in that you feel more free to move where you please, with ease. Since Enguarde makes underwater movement and gameplay much easier, his locations, like Rambi’s, are hidden well in the level, so that finding and using him is a reward for good exploration.

One could think of Enguarde as the underwater version of Rambi.

Squawks

As mentioned, Squawks is a parrot that gives you the ability to fly in a stage. Unlike Enguarde, you don’t get to hover in place when idle, so there is still challenge involved with using the bird. Squawks also has a ranged attack, hurling eggs out of its mouth in a parabolic trajectory when the Y button is pressed.

Unlike Enguarde, who generally makes level movement easier, Squawks requires more care to ride. Any contact with an enemy damages Squawks, so you are required to be careful as to where you fly. The egg projectiles don’t fly straight, so aiming them to hit enemies, while maintaining flight, is in and of itself a challenge for the player. As such, Squawks is often placed in locations that are easier for the player to find.

Squitter

Squitter gives you the ability to shoot webs. Pressing Y delivers an attack web which can defeat any non-invulnerable enemy; you can control the arc of the shot by holding up or down on the direction input after the attack web has been fired. Pressing the A button (or, alternatively, either the L or R buttons) shoots a slow-moving platform web. Pressing the platform web button again after firing deploys the web platform, giving you something to stand on in mid-air. These platforms disappear after a short period of time, however, so you need to move quickly. Unlike other ground animals, Squitter cannot defeat enemies by jumping on them, so you need to keep away from foes and blast them from afar.

From a design standpoint, Squitter is a massive change to your movement toolset. Riding him takes away your ability to jump on enemies, but gives you an infinite amount of projectiles, making you adopt a long-ranged approach to defeating enemies. Creating platforms at will allows you to avoid hazards, pitfalls, and enemies that would otherwise prove difficult or impossible to overcome; they also provide an interesting way to add more vertical movement to the regular platforming action.

Rattly

This snake has an extremely high jump, allowing you to reach higher platforms than you could with the Kongs by themselves. Holding down A charges Rattly to make a super-jump, similar to how holding A charges Rambi to make a super run. Rattly defeats any enemy that it jumps on, except for ones that are invincible. Interestingly, when Rattly moves along the ground, movement consists of small jumps that result in a lurching style of movement; this is perhaps an encouragement to move by pressing the jump button, which feels more fun to do.

Rattly is all about the jump. He can jump on nearly anything and live, and, since your spin attack is taken away while riding him, you are strongly encouraged to jump on foes in your path. The high jump allows for adding taller sections to levels that take advantage of that bigger jump height.

I am leaving out Glimmer and Clapper because they do not change your movement mechanics.

Careening in Carts

Three levels let you ride in carts. While in a cart, the Kongs move in a consistent rightward direction at a controlled speed (you can go faster by holding the forward directional input, and slower by holding back). Depending on the level, jumping will either cause both Kong and cart to jump, or just the Kong. With the consistent movement, beating these levels is about reacting to upcoming threats quickly; you aren’t given time to ponder your next action. This can be frustrating while learning the level, but it results in satisfying rushes once you consistently react correctly to the level’s challenges.

What does this sudden change in movement bring to the design table? The rush of speed, for one. It’s fun to move fast through a level, much faster than you normally get to move. Along with that speed comes reduced amounts of time to react to level hazards. You can’t stop to figure out how to get past those hazards, either; you’re hurtling at them at high velocity, and you have to make quick choices about how to get past them. This is hard, but exhilarating, and they add a health dash of experiential spice to the movement gameplay.

These cart levels are ones I loved playing as a kid. That said, I think it’s good that there aren’t many of these kind of levels, as the high tension throughout would make level to level gameplay far more exhausting if that was the majority of the action.

Tossing in Throwable Objects

One final Kong ability remains: throwing objects. Both Kongs are capable of picking up and holding barrels and other objects placed throughout the level by holding the Y button; releasing the Y button causes the Kong to throw the projectile, which will defeat any enemy that it touches along the flight path. Aside from certain animal buddies, this is the only ranged attack you have access to in the game.

In many cases, the objects will break on impact, whether with the world or an enemy, making them a single-use resource. This forces you to consider carefully when you encounter a projectile to throw; should you use it now, on the nearest enemy, or should you try to hold on to it to use on some future part of the level with a more difficult obstacle, or a bonus area entrance? Your movement isn’t impeded while holding a barrel, so you still have the same level of control that you do normally; however, since objects break on contact, and you are forced to throw the object when you jump on an enemy, holding on to a projectile you wish to keep intact transforms the gameplay from defeating enemies to avoiding them until such time as you wish to use the projectile. This is a great way to subtly introduce a change in gameplay.

Interestingly, I always thought in my mind that you moved slowly while holding on to a barrel, but in testing I found that this is actually not true; you still move the same amount regardless of whether you’re holding an object or not. This is an interesting psychological effect; I wonder if the designers intended it, or if it was just a byproduct of our expectations for movement with held objects.

What if you don’t want to hold the projectile right now, but still need to keep it around? You can hold down and release Y to set the object back down on the ground, where it will wait for you to collect again. If you move far enough to cause the object to go off-screen, however, it resets its position back to where you found it; while this was probably something introduced by the technological limitations of the SNES days, rather than a design decision, it’s something you still have to consider when setting the object down.

The way in which Diddy and Dixie throw projectiles differs for each. Diddy picks up objects with his hands and throws them in a flat, shallow arc that is almost effectively a straight line; Dixie picks up objects with her ponytail (boy, her hair can really defy the laws of physics, can’t it) and throws them with a more pronounced arc. Because her hair is on her head, the release point of projectiles Dixie throws is higher than Diddy’s, resulting in making it easier to hit enemies that are up in the air. Both Kongs are able to jump while holding objects, which provides additional control over where the projectile is thrown. Since Diddy’s throw starts lower to the ground, jumping and throwing is almost essential when throwing with him to get good distance. Giving the two Kongs different throwing styles further differentiates between using the two Kongs, making it useful to have both around to switch up which one you use for a given situation.

There are many kinds of objects the Kongs can throw throughout their journey. Each kind of object provides different characteristics that affect player strategy for using them.

Barrels

This is the bread and butter projectile of the game: a simple, metal-banded wooden barrel. You can throw it normally, which will fling the barrel forward and cause it to break on impact with the ground (plowing through however many enemies were in its way), or you can heave it upwards into the air, which will cause it to land softly enough to not break on impact, and subsequently will roll along the ground (and through enemies) until it hits a wall or falls off a ledge.

Crates

Crates, on the other hand, are square projectiles that clearly communicate that they won’t roll. They also break on impact, regardless of how you throw them, making them a much more limited resource.

Buddy Barrel

The buddy barrel contains your partner Kong, if you lost them at some point during gameplay, and when the barrel breaks open you get your partner back. Interestingly, the buddy barrel’s impact mechanics are similar to the crate, in that it breaks immediately on impact with the ground or with an enemy, so when there is no Kong in the barrel the two objects are pretty much the same, functionally.

Cannonballs

These spheres of solid metal do not break on impact with anything, but they also do not roll when they hit the ground. Because of their indestructible nature, they are useful as reusable projectiles or shields for as long as you can keep from throwing them into a pit.

Chests

Chests break only on impact with enemies (or bonus area entrances), and they contain some form of beneficial pickup (or “goodies”) which gives you something of value, from banana bunches to life balloons (extra lives) to Kong letters, and everything in between. Since they don’t break on hitting the ground, you pretty much have to use them on enemies, which means you’ll have to find an enemy to use it on or you won’t be able to break it.

TNT Barrels

The holy hand grenade of DKC2, these explosive barrels are capable of destroying any enemy in a fiery explosion. This explosion has a small radius that will destroy any other enemies it touches. This barrel is often the only means you are given to deal with invulnerable enemies, which means being given one can be a sign that such an enemy is coming up.

To Be Kong-tinued

We’ve taken a deep look into the movement of Donkey Kong Country 2. From the simplest, basic moves to the variations introduced by special abilities, controlling the Kongs feels tight, responsive, and fun. But having characters that move well is only part of the gameplay story; the environments the Kongs move through and the enemies they fight contribute just as much to the feel of the movement as the movement itself.

In the next part of this series, we’ll take a dive into the level design of Donkey Kong Country 2, and see how much it impacts how the movement feels. Stay tuned!

This is Part 1 of a 3-part series. Check out all the parts here!

Part 1: This post.
Part 2: Here!
Part 3: Here!

Bullet Journaling

Learning how to use a bullet journal has been one of the most important things to happen in my life.

Yes, I’m comparing the use of a paper book intended for organization up there with events such as my marriage or the birth of my son, and no, I’m not trivializing either of those things by comparison.

Let me put it this way: bullet journaling has almost single-handedly given me, someone with severe ADHD, the capability to structure my life in such a way that I can attempt to accomplish my dreams. If I hadn’t learned how to do it, I would undoubtedly not be in the blessed, fortunate position that I am in today, supporting my family with web development and making games on the side.

I know there’s plenty of resources out there that talk about bullet journaling; my intent here is merely to tell my own story: how my life was prior to bullet journaling, how I implemented bullet journaling techniques, and how I use bullet journaling today. In doing so, perhaps you may be convinced to try bullet journaling yourself; or, if you already use a bullet journal, you might see techniques you can take to augment your own journaling methods.

Shoutout to Todd Mitchell of Code Write Play, whose inquiries about my bullet journaling experience led directly to me writing this article!

Life Before Bullet Journaling

As mentioned, I have ADHD (Attention Deficit Hyperactivity Disorder). This malady is caused by a deficit of chemicals in my brain which help it to focus and make judgment calls, functionalities that are critical if one wants to have any measure of success in life. Because I struggle with these things, my life is made exceptionally more difficult. While medication significantly helps with regulating the chemical levels in my brain, it doesn’t magically grant me the ability to focus perfectly and make good decisions; I need to supplement the medications with good habits and routines. Ironically, forming good habits and routines are things which require focus and sound judgment.

How do “normal” people form good habits and routines, beyond “just doing it” (an ability that is not readily available to me)? They use planners and scheduling calendars, setting up a written means to refer back to what they want to do, not needing too much of a push to get it done. Why can’t I do the same?

Here’s the catch: Sitting down and figuring out what I need to do is boring. For someone with ADHD, being bored feels like torture, like dying (that may sound hyperbolic, but, trust me, it’s not far from the truth), so it’s something I innately want to avoid doing. Even when I force myself through something that bores me, the sheer amount of mental willpower required to do so drains me utterly.

On top of that, most planners, in terms of books that are specifically designed for the purpose of making plans, have a set structure that you are required to conform to in order to use. Things like “Daily Todo List” or “Monthly Plans”, all given specific sections on a page, with little to no room for customization. I’ve yet to come across a planner whose design matches what I feel comfortable with. Do I know what feels comfortable to me? Nope! But every planner I’ve attempted to work with felt dreadful to use, and something that feels dreadful gives me zero motivation to keep using it, especially when it isn’t yielding positive, impactful results.

A logical followup would be to wonder: why not make your own planner, then? Surely, you can figure out what you need and design your planner accordingly, right? Sadly, ADHD also impacts my ability to visualize what works for me. Often, that means I don’t know what I really want. In the moment, something might feel like it would work for the long term, but days later that same strategy might prove chafing to my psyche, for reasons I couldn’t foresee. It’s already difficult, figuring out how to structure a planner that works for all cases; it’s even tougher when I need to use it mid-development to organize the rest of my life. While I did attempt to create custom planners, time and time again they not only proved to be more work than I was willing to force myself to do, they weren’t effective enough to be worth the hard work in the first place.

Without the aid of planners, I resorted to many disparate means of keeping track of what I needed to do. Notes left in places I’d likely be around; notes scattered all over my desk, where I spent much of my day; stacks of things left out on the floor, increasing the chance that I’d remember to deal with them… The overarching problem, aside from it being a disgraceful cacophony of approaches, is that at no point did these scattershot solutions solve for making plans beyond the immediate here and now. If I wanted to do a project, I had to spend all of my spare time focusing on that project; otherwise, I’d get bored of it, or forget about it entirely, and bounce off to the next more interesting project.

I hope these words paint the picture of my life before bullet journaling. I was getting by, technically. But I felt powerless to make true progress on the things I wanted to do.

How I Got Into Bullet Journaling

My wife bought a bullet journal for me in 2016, as a Christmas gift. I intended to try it; in the back of my mind, however, I didn’t feel like this was going to be any different from other times I’d tried to learn new planners. I’d had years of failure and pain built into my memories, and these discouraged me from wanting to make a serious go at bullet journaling.

So, the journal sat around the apartment, being ignored. 2017 came, and passed by. All throughout the year, I kept telling myself I’d give it a shot, and still I never got around to it. At the time, I already had a career in web development, and I was doing my best to keep learning more about web development to improve my chances at getting better-paying jobs. What time wasn’t being spent on that was spent recovering from the difficulty of spending so much of my focus and attention on one specific goal.

Near the end of 2017, Rebecca and I decided that we wanted to try and start a game development studio. Making video games is something I’d always wanted to do, but it wasn’t until now that I felt my programming skills were solid enough that I could attempt to act on this desire. However, I needed to continue building on my web development skills to continue making money from the career that pays our bills. That meant I would have to find a way to juggle working on multiple projects simultaneously, for month after month, year after year; I specifically struggled with this exact thing because of my lack of ability to plan. It became clear: I needed to improve my planning skills, or none of this game development stuff would come to fruition.

I finally had the drive to change, and at the beginning of 2018 I committed to forcing myself to learn how to bullet journal.

I don’t recall if it was a New Year’s Resolution or not, to be honest. It could’ve been, but I simply don’t recall. Personally, I’m not much of a New Year’s resolutions person, anyway; why wait for the new year when you can make a resolution anytime you want?

How I Implemented Bullet Journaling

I started by watching tutorials on YouTube about how bullet journaling works. How To ADHD (by Jessica McAbe), in particular, had great tutorials on bullet journaling that proved helpful to me. To my surprise, I learned that there is no strict formula to making a bullet journal work; you are encouraged to take the parts that work for you, and not worry about the parts that don’t. That was already a major difference from my previous attempts to learn planners: I got to decide what works for me.

Of course, just using a rule-lined notebook technically accomplishes the same thing. The crucial difference is that there were suggested systems for me to use, giving me more than a blank page to work with. At the core of bullet journal philosophy is the daily list of todos: each day, I make a new entry in the bullet journal and write down all the things I need to get done that day. At the end of the day, I mark down what got done, what I moved to a different day, and what I chose to not get done. That is the one habit that was asked of me, to ensure I always make this daily list; this was a far cry from planning systems of the past that made me feel as though I had to plan weeks to months at a time.

What that daily todo entry looks like, visually, is up to me. The bullet journal I was given used dot grid notation, and it is an underrated, but crucial, aspect of making bullet journaling work for me. Personally, ruled paper always suggests that I have to keep my writing between the lines, and it visually discourages me from making any sort of lines that broke from that horizontal structure in any way. Blank paper gives me the freedom to create whatever I want, but it is a lot of effort to make consistent spacing and lines, which are things that help keep me focused. Dot grid paper, on the other hand, provides a symmetric grid of small dots, giving me a sense of where the structure should take place, while not providing explicit lines that make me feel restricted. Honestly, the dot grid paper played a huge role in getting me to buy in to the bullet journal methods.

This is how my first bullet journal was structured.

How Bullet Journaling Helped Me

With simple guidelines, and simple structures on dot grid paper, I slowly integrated use of the bullet journal into my daily routine. It made more than a noticeable impact; it shocked me with just how powerful and effective it was, especially compared to all the other methods of planning I’d attempted throughout my life.

For the first time, ever, I was able to make consistent progress on multiple projects simultaneously. Having a daily reminder of what I needed to do helped me remember what to work on, and having an easy means to keep making a schedule helped ensure that it wasn’t too hard to put my plans to paper. Being able to see at a glance what I needed to do also helped prevent me from jumping off on more interesting projects. This is what I’m working on, I’d tell myself, I want to keep at it so I can finish it.

Incidentally, now that I was writing down my plans, I got better at deciding what I needed to work on. I could research how to do some kind of task, and break it down into parts that I could subsequently schedule. On top of that, the dot grid paper was also great for taking notes down for these projects; the bullet journaling method actually encourages this, as keeping thoughts near your todos helps keep your thoughts nearby without losing them on scattered scraps of paper.

Perhaps most importantly, I made progress on projects. I didn’t bounce off of them, I didn’t have to keep them in the front of my mind, I just did them. For someone like me, this was an awe-inspiring experience. I could actually have a plan to do something, and pull it off while still living other aspects of my life. I’d never known this experience before, and I absolutely loved it.

I also had a much easier time remembering to pay bills on time!

Customizing My Approach

I’ll freely admit: I don’t do bullet journaling exactly as described by the tutorials that I watched. That’s perfectly okay! Using the bullet journal approach, as I’ve stated before, is about adopting the parts that work and discarding the parts that don’t. Where the “official” approach doesn’t provide a feature I want, I’m totally free to add that feature to my bullet journaling method; where it suggests something that I don’t find useful, I ignore it.

For example, the official method suggests creating a “Future Log” that encompasses the next six months, and contains the things you want to accomplish in the distant future. I didn’t find scheduling things out for six months particularly useful, so I created my own approach. It uses the monthly page suggested by the original approach, but I instead make two monthly pages: one that contains the dates and events I need to track for that month, and a “Notes/Tasks/Plans” page which contains the things I want to keep in the back of my mind over the coming month, including plans scheduled further out into the future than a month. This is what works for me, and I’ve not yet seen reason to adjust from it.

The way I structure my todos and notes has also changed since those initial days. Before, I simply placed todo lists at the next blank page in succession. It worked to keep things simple, but I quickly got annoyed with having to hunt between pages of notes to find past todo lists. I decided to impose my own order for todo lists and notes pages: pages from the start of the book are todo lists (daily, weekly, and monthly), while pages from the back of the book are my notes pages. When the two meet, that’s when I start a new bullet journal.

Additionally, the structure of the daily and weekly todos themselves has changed. At first, it was simply making a list on a page where space fit; if a page had enough space for multiple daily todos, I’d add multiple lists. That wasn’t organized enough for my tastes, so I chose to use a half-page structure. Each page would contain two days worth of lists, and no more. After reserving one of the halves for the weekly list, I had a consistent structure where one week’s worth of todos would be predictably spread across exactly four pages. Giving myself a small amount of space for lists also helped me realize when I was trying to put too many things on my list for a given day, and spread things out accordingly.

After using this for nearly a year, I decided to make one more change: instead of each page containing two days worth of todos, each page would represent a single day. The pages were still divided in half, with the top half being dedicated to todos, while the bottom half was reserved for me making notes about things that happened during the day. This change was inspired by a realization: I have trouble recalling what has happened throughout the course of my life. If it wasn’t a major event, it was something I likely wouldn’t be able to recall easily. That troubled me enough that I decided I wanted to have a means of recording at least something about what happened on a particular day. To be honest, it kind of annoys me, but I consider it important enough for my future self that I continue to do it. And since it doesn’t take more than five minutes (if that), it’s not annoying enough for me to give up on it.

Finally, I’ve added my own symbols to the ones officially suggested. Instead of listing what’s different, however, I think it’d be more useful to simply list what symbols I use, since that’s the part which really matters, anyway.

My symbology:

  • A single dot is an unresolved todo.
  • A dot with an X is a todo that was completed.
  • A dot with a forward arrow is a task that I’ve migrated to a future date.
  • A dot with a backwards arrow is a task that I accomplished on a previous date.
  • A task that is crossed out is a task that I chose not to do (and didn’t migrate to a future date).
  • A question mark is a task that is optional. It needs to be resolved, but I’m being clear to myself that it is not required to complete.
  • An exclamation point is a task that I must complete now or in the immediate future.
  • A dot within a circle is an event that is taking place.
  • A question mark within a circle is an event that might happen, but not for certain.
  • A dash is a note that I want to keep in mind on a daily basis. Keeping it in the daily todos helps me keep it in memory, even if it isn’t strictly a task to accomplish.

I do choose to use the “Bullet Journal”-brand journal, for two reasons: it contains all the journal features I want, such as dot grid paper and three bookmarks, and I have a thing for keeping all my journals consistent, so I continue to buy the brand that I was originally given. Though I don’t mind paying more for my journal material, there are definitely cheaper options which are just as valid!

The structure my bullet journals have used for the last few years. I still have yet to consistently make salads.

Augmenting the Journaling

Bullet Journaling is the planning system that has finally stuck with me, but there’s a catch with all planning systems: you have to remember to use them, or they aren’t effective! To that end, I’ve needed to adopt various strategies outside of the bullet journal to help ensure I don’t forget to keep it up to date.

I try to keep my journal physically located in a centralized location in the place that I live. Right now, this is at the top of the stairs, which I have to pass by every day. Seeing the journal helps jogs my memory that I need to do something with it.

Another thing that significantly helps me is the use of phone alarms. I was already using these to help me remember when I need to take my medications, so it wasn’t hard for me to add additional alarms to remind me to periodically check the bullet journal to ensure I’m not missing time-sensitive things (like calling a doctor’s office during open hours).

An odd thing that I also employ is the use of a habit tracker app on my phone. At least, it seems odd to me, because most of the time I don’t care for systems that employ streak-keeping as the means of positive reinforcement, and most of the time I outright ignore other apps which employ that tactic. For some reason, however, I feel strongly enough about keeping up my habit of completing the bullet journal that, when I see it ask me if I’ve done it yet, it’s enough to get me to stop what I’m doing and go resolve my tasks. Maybe it’s because I care enough about making bullet journaling work that I don’t want to see evidence that I’m flaking away from it? Regardless, this helps ensure that I don’t forget to update the journal over multiple days (which is easy to do with a forgetful short-term memory).

Even though bullet journaling is by far the least painful method of journaling I’ve tried, I do still find it annoying and boring to have to sit down with it and make plans. To help combat this, I’ve designated Thursdays as the day of the week where I force myself to make the plans for the following week (and month, if it’s the last Thursday before the end of the month). That way, I only have to sludge through the boredom one time a week, and it rarely takes more than 10-20 minutes to get everything resolved. I picked Thursday because it gives me a couple of days worth of buffer, should I be unable (or unwilling) to make the new schedule that day.

The Continuous Happy Ending

I opened this article by saying learning how to bullet journal was one of the most important parts of my life, right up there with traditional life-shattering occasions. Before, my lack of focus and good judgment made it very hard to keep working on projects and achieving my goals and dreams. Once I committed myself to bullet journaling, my life has changed dramatically and positively; I can confidently say that, without it, I wouldn’t be having a life as good as I have it today.

I wouldn’t be a game developer. I wouldn’t have a good job that pays well. I wouldn’t have a hope of doing the things I want to do.

If you’ve struggled with making plans, if you have ADHD, if you want to do something that might just change your life for the better…try bullet journaling. I wholeheartedly recommend it.

Speaking of game development, Rebecca and I have released our first game, Sanity Wars Reimagined! Play it on Itch.io to see the progress we’ve made over the past several years, and follow me on social media to see what we’ll do next!

Postmortem: Sanity Wars Reimagined

After seven months of development, Rebecca (PixelLunatic) and I have released Sanity Wars Reimagined! Along the way, we learned a lot, lessons we hope to apply to future games we develop. In this article, I want to dig a little bit into what we learned, from what we were initially trying to do to where we wound up, and the various lessons learned and mistakes made along the way.

You can check out Sanity Wars Reimagined on Itch.io!

This project started with a discussion Rebecca and I had about where things were from a long-term standpoint. For the past few years, we’d attempted, and abandoned, various prototypes, and it seemed like we were still far off from actually releasing a product. At the time, I had just come off of a month-long project exploring the creation of game AI; my intent was to start another project aimed at making improvements to the dialogue system. During our discussion, however, Rebecca pointed out that, while our ultimate goal was to make games and sell them, we had a bad habit of not committing to anything long enough to get it released. What’s more, she was concerned that this pattern of starting and abandoning projects wasn’t good for our morale.

She was right to be concerned about this; I’d felt that making an actual release was still a point far off in the distance, and this was making it easier for me to accept the idea of working on non-release projects, to “gain experience”. How would we ever learn how to improve, though, if we never got our projects to a releasable, playable state? Only two of our game jam games and one of our prototypes had ever been given to other people to play, so we had very little feedback on where we needed to improve. I realized that we couldn’t keep waiting to release something until “we got better”; we needed to make something and release it, however bad it may be.

We decided that we would start with a small project, so that it would take less time to get it to a releasable state. To that end, we determined that the project should be a remake of our first jam game, Sanity Wars. By remaking a game that was already released, we thought, we could focus on actually building the parts needed to make the game work; since I’d made those things work previously, we would hopefully avoid the pitfall of trying to create mechanics that were beyond our current skill to implement, or would take too much time to build. Why Sanity Wars? Out of all the previous jam games we’d made, it seemed like the most successful one, so we thought we could just add some polish, redo the art (since Rebecca did not do the original’s art), and it would be fine.

With that, our next project was set: Sanity Wars Reimagined. We would stay faithful to the mechanics of the original, aiming only to remake them in Godot, as this would be quicker than trying to iterate and make new mechanics. I would also take the opportunity to try and make systems that would be reusable in future games; ideally, we would treat Sanity Wars Reimagined as a foundation that we would directly expand upon for the next game. Since the original Sanity Wars was done in three full days, I thought this project wouldn’t take long to complete. Accounting for our busy adult schedules, I estimated the work would take two weeks to complete; at most, a month.

It didn’t take two weeks. It didn’t take a month. It took seven months before we finally released Sanity Wars Reimagined on Itch.io. Along the way, we made significant modifications to the core mechanics, removing multiple parts that were considered essential to the original Sanity Wars; even with those changes, the end result was still not that fun (in our minds). There were many times during the development period where it felt like it was going to drag on and on, with no end in sight. All that said, I still think Sanity Wars Reimagined was a successful release.

Why do I think that? To answer that question, I want to examine what technologies we developed during the project, what mistakes we made, and what we plan to do to improve things for our next project.

Technologies Developed

A lot of what I made from a code standpoint was able to be imported back into my boilerplate Godot project, which will then be available from the start when I clone the Genesis boilerplate to make any future game project. In doing so, I’ve hopefully decreased the amount of development time needed for those projects.

Genesis is name of a tool I created in NodeJS that lets me keep a centralized Godot boilerplate template and create new projects from that boilerplate using simple commands in a command line interface. To give a non-technical summary, it allows me to quickly create and update new Godot projects that include common code that I want to reuse from project to project.

Here are some of the things that I’ll be able to make use of as a result of the work done for Sanity Wars Reimagined:

Resolution Management

There is a lot to consider when supporting multiple resolutions for a game, especially one that uses a pixel art aesthetic. I was already aware of this before committing to figuring out a solution for Sanity Wars Reimagined, but I underestimated just how much more there was to learn. The good news is that I created a solution that not only works for Sanity Wars Reimagined, but is generalized enough that I can use it as the starting point for future games.

I’ll talk in brief about some of the struggles I had to contend with and what I did to solve them.

For starters, when working with pixel art, scaling is a non-trivial issue. Because you are literally placing pixels in precise positions to render your art aesthetic, your scaling must always keep the same aspect ratio as your initial render; on top of that, it must specifically be increased in whole integer factors. This means you can only support a limited number of window sizes without messing up the pixel art. That plays a huge factor in determining what your base size is going to be; since most monitors use a 1920px by 1080px resolution size, your base needs to be a whole integer scale of 1920×1080, or else the game view is not going to actually fill the whole screen when it is maximized to fill the screen (aka fullscreen).

The way fullscreen modes are typically handled for pixel art games, when they attempt to handle it at all, is to set the game view to one of those specific ratios, and letterbox the surrounding area that doesn’t fit cleanly into that ratio. That is the approach I chose for my fullscreen management as well.

Godot does give you the means to scale your game window such that it renders the pixel art cleanly, and you can also write logic to limit supported resolution sizes to only those that scale in whole integers from the base resolution. However, there is a catch with the native way Godot handles this: any UI text that isn’t pixel-perfect becomes blurry, which isn’t a great look to have. I could have switched to only using pixel-perfect fonts, but that wasn’t the look I wanted the game UI to have. After spending a lot of time experimenting with ways to handle this in Godot’s settings, I determined that I would have to create a custom solution to achieve the effect that I wanted.

I wound up talking to Noel Berry (of Extremely OK Games) about how Celeste handled this problem, as its crisp UI over gameplay pixel art was similar to what I was hoping to achieve. He told me that they actually made the UI render at 1920×1080 at default, and then scaled the UI up or down depending on what the game’s current resolution was. This inspired me to create a version of that solution for Sanity Wars Reimagined. I created a UI scaling node that accepts a base resolution, and then changes its scale (and subsequently the scale of its child and grandchild nodes) in response to what the game’s current resolution is. It took a lot of effort, but in the end I was able to get this working correctly, with some minor caveats*.

* I’ll be coming back to these caveats later on in the article, when I discuss mistakes that were made.

Overall, I’m very pleased with the solution I developed for resolution management in Sanity Wars Reimagined, and ideally this is something that should just work for future pixel art-based games.

Screen Management

Another system I developed for Sanity Wars Reimagined is a screen management system that supports using shaders for screen transitions. Although my boilerplate code already included a basic screen manager that was responsible for changing what screens were being currently portrayed (MainMenuScreen, GameScreen, etc.), a significant flaw it had was that it didn’t provide support for doing screen transitions. This was going to be a problem for Sanity Wars Reimagined, as the original game had fade transitions between the different screen elements. I thus set out to refactor my screen management to support doing transitions.

In the original Sanity Wars, the way I accomplished the fade was through manipulating the drawn image in the browser’s canvas element (as the original game was built using HTML/JavaScript, the technologies I was most familiar with at the time). It was hardcoded to the custom engine I’d built, however, and there wasn’t a direct way to achieve the same effect in Godot. It’s possible I could have made the fade transition, specifically, work by manipulating the screen node’s modulation (visibility), but I didn’t feel comfortable making direct changes to node properties for the sake of screen effects, especially if I wanted to have the ability to do other kinds of transitions in the future, such as screen slides or flips; anything more complex than that would be outright impossible through mere node property manipulation.

My focus turned towards experimenting with a different approach, one based on using Godot’s Viewport node to get the actual render textures, and then manipulating the raw pixels by applying shaders to the render images of the outgoing screen and the incoming screen. Viewports were something I hadn’t had much experience with, however, so I wasn’t certain if the idea I had would actually work. To prove the concept, I spent a weekend creating a prototype specifically to test manipulating viewport and their render textures. The approach did, in fact, work as I envisioned (after a lot of research, trial, and error), so I proceeded to refactor the screen management system in Sanity Wars Reimagined to use this new approach.

When referring to screens here, I’m not talking about physical monitor screens; it’s a term I use to refer to a whole collection of elements comprising a section of the game experience. For instance, the Main Menu Screen is what you see on booting up the game, and Game Screen is where the gameplay takes place.

Overall, the refactor was an immense success. The fade effect worked precisely the way it did for the original Sanity Wars, and the system is flexible enough that I feel it should be easy enough to design new screen transition effects (in fact, I did create one as part of making the LoadingScreen, transitioning to an in-between screen that handled providing feedback to the user while the incoming GameScreen prepared the gameplay). Should I want to create different visual effects for future transitions, it should be as simple as writing a shader to handle it. (Not that shaders are simple, but it is far easier to do complex visual effects with shaders than with node property manipulation!)

Automated Export Script

After realizing that I needed to export game builds frequently (more on that later), I quickly found that it was tedious to have to work through Godot’s export interface every time I wanted to make a build. On top of that, Godot doesn’t have native build versioning (at least, not that I’ve found), so I have to manually name each exported build, and keep track of what the version number is. Needless to say, I wondered if there was a way I could automate this process, possibly through augmenting the Genesis scripting tools to include a simple command to export a project.

I took a few days to work through this, and in the end I managed to create functionality in my Genesis scripting tool that did what I wanted. With a simple command, godot-genesis export-project "Sanity Wars Reimagined" "Name Of Export Template", Genesis would handle grabbing a Godot executable and running a shell command to make Godot export the project using the provided export template, and then create a ZIP archive of the resulting export. The name of the export was the project name, followed by a build number using the semantics I chose (major.minor.patch.build). By providing a -b flag, I could also specify what kind of build this was (and thus which build number to increment). It works really well, and now that exports are so easy to do I am more willing to make them frequently, which allows me to quickly make sure my development changes work in the release builds.

Other Features and Improvements

There are many other features that were created for Sanity Wars Reimagined; to save time, I’ll simply give brief summaries of these. Some of these were not generalized enough to be ported back into the Genesis boilerplate, but the experience gained from creating them remains valuable nonetheless.

Generators

These nodes handle spawning entities, and I made the system flexible enough that I can pretty much generate any kind of object I want (though, in this case, I only used it to spawn Eyeballs and Tomes).

RectZone

This is a node which let me specify a rectangular area that other nodes (like Generators) could use to make spatial calculations against (aka figure out where to spawn things).

PixelPerfectCamera

This is a Camera node that was customized to support smoothing behavior rounded to pixel-perfect values. This helps reduce the amount of visual jitter that results from when a camera is positioned between whole integer values.

The reason this happens is because pixels can’t be rendered at fractional, non-integer values, so when a pixel art game asset is placed such that the underlying pixels don’t line up to a whole integer, the game engine’s renderer “guesses” what the actual pixel color values should be. This is barely noticeable for high-resolution assets because they consist of a huge number of pixels, but for something as low-resolution as pixel art, this results in visual artifacts that look terrible.

UI Theming

I finally took a dive into trying to understand how Godot’s theme system works, and as a result I was able to create themes for my UI that made it much simpler to create new UI elements that already worked with the visual design of the interface. I plan to build on my experience with UI themes for future projects, and ultimately want to make a base theme for the Genesis boilerplate, so I don’t have to create new themes from scratch.

State Machine Movement

I converted my previous Player character movement code to be based on a state machine instead of part of the node’s script, and this resulted in movement logic that was far simpler to control and modify.


As you can see, there were a lot of features I developed for Sanity Wars Reimagined, independent of gameplay aspects. A large part of what I created was generalized and reusable, so I can put the code in future projects without having to make modifications to remove Sanity Wars-specific functionality.

Complications

No human endeavor is perfect, and that is certainly true for Sanity Wars Reimagined. In fact, I made a lot of mistakes on this project. Fortunately, all of these mistakes are things I can learn from to make future projects better. I’ll highlight some of these issues and mistakes now.

Both Rebecca and I learned a lot from the mistakes we made developing this project, but I’m specifically focusing on my mistakes in this article.

New Systems Introduced New Complexities

The big systems I added, like Resolution Management and Screen Management, added lots of functionality to the game. With that, however, came gameplay issues that arose as the result of the requirements integrating with these systems introduced.

Take ScreenManager, for example. The system included the ability to have screens running visual updates during the transition, so the screen’s render texture didn’t look like it was frozen while fading from one screen to the next. By creating this capability, however, I needed to modify the existing game logic to take into account the idea that it could be running as part of a screen transition; for instance, the player character needed to be visible on the screen during the transition, but with input disabled so the player couldn’t move while the transition was running.

Another issue the ScreenManager refactor created had to do with resetting the game when the player chose to restart. Before, screens were loaded from file when being switched to, and being unloaded when switched from, so restart logic was as simple as using node _ready() methods to set up the game logic. After the refactor, this was no longer true; to avoid the loading penalty (and subsequent screen freeze) of dealing with loading scenes from file, ScreenManager instead kept inactive screens around in memory, adding them to the scene tree when being transitioned to and removing them from the scene tree when being transitioned from. Since _ready() only runs once, the first time a node enters the scene tree, it was no longer usable as a way to reset game logic. I had to fix this by explicitly creating reset functions that could be called in response to a reset signal emitted by the controlling screen, and throughout the remaining development I encountered bugs stemming from this change in game reset logic.

ResolutionManager, while allowing for crisp-looking UI, created its own problems as well. While the UI could be scaled down as much as I wanted, at smaller resolutions elements would render slightly differently from how they looked at 1920×1080. The reason for this was, ironically, similar to the issues with scaling pixel art: by scaling the UI down, any UI element whose size dimensions did not result in whole-number integers would force Godot’s renderer to have to guess what to render for a particular pixel location on the monitor. Subsequently, some of the UI looked bad at smaller resolutions (such as the outlines around the spell selection indicators). I suspect I could have addressed this issue by tweaking the UI design sizes to scale cleanly, but my attempts to change those values resulted in breaking the UI in ways I couldn’t figure out how to fix (largely due to my continued troubles understanding how to create good UI in Godot). In the end, I decided that, with my limited amount of time, trying to fix all the UI issues was less important than fixing the other issues I was working on, and ultimately the task was cut during the finishing stages of development.

I’m guessing most people won’t notice, anyway, since most people likely have the game at fullscreen, anyway.

Complexities arising from implementing new systems happened in other ways throughout the project as well, although the ones stemming from ScreenManager and ResolutionManager caused some of the bigger headaches. Fixing said issues contributed to extending development time.

Designing for Randomization

One of the core mechanics of Sanity Wars (original and Reimagined) is that all the entities in the game spawn at random locations on an unchanging set of maps. At the time I created the mechanic, my thought was that this was a way to achieve some amount of replayability, by having each run create different placements for the tomes, eyeballs, portals, and player.

Playtesters, however, pointed out that the fully random nature of where things spawned resulted in wide swings of gameplay experience. Sometimes, you got spawns that made runs trivially easy to complete; other times, the spawns made runs much more difficult. This had a negative impact on gameplay experience.

The way to solve this is through creating the means of controlling just how random the processes are. For example, I could add specific locations where portals were allowed to spawn, or add logic to ensure tomes didn’t spawn too close to portals. Adding controlled randomness isn’t easy, however, because by definition it means having to add special conditions to the spawning logic beyond simply picking a location within the map.

The biggest impact of controlled randomness wasn’t directly felt with Sanity Wars Reimagined, however; it was felt in our plans to expand directly off of this project for our next game. Given that random generation was a core element of gameplay, that meant adding additional elements would also need to employ controlled randomness, and that would likely result in a lot of work. On top of that, designing maps with randomness in mind is hard. It would likely take months just to prototype ideas, let alone flesh them out into complete mechanics.

This aspect, more than anything else, was a huge influence in our decision to not expand on Sanity Wars Reimagined for the next project, but to concentrate on a more linear experience. (More on that later.)

Clean Code

If you’re a programmer, you might be surprised at seeing “clean code” as a heading under complications. If you’re not a programmer, let me explain, very roughly, what clean code is: a mindset for writing code in such a way that it is easy to understand, has specific responsibilities, and avoids creating the same lines of code in different files; through these principles one’s code should be easier to comprehend and use throughout your codebase.

Under most circumstances, writing clean code is essential for making code not only easier to work with, but faster to develop. So how did writing clean code make Sanity Wars Reimagined more complex?

Simply put, the issue wasn’t strictly with adhering to clean code principles in and of themselves; the issue was when I spent a lot of time and effort coming up with clean code for flawed systems. Clean code doesn’t mean the things you create with it are perfect. In fact, in Sanity Wars Reimagined, some of the things I created wound up being harmful to the resulting gameplay.

A prime example of how this impacted development is the way I implemented movement for the Eyeball entity. I had the thought of creating a steering behavior-based movement system; rather than giving the entity a point to navigate to and allowing it to move straight there, I wanted to have the entity behave more like real things might move (to put it in very simple terms). I then spent a long time creating a locomotion system that used steering behaviors, trying to make it as clean as possible.

In the end, my efforts to integrate steering behavior movement were successful. There was a huge flaw, however; the movement hampered gameplay. Steering behaviors, by design, are intended to give less-predictable behavior to the human eye, which makes it harder to predict how the eyeball is going to move when it isn’t going in a straight line. This style of movement also meant the Eyeballs could easily get in a position where it was difficult for the player to hit them with the straight-line spirit bullet projectile, which was specifically intended for destroying Eyeballs. Since steering behaviors work by applying forces, rather than explicitly providing movement direction, there wasn’t an easy, feasible way for me to tweak the movement to make it easier for the player to engage with Eyeballs.

In addition to making Eyeballs less fun to play against, the steering behaviors also made it hard to make Eyeballs move in very specific ways. When I was trying to create a dive attack for the Eyeball, I literally had to hack in movement behavior that circumvented the steering behaviors to try and get the attack to visually look the way I wanted to; even then, I still had a lot of trouble getting the movement to look how I felt it should.

How did clean code contribute to this, precisely? Well, I’d spent a lot of time creating the locomotor system and making it as clean an implementation as I possibly could, before throwing it into the gameplay arena to be tested out and refined. If there had been time to do so, I likely would have needed to go back and refactor the Eyeball movement to not use steering behaviors; all that work I’d spent making the steering behavior implementation nice and clean would’ve gone to waste.

Don’t get me wrong; writing clean code is very important, and there is definitely a place for it in game development. The time for that, however, is not while figuring out if the gameplay works; there’s no sense in making something clean if you’re going to end up throwing it out shortly thereafter.

Playtesting

I didn’t let other people playtest the game until way too late in development. Not only did that result in not detecting crashes in release builds, it also meant I ran out of time to properly take feedback from the playtests and incorporate it back into the game.

For the first three months of development, I never created a single release build of Sanity Wars Reimagined. I kept plugging away in development builds. The first time I attempted exporting the project was the day, no, the evening I was scheduled to bring the game to a live-streamed playtest session with IGDA Twin Cities. As a result, it wasn’t until two hours before showtime that I found out that my release builds crashed on load. I spent a frantic hour hack-fixing the things causing the crashes, but even with that, the release build still had a major, game-breaking bug in it: the testers couldn’t complete the game because no portals spawned. Without being able to complete the game, the testers couldn’t give me good feedback on how the game felt to them. From that point onward, I made a point of testing exports regularly so that something like that wouldn’t catch me off-guard again.

The next time I brought the game out for playtesting was in the middle of January 2022, three months after the first playtest. At that point, I’d resigned myself to the fact that Sanity Wars Reimagined didn’t feel fun, and was likely going to be released that way; my intent with attending the playtest was to have people play the game and help me make sure I didn’t have any showstopping bugs I’d need to fix before release. What I wasn’t expecting (and, in retrospect, that was silly of me) was that people had a lot to say about the game design and ways it could be made more fun.

To be honest, I think I’d been stuck so long on the idea that I wanted to faithfully recreate the original game’s mechanics that I didn’t even think about making changes to them. After hearing the feedback, however, I decided that it would be more important to make the game as fun as I could before release, rather than sticking to the original mechanics.

That playtest, however, was one and a half weeks before the planned release date, meaning that I had very little time to attempt making changes of any significance. I did what I could, however. Some of the things I changed included:

  • Removing the sacrifice aspect of using spells. Players could now access spells right away, without having to sacrifice their maximum sanity.
  • Giving both the spells dedicated hotkeys, to make them easier (and thus more likely) to be used.
  • Adding a countdown timer, in the form of reducing the player’s maximum sanity every so often, until the amount was reduced to zero, killing the player. This gave players a sense of urgency that they needed to resolve, which was more interesting than simply exploring the maps with no time constraints.
  • Changing the Eyeball’s attack to something that clearly telegraphed it was attacking the player, which also made them more fun to interact with.
  • Adding a scoring system, to give the player something more interesting to do than simply collecting tomes and finding the exit portal.
  • Various small elements to add juice to the game and make it feel more fun.

Although we did wind up extending the release date by a week (because of dealing with being sick on the intended release week), I was surprised with just how much positive change I was able to introduce in essentially two and a half weeks’ worth of time. I had to sacrifice a lot of clean code principles to do it (feeding into my observation about how doing clean code too early was a problem), but the end result was an experience that was far more fun than it was prior to that play test.

I can only imagine how much more fun the game could’ve been if I’d had people involved with playtesting in the early stages of development, when it would’ve been easier to change core mechanics in response to suggestions.

Thanks to the IGDA Twin Cities playtest group, and specifically Mark LaCroix, Dale LaCroix, and Lane Davis, for offering many of the suggested changes that made it into the final game. Thanks also to Mark, Lane, Patrick Grout, and Peter Shimeall for offering their time to playtest these changes prior to the game’s release.

Lack of a Schedule

I mentioned previously that I’d thought the entire Sanity Wars Reimagined project wouldn’t take more than a month, but I hadn’t actually established a firm deadline for when the project needed to be done. I tried to implement an approach where we’d work on the project “until it felt ready”. I knew deadlines were a way that crunch could be introduced, and I wanted to avoid putting ourselves in a situation where we felt we needed to crunch to make a deadline.

The downside, however, was that there wasn’t any target to shoot for. Frequently, while working on mind-numbing, boring sections of code, I had the dread fear that we could wind up spending many more months on this project before it would be finished. This fear grew significantly the longer I spent working on the project, my initial month-long estimate flying by the wayside like mile markers on a highway.

Finally, out of exasperation, I made the decision to set a release date. Originally, the target was the middle of December 2021, but the game wasn’t anywhere near bug-free enough by that point, so we pivoted to the end of January 2022, instead. As that deadline approached, there were still dozens upon dozens of tasks that had yet to be started. Instead of pushing the deadline out again, however, I went through the list to determine what was truly essential for the game’s release, cancelling every task that failed to meet that criteria.

Things that hit the cutting room floor include:

  • Adding a second enemy to the game, which would’ve been some form of ground unit.
  • Refactoring the player’s jump to feel better.
  • Fixing a bug that caused the jump sound to sometimes not play when it should.
  • Adding keyboard navigation to menus (meaning you had to use the mouse to click on buttons and such).
  • Create maps specifically for the release (the ones in the final build are the same as the ones made for the second playtest).

It’s not that these things wouldn’t have improved the game experience; it’s just that they weren’t essential to the game experience, or at least not enough to make it worth extending the release date to incorporate them. By this point, my goal was to finish the game and move on to the next project, where, hopefully, I could do a better job and learn from my mistakes.


These are far from the only complexities that we had to deal with during Sanity Wars Reimagined, but they should serve to prove that a lot of issues were encountered, and a lot of mistakes were made. All of these things, however, are learning opportunities, and we’re excited to improve on the next project.

Improvements for Next Time

There’s a lot of things that I want to try for the next project; many of them serve as attempts to address issues that arose during the development of Sanity Wars Reimagined.

Have A Planned Release Date

I don’t want to feel like there’s no end in sight to the next project, so I fully intend to set a release date target. Will we hit that target? Probably not; I’m not a great estimator, and life tends to throw plenty of curveballs that wreak havoc on plans. By setting an end goal, however, I expect that it will force us to more carefully plan what features we want to try and make for the next game.

In tandem with that, I want to try and establish something closer to a traditional game development pipeline (or, at least, what I understand of one), with multiple clearly-defined phases: prototyping, MVP, alpha, beta, and release. This will hopefully result in lots of experimentation up front that settles into a set of core mechanics, upon which we build lots of content that is rigorously tested prior to release.

Prototype Quickly Instead of Cleanly

Admittedly, the idea of not focusing on making my code clean rankles me a bit, as a developer, but it’s clear that development moves faster when I spend less time being picky about how my code is written. Plus, if I’m going to write something, find out it doesn’t work, and throw it away, I want to figure that out as quickly as possible so I can move on to trying the next idea.

Thus, during the prototyping phase of the next project, I’ll try to not put an emphasis on making the code clean. I won’t try to write messy code, of course, but I’m not going to spend hours figuring out the most ideal way to structure something. That can wait until the core mechanics have been settled on, having been playtested to confirm that said mechanics are fun.

Playtest Sooner Rather Than Later

The feedback I received from the final big playtesting session of Sanity Wars Reimagined was crucial in determining how to make the game more fun before release. For the next project, I don’t want to wait that long to find out what’s working, what’s not, and what I could add to make things even more fun.

I don’t think I’ll take it to public playtesting right away, but I’ll for sure reach out to friends and interested parties and ask them to try out prototype and MVP builds. It should hopefully be much easier to make suggested changes during those early stages, versus the week before release. With more frequent feedback, I can also iterate on things more often, and get the mechanics to be fun before locking them down and creating content for them.

Make a Linear Experience

After realizing how much work it would be to try and craft a good random experience, I’ve decided that I’m going to purposely make the next game a linear experience. In other words, each playthrough of the game won’t have randomness factoring into the gameplay experience. This may be a little more “boring”, but I think doing it this way will make it easier for me to not only practice making good game design, but make good code and good content for as well.

Will it be significantly less fun than something that introduces random elements to the design? Maybe, maybe not. We’ll find out after I attempt it!


Those are just a few of the things I intend to try on the next project. I don’t know if all of the ideas will prove useful in the long run, but they at least make sense to me in the moment. That’s good enough, for now. Whatever we get wrong, we can always iterate on!

Conclusion

That’s the story of Sanity Wars Reimagined. We started the project as an attempt to make a quick release to gain experience creating games, and despite taking significantly longer than planned, and the numerous mistakes made along the way, we still wound up releasing the game. Along the way, we developed numerous technologies, and learned lots of lessons, that should prove immensely useful for our next project. Because of that, despite the resulting game not being as fun as I wish it could’ve been, I consider Sanity Wars Reimagined a success.

What’s next for Rebecca and I? It’ll for sure be another platformer, as that will allow us to make good use of the technologies and processes we’ve already developed for making such games. I fully expect there will be new challenges and complications to tackle over the course of this next project, and I can’t wait to create solutions for them, and learn from whatever mistakes we make!

Sanity Wars Reimagined released!

The game PixelLunatic and I have been working on for the past seven months, Sanity Wars Reimagined, has been officially released! Download it here.

It’s been a long seven months, working on this game, and while there’s still plenty of things that could be improved, overall I’m pleased with how this game turned out. Most importantly, we learned many things that will prove useful for our future game development endeavors.

We hope you enjoy our game!

Implementing the Messenger Pattern in Godot

Note: If you implemented this pattern and are now experiencing issues with get_tree() calls, see my Issues with get_tree() section at the end of the article.

Oftentimes, in code, you need a way to have different parts of the codebase communicate with each other. One way to do this is have those components directly call methods from another component. While that works, it means you directly couple those components together. If you want to reuse one component in another project, you either have to take all the directly-coupled components with it or you have to refactor the direct couplings out of the component you want to reuse, neither of which is desirable from a clean code standpoint.

A way to solve this problem is to use the signal pattern. This is where each component can emit a named signal, and other components can then be connected to that signal. From that point on, whenever that signal is emitted by the component, anything that is listening for that signal can run code in response to that emission. It’s generally a great pattern, allowing for code to indicate when some event, or signal, happens, and for other parts of code to respond to that event accordingly (without code directly relying on calling methods from one another).

There is a third way to have decoupled components communicate to one another: the messenger pattern. At surface level, it’s very similar to the signal pattern: a part of your code dispatches a named message, and any code that is listening for that particular message can respond to it. Those different parts of your code aren’t connected to one another, however; instead, they interact through a Messenger node. Code that wants to listen for a message registers a message listener to the Messenger, and when another part of code dispatches a message with that name, the Messenger loops through all the registered listeners for that message name and invokes their callback functions.

Both the signal pattern and the messenger pattern can be considered subsets of the Observer pattern. The key difference is that the signal pattern has many objects connecting to one (the object emitting the signal), while the messenger pattern has a mediator object through which messages are dispatched and listened for by other objects. Which is better? It depends on what you are trying to accomplish architecturally, and there’s no reason you can’t use both.

Let’s discuss specifics, with relation to what Godot uses. Godot has the signal pattern baked into it at the core. Nodes can define signals through use of the signal keyword. Any node that wants to listen for another node’s signal can connect() to that node’s signal and associate a callback function to it. It looks like this, at a simplified level:


# OrdinaryNode
extends Node
signal some_cool_thing

# DifferentNode
extends Node

func _ready():
  # Assuming both OrdinaryNode and DifferentNode are children of a hypothetical parent node.
  get_parent().get_node('OrdinaryNode').connect('some_cool_thing', self, '_do_something_awesome')

func _do_something_awesome():
  print("This is awesome!")

From then on, whenever OrdinaryNode emits the some_cool_thing signal, the _do_something_awesome() function in DifferentNode will run, printing “This is awesome!”

While this is a good implementation of signals, the nature of how the signal pattern works implies some shortcomings. For instance, all signals must be explicitly defined in code. You can’t have OrdinaryNode, as written above, emit a coffee_break signal because the code didn’t explicitly define that such a signal exists. This is by design, as it means you have to plan what your node can and can’t emit. Sometimes, though, you do want to have a more flexible way to communicate with other nodes, and at that point signals can’t help you. This is one thing the messenger pattern can help with, by not requiring you to explicitly define what messages can or can’t be sent.

Another aspect of the signal pattern is that it requires you to have nodes define a connection to the node emitting the signal if you want those nodes to react to the signal. That means those nodes must, by definition, couple themselves to the node emitting the signal (though the emitter node doesn’t know, or care, about those couplings). This isn’t necessarily bad, but it limits how you can architect your code; you have to make sure nodes that need to listen for a specific signal are able to connect to the node emitting said signal. Conversely, using the messenger pattern, you can have nodes connect only to a single Messenger node, which can be simpler to implement.

Godot does not natively implement such a messenger node, however. If we want to use this messenger pattern, we’ll need to make something ourselves. That’s what this tutorial will be about.

Note: What I’m calling the Messenger Pattern is more commonly known as the Mediator Pattern. I came up with the name Messenger before I learned what it is called, and I’ll continue to use it in this tutorial because I think it communicates more clearly what I’m using it for.

Setting Up

There is a sample project, if you want to refer to the finished product.

If you want to code alongside the tutorial, start by creating a new Godot project, then create a GDScript file named Messenger.gd. We’ll make this as the base file that other implementations of messengers can extend to provide their own functionality.

The original project was created in Godot 3. Here is a branch that is configured for Godot 4. (Thanks to valVk for assisting the Godot 4 conversion!)

Adding and Removing Listeners

The first thing we want to do is provide a way to add and remove message listeners. Let’s begin with adding listeners.


var _message_listeners := {} # Stores nodes that are listening for messages.


# Add object as a listener for the specified message.
func add_listener(message_name: String, object: Object, method_name: String) -> void:
  var listener = { 'object': object, 'object_id': object.get_instance_id(), 'method_name': method_name }
  
  if _message_listeners.has(message_name) == false:
    _message_listeners[message_name] = {}
  
  _message_listeners[message_name][object.get_instance_id()] = listener

This is fairly straightforward. We take the name of the message, the object that has the callback function, and the name of the callback. We store all that in a listener dictionary (defined as a class property outside of the function) and store it in _message_listeners in the dictionary stored at the key matching the message name (creating a dictionary for that key if it doesn’t already exist). We key this listener in the message_name dictionary to the object’s instance id, which is guaranteed to be unique.

Since Godot implements signals at the object level (Node inherits from Object), I’ll be typing these as Objects rather than Nodes, which allows for any node inheriting from Object to be used as a listener (including Resources).

Next, the ability to remove a registered listener.


# Remove object from listening for the specified message.
func remove_listener(message_name: String, object: Object) -> void:
  if not _message_listeners.has(message_name):
    return
  
  if _message_listeners[message_name].has(object.get_instance_id()):
    _message_listeners[message_name].erase(object.get_instance_id())
  
  if _message_listeners[message_name].empty():
    _message_listeners.erase(message_name)

Again, fairly straightforward. We run existence checks to see if a listener exists at that message_name key, and erase it from the dictionary if so. Additionally, if no more listeners exist for that message_name, we erase the dictionary for listeners of that message name.

Sending Messages

Now that we can add and remove message listeners, it’s time to add the ability to send those messages.


# Sends a message and triggers _callbacks on its listeners.
func dispatch_message(message_name: String, data := {}) -> void:
  var message = { 'name': message_name, 'data': data }

  _process_message_listeners(message)

We take a message_name string and a data dictionary (which defaults to be an empty dictionary), store it to a message variable, and pass that variable into _process_message_listeners.


# Invoke all listener callbacks for specified message.
func _process_message_listeners(message: Dictionary) -> void:
  var message_name = message.name
  
  # If there aren't any listeners for this message name, we can return early.
  if not _message_listeners.has(message_name):
    return
  
  # Loop through all listeners of the message and invoke their callback.
  var listeners = _message_listeners[message_name]
  for listener in listeners.values():
    # Invoke the callback.
    listener.object.call(listener.method_name, message.data)

This is where we handle triggering the callbacks for a message listener. If there aren’t any listeners for that message name, we return early to avoid doing further processing. If there are listeners for that message name, then we loop through each one and trigger the stored method callback, passing in the message’s data dictionary.

That’s it, as far as the basic implementation goes. But there are a couple of caveats that need to be dealt with.

Dealing with Nonexistent Listeners

One such case happens when a listener’s object is freed, making the stored reference in the listener dictionary invalid. If you try to operate on it, Godot will crash, so we need to provide a way to scan for dead listeners and remove them from storage.

Let’s start with a function to perform both the check and the purge:


# Removes a listener if it no longer exists, and returns whether the listener was removed.
func _purge_listener(listeners: Dictionary, listener: Dictionary) -> bool:
  var object_exists = !!weakref(listener.node).get_ref() and is_instance_valid(listener.node)
    
  if !object_exists or listener.node.get_instance_id() != listener.node_id:
    listeners.erase(listener.node_id)
    return true

  return false

Multiple checks are used to see if the object exists (I’ve found in practice that I’ve needed both of these, not just one or the other). We also check to see if the instance id of the stored listener matches the id of the listener object we passed in; honestly, I can’t recall when or why that particular scenario occurs (I sadly forgot to write a comment about it in my code), but I know I’ve encountered it in the past, so I continue to include it as part of my check. If the object doesn’t exist, or the ids don’t match, we conclude the listener’s object no longer exists, and thus remove the listener from storage. Finally, we return a boolean value indicating whether the purge was performed or not.

Now we need to modify our existing code to use this function.


func _process_message_listeners(message: Dictionary) -> void:
  # ...existing logic
  
  for listener in listeners.values():
    # If the listener has been freed, remove it
    if _purge_listener(listeners, listener):
      # Check if there are any remaining listeners, and erase the message_name from listeners if so.
      if not _message_listeners.has(message_name):
        _message_listeners.erase(message_name)
        return
      else:
        continue

    # ...existing logic

The difference is we call _purge_listener before we try to invoke the callback. If the listener was purged, we perform an additional check to see if there are any other listeners of message_name, and erase the dictionary keyed to message_name if there aren’t; otherwise, we proceed to the next listener in the for loop.

That takes care of dead listeners. There’s one more problem we need to address.

Dispatching Messages Too Early

Right now, if we try to send and listen for messages during the ready process (when Godot’s nodes all run their _ready callbacks), then we’ll likely run into issues where messages are dispatched before the listeners of those messages are registered (because their ready callbacks run later than when the messages are sent). To solve this, we’re going to add a message queue. If a message is being dispatched before the root node of the scene tree is ready, we’ll add the message onto this queue, and once the root node emits its ready signal we’ll process all the messages in the queue.

Let’s start with setting up the message queue, and modifying our dispatch_message function.


var _message_queue := [] # Stores messages that are being deferred until the next physics process tick.
var _messenger_ready := false # Is set to true once the root node is ready, indicating the messenger is ready to process messages.

# Sends a message and triggers _callbacks on its listeners.
func dispatch_message(message_name: String, data := {}) -> void:
  var message = { 'name': message_name, 'data': data }

  if _messenger_ready:
    _process_message_listeners(message)
  else:
    _message_queue.push_back(message)

We’ve added two new class properties, one to house the message queue and the other to mark when the messenger node considers itself ready. dispatch_message has been modified to first check _messenger_ready, and if so it runs the code the same as before. If the messenger node is not ready, then the message is pushed onto the message queue.

Now let’s set up the ability to process the message queue.


func _ready() -> void:
  get_tree().get_root().connect('ready', self, '_on_Root_ready')


# Is called when the root node of the main scene tree emits the ready signal.
func _on_Root_ready() -> void:
  _messenger_ready = true
  _process_message_queue()


# Process all messages in the message queue and reset the queue to an empty array.
func _process_message_queue() -> void:
  for message in _message_queue:
    _process_message_listeners(message)
  
  _message_queue = []

In Messenger’s own _ready callback, we register a listener to the scene tree root’s ready signal. The callback then sets _messenger_ready to true and calls a function, _process_message_queue(), which loops through each message in the queue and calls _process_message_listeners() on them. At the send, we clear the message queue, since we don’t need (or want) to process these messages again.

Creating a GlobalMessenger

At this point, we have a base Messenger class that can be used anytime we want to implement the messenger pattern in our code. Let’s demonstrate this by creating a global singleton, GlobalMessenger, that we can interact with from anywhere in our codebase.

Start by creating a new file, global_messenger.gd, and have it extend our Messenger class. If Godot claims the Messenger class doesn’t exist, then you’ll need to reload the project to force Godot to update itself and recognize the Messenger class we added in Messenger.gd.


# Creates a global messenger that can be accessed from anywhere in the program.
extends Messenger

The reason I made this file name snake_case is because my personal convention is to name files that are solely used as singletons with this format, to distinguish them from files containing extensible classes. This is my personal preference only, and is not required to make this code work.

That’s all that needs to be done from a code standpoint. To make this a globally-available singleton, we need to go to Project -> Settings in the editor menu, navigate to the AutoLoad tab, and add global_messenger.gd to the list of autoloaded files.

And…that’s it! We now have a global singleton that we can use from anywhere in our codebase to dispatch messages!

Deferring Messages

Let’s add some additional functionality to our global messenger. For instance, right now, once the messenger is ready, we immediately run listener callbacks upon receipt of the message. What if we wanted to defer message dispatches until the next process tick? It might prove useful to ensure all game data is updated by the time your message callbacks are being run.

We already have a message queue that is used to make sure messages are deferred until the messenger is ready. We can build on that to add functionality to intentionally defer message dispatching until the next physics process tick.


func _ready() -> void:
  set_physics_process(false)


func _physics_process(_delta) -> void:
  ._process_message_queue()
  set_physics_process(false) # We don't need to keep updating once messages are processed.


# Queues a message to be dispatched on the next physics processing tick.
func dispatch_message_deferred(message_name: String, data := {}) -> void:
  _message_queue.push_back({ 'name': message_name, 'data': data })
  
  set_physics_process(true)

First, we use _ready() to disable physics processing. That’s because, whenever _physics_process() is defined in a script file, Godot automatically enables processing. We only want to process when there are messages in queue, so we just disable physics processing right off the bat.

I use _physics_process instead of _process to ensure messages are processed at a consistent rate. physics_process is run a consistent amount of times per second, whereas _process is run as often as possible, and I’ve found that having messages processed as fast as possible can result in unexpected complexity when sent from code that is expecting a consistent frame rate.

Next, in the _physics_process() callback, we call _process_message_queue(), then disable physics processing again (basically, only running the update step a single time).

Finally, we create a new function, dispatch_message_deferred, making it obvious that calling this will be different from a regular message dispatch. We add the message straight onto the message queue. Afterwards, we set the physics processing step to be true. This way, the next time _physics_process() callbacks are run in the code, the global messenger’s _physics_process() callback will be run, too. And since it is a global singleton, it will be run before other nodes in the root scene.

That’s it!

Testing our Implementation

Now that we have a Messenger node, and a GlobalMessenger implementation of it, let’s set up a test scene in our project to test their functionality and make sure they work as intended.

Create a new scene, TestScene, then structure it thusly:

LocalMessenger is a node which is extended from Messenger; we will use this to test that a locally-built implementation of our messenger node works.

The other two nodes, OrdinaryNode and DifferentNode, should contain the following code:


# OrdinaryNode
extends Node


onready var localMessenger = $"../LocalMessenger"


func _ready() -> void:
  GlobalMessenger.dispatch_message('test_1', { 'fish': 'shark' })
  localMessenger.add_listener('test_local', self, '_on_Test_local')


func _on_Test_local(data) -> void:
  print('Do you like looking at the ', data.animal, '?')
  
# DifferentNode
extends Node


onready var localMessenger = $"../LocalMessenger"


func _ready() -> void:
  GlobalMessenger.add_listener('test_1', self, '_on_Test_1')
  localMessenger.dispatch_message('test_local', { 'animal': 'rabbit' })


func _on_Test_1(_data) -> void:
  print('Test 1 received')

At this point, if you run the scene, you should see the two messages printed to console. If you do, then everything was set up correctly!

Issues with get_tree()

Recently (in May 2023), I encountered a strange bug where a message callback that invoked get_tree() was not returning the scene tree, despite the node housing the callback function being in the scene tree. After some investigation, I realized that I was calling the add_listener() function from the node’s _ready() callback; when I switched to adding the listener in _enter_tree() and removing it in _exit_tree() the get_tree() call worked as expected.

I admittedly am not entirely sure why this works, but my theory is that adding the listener during _ready() is either storing the reference to the function call when the tree is not yet defined or subsequent tree exits and enters is causing the reference to be lost. In any case, I wanted to add this addendum in case anyone else chose to implement this pattern and ran into the same problem.

If you happen to know more info about why this might have happened, please let me know!

Conclusion

We now have a base Messenger node, as well as a GlobalMessenger singleton that extends it and adds defer functionality to it. When should it be used? Personally, I use the messenger pattern in cases where I want to enable node communication, but for whatever reason it doesn’t benefit me to define the specific signals ahead of time, which is when the messenger’s dynamism comes into play.

Of course, that dynamism leads to the risk of making messy code. One advantage to explicitly forcing signals to be defined is that it forces you to think about how you are architecting your code, by making you think clearly about how your signals are going to be used. Since Messenger lets any node send whatever message it wants, it falls on you to make sure that power isn’t abused to send messages when the situation doesn’t call for it. For instance, if you have one node which you want other nearby nodes to listen for a specific event from, you don’t need the dynamic nature of Messenger; signals work perfectly fine, and are a cleaner way to get the job done.

As with all things, in life and code, consider carefully how you do things, and use whatever tools and patterns best fit your needs.

How I Structure My Game Projects

As a programmer, I spend a lot of time figuring out how things should be named and organized. I want things to have readable, comprehensible names, and to be organized in ways that make sense to me. To that end, when I started out in game development, I wanted to figure out how I should organize the files and directories in my game code.

Surprisingly, I’ve found little in my internet searching that speaks to this topic, nor to how things should be named. It just doesn’t seem to be talked about much, and I think that’s a shame. Sure, in the end, the game’s logic doesn’t care where you place your files, or what you call them, but having an understandable code base makes it easier for you to comprehend what particular files are for, without having to look directly at the code or the places that code gets used, and having easy understanding of your code makes for easier development. To that end, I’m writing a high-level overview of my current project, Sanity Wars Reimagined, which contains my latest iteration of my code’s architecture. I’ll show the high-level directory structure, and explain how I came to name those higher-level directories.

Is it something that you should take as gospel and use for all of your projects? No, because everyone’s code needs are different, which is probably part of the reason why articles and videos on this topic aren’t easy to find. What I intend this article to be is an overview on how I structure things and why I do things the way that I do. Hopefully, someone who is looking for inspiration on how to organize their own code might see things they like and incorporate them, saving them the time it took me to figure those things out through trial and experience.

I’ll be the first to admit that, even though generally I like where things are at now, there are still aspects that I’m not entirely satisfied with, so even though I have guidelines on how I should structure things, if my guides don’t make sense, I’ll break away from them to try something that seems better. If they do prove better than my old ways, I’ll incorporate them into my next project. There’s a few instances of this happening in SWR, and I’ll point them out as I go through my code base.

The Project Directory

Without further ado, here is the top level of my SWR directory:

I like to keep my top level relatively simple. The things that go here (other than Godot and VS Code-related files) represent the highest-level reduction of a collection that I could think of. I don’t like seeing too many files and folders at once, as it makes it harder for me to scan the architecture at a glance, so the more I can reduce things, the better.

Note that I’m using Visual Studio Code. Godot lets you plug in third-party editors to use in place of its own built-in editor, and since I use VSC for my day job (web development), I also use it for my game development. There are also things that an external editor shows you that Godot’s editor hides, like markdown files (which I use for my README files) and JSON files (a file format I work with for some kinds of data).

While I’ll generally avoid going into the specifics of how any particular file or system works, I do want to take a moment to explain one particular file in the project directory, the INIT.tscn file. I have a Godot boilerplate project (which I call “Genesis”) that I clone whenever I want to start a new project. Having boilerplate to start with means I can take things I liked about previous projects and incorporate them in a generalized form, so all future projects I make benefit from what I’ve made in the past. How does the INIT file factor into that? That’s the file I’ve marked as the default scene in the Godot project config, and it reads a value in CORE_CONFIG.gd that tells it which screen file I want it to load first. Note that I said screen, not scene. I’ll get more into what screens are later on.

Why not just change the default screen with each project instead of changing my own config value? To give the short explanation, it gives me a consistent, expected environment for my global scripts to hook into. There might be better ways to do it, but this is what I’ve come up with for now, and it works well enough that I haven’t felt a pressing need to change it. An important aspect of architecting code is that there will be plenty of times you feel something isn’t ideal, but running with a slightly imperfect solution is better than spending a lot of time trying to come up with a perfect solution, only to find later on that future scenarios render that perfect solution imperfect. Iteration makes perfection.

Now that we’ve seen everything at a glance, let’s look at each of the top-level directories in my project.

_debug

As explained in previous tutorial posts (starting with this one), I have a custom system that I use to aid with debugging my code in-game, by providing a comprehensive, extensible way to render debugging information widgets. The _debug directory is where I store the code that makes this system work. Since I believe in keeping debug code as isolated from game logic as I can, I keep the scenes and scripts for my debug code in a separate location as well.

Note that I start the directory name with an underscore. In many programming languages, starting a name with an underscore indicates that this is private to some particular scope or class. That’s roughly the idea I’m trying to communicate by using it here. Nothing in _debug is used to make game logic run, so I want the directory name to help communicate that.

_samples and _tests

_samples is something that I include with my boilerplate that contains sample projects demonstrating some of the game systems I’ve built, so I can mess around with them and see how things should look by default. _tests is where all my experiment code goes. If I’m trying to build a new system, I’ll make test directories and files for it in here so I can experiment with the system in isolation before incorporating it with the rest of the game.

One thing about both of these directories is that I specifically filter them out of game exports. These are never intended to be used in my game code, so there’s no reason to include them in official game builds. The reason I don’t do this with _debug as well is because I have to reference the debugging global in my game code to make use of the debugging tools, and I haven’t yet come up with a place to house my debugging global somewhere that makes sense other than in _debug itself. As mentioned before, this is running with a slightly imperfect solution, and if I come up with something that’s better I’ll work it in.

addons

This is what Godot expects you to call the folder which houses third-party engine plugins. I use it for that purpose, of course, but I also use it to house any third-party code (such as GDQuest’s Steering Behaviors AI Framework) that I intend to use exactly as-is in my game code. addons might not be the best name for code serving this latter purpose, but I decided that I preferred to keep all third-party code in a single location instead of using two accurately-named directories, one for proper addons and one for other third-party code. It keeps the top-level architecture simpler, in my opinion.

assets

This is a place where things which are considered resources for other game systems are stored. This includes the obvious in audio and graphics, but also includes scenes that are treated as resources, particles, and shader scripts, among other things. This keeps all those things in one location, and by storing all instances of such things in that one location, I know exactly where to look when I’m looking for anything that I intend to use as some form of content resource.

At the root level of the directory is a README file. This is what I leave for documenting what I intended this directory to be used for, to help future me (and, maybe, future contributors to my game code, should things get to that point) remember what I was thinking. By including this documentation, when I’m considering where to put a new scene or directory, I can refer to a directory’s README file to understand what I intended it to be used for, and see if the new thing I’m making fits that criteria (and if it should fit the criteria and my description says otherwise, I can update the description).

config

This directory is specifically for systems which use some form of scene/node-based configuration, which means they don’t quite fit within the systems directory. This is a new approach I’ve been trying based on past experiences. I’m used to strictly string/file-based configuration files (which is why there is a CORE_CONFIG file at the project level), but making scene-based config files allows for a lot more flexibility in how your configuration works, plus it gives the benefit of using the editor UI to manage your configuration data, which can make for better visual presentation.

Next are the connectors and core directories, but I’m going to skip them for now and come back to them later, as explaining their purpose makes more sense after seeing what the other directories are used for.

effects

This is a directory where I’ve kept scenes that implement a particular effect, such as a 2D trail or shield bubble. Note from the image that there isn’t anything in this directory other than the README file. That’s because I made it part of my boilerplate code, but by default my boilerplate directories don’t include game-specific code.

That said, I’m starting to wonder if this isn’t better served being part of the assets directory. Effects could easily be considered a form of content. This might be something I refactor out in the future.

game_objects

In this directory live all of the objects which get used within the game world, or that augment other game objects. The way I architect this file is the one that is most in flux, since depending on what game I build there could be different ways to classify the game objects. To that end, while I include a default set of directories for the game_objects directory in my boilerplate code, I have the expectation that I’ll likely be making a lot of changes to this architecture.

One subdirectory of game_objects I’ll dig more into is entities. This is where I house any game object that is meant to be rendered visually within the game. Here I store things like the player character, enemies, projectiles, decorative objects, etc. As with the rest of game_objects, the specific architecture of the entities directory is going to adapt whatever structure makes sense for the particular project I’m working on.

There is also a test file, which contains some simple test characters. This predates some of the conventions I’ve come up with for my code, like storing all such test code under _tests, so I plan to refactor this out in the future and move the contents to more appropriate places.

game_world

Here is where I store all scenes that pertain to building the game world. Things like levels, maps, and world areas are included here. Why not make this a part of game_objects, since it could be argued that these are all game objects? I currently think it makes sense to keep things that make up the level formation, such as maps, separate from the objects the populate those worlds. In other words, it’s a personal preference.

The MapManager directory doesn’t really belong here. It’s a system that manages which map is being shown, and although it works with maps, the fact that it’s a system means it would be more accurate to have it be under the systems directory. I’ll probably refactor this at some point.

helpers

These are where files containing generalized helper functions are located. For instance, the MATH helper consists of any functions designed around math calculations that Godot doesn’t provide out of the box. If a helper is system-specific, however, then it would stay with the respective system’s directory instead of being placed here.

screens

This is where I store screens. What’s a screen? It represents whatever collection of systems, game objects, and UI elements the player is currently engaging with. The MainMenuScreen, for example, contains the main menu interactions and visuals. The IntroScreen is where I show the game’s introductory text, TestGameScreen is the screen where actual gameplay for Sanity Wars Reimagined takes place (someday I might remove the “test” prefix because, well, it’s the actual game screen at this point), and the various end screens are where I show the text you see when an end game condition is triggered.

systems

Here reside the various gameplay systems that implement the gameplay for Sanity Wars Reimagined. portals contains the systems logic for making the portals work, while generators contains the base code which is used to control automated spawning of game objects, which is built on for both Eyeball and Tome generation on maps. camera contains the base game objects which implement customized cameras that extend on Godot’s inherent camera nodes.

Didn’t I say game objects belong in the game_objects directory, though? Well, to be more specific, game_objects contains the specific implementations of game objects, whereas any game objects in systems are the foundations on which those specific implementations extend.

Before I move on to ui, I want to go back to one of the directories that we skipped earlier.

connectors

This directory is exclusively for connecting different game systems together. A connector node is a single node that takes two or more other systems nodes as setup arguments, and then applies any connections and custom logic to make those different systems interact with each other. It seems a little convoluted, compared to just building those connections directly into the systems themselves, but, by doing so, I can build individual systems that handle their own logic without directly relying on other systems, and thereby make them easier to reuse with different systems.

Admittedly, this kind of thing isn’t necessarily useful for the specific implementations I’m making for this specific game; the benefit, in theory, will arise from future projects where I want to use certain systems I’ve built for Sanity Wars Reimagined and connect them with the systems of said future projects. I’m still on the fence on whether this abstraction will actually prove useful, but, for now, this is the best solution I’ve come up with to making my systems reusable. If I find a better way to do it in the future, I’ll implement it.

With that out of the way, let’s jump back to

ui

This is both a collection of generalized UI elements that I can build on and the specific implementations of UI elements as shown in my game. Examples of the former include the Sanity gauge and the spell indicator buttons, and an example of the latter is the UI display node which contains the Sanity gauge and each spell indicator being used for the game’s selection of spells.

Given some of the distinctions I’ve made for various game objects, it might seem surprising that I simply lump all the UI elements together. Admittedly, that bothers me a little bit, too, but I haven’t yet thought of a way to cleanly separate the base UI elements and their implementations in a way that makes more sense to me than what I’m doing now. In the meantime, it’s useful for me to know where I store all the UI elements for my game, so they all go here.

Well, not all of them. It’s time now to go back to the other directory I skipped earlier.

core

The core directory, at first glance, looks suspiciously like a microcosm of the project directory, containing directories with names matching the ones I outlined earlier in the article. core is used to contain elements of my code base which I’ve successfully generalized to be reused across any game I make. Therefore, anything housed in core is something which I can safely build specific implementations on top of, without worrying about said things implementing some functionality from a past project.

This is crucial to my approach for making reusable code. Earlier, I mentioned that I make use of Genesis, a boilerplate Godot project. Specifically, the only code that is actually stored in Genesis which gets copied over are the things in this core directory. All the project directories I’ve gone over start out as empty shells, waiting to be filled out with my game-specific implementations that extend from these core systems and files.

What if I need to change how a core system works? In that case, I move the directory/file I need to customize out of core and into its appropriate project-level directory. Why not just modify the things in core directly? It’s out of convention; by enforcing the idea that things in core are never meant to be modified, I can be confident that whatever I build that is on top of a core system or object is something that was designed to work with any project, while something in a project directory, say systems, was designed to specifically work with the current project.

This convention also plays into the scripts I use for managing updating game projects with changes made to the Genesis boilerplate project, such as bug-fixing. Anything in the core directory is something I assume can be straight-up replaced, while anything outside of core needs to receive more manual attention.

That’s All, Folks

That concludes my high-level tour of how I structure my game projects. Again, I’m not saying the way I do things is the best way to organize all game projects; I just wanted to explain how I structure things and the reasons behind those architectural decisions. If you’re looking for ideas on how you should structure your own game projects, I hope my overview helps inspire you to come up with the solutions that work best for you.

Examining the EMMI Zones of Metroid Dread

I’ve been playing Metroid Dread recently (with one finished playthrough under my belt). It’s the first Metroid game I’ve played (though I’ve played other Metroidvanias), and I absolutely enjoyed it. There are many mechanics that I found fun: the combat is simple, yet engaging; the various abilities you unlock are interesting and lead to great gameplay moments; the level design both helps you avoid getting too lost and still lets you feel like you can explore and discover new areas; and the boss battles are tough, yet fair.

The flagship mechanic, the one that Nintendo advertised heavily, are the EMMI zones. These are designated sections of a map patrolled by a powerful EMMI robot, whose sole purpose is to hunt you down. Your normal weapons can’t hurt the EMMI, so you have to avoid encountering them while moving through their territory. If an EMMI catches you, it kills you unless you succeed on a low-probability counter move. There’s only one way to stop an EMMI for good: finding the energy to power your Omega Cannon, the one weapon you have that can damage them.

EMMI Zones present an action-oriented take on stealth gameplay, and I think they pull the job off well. They’re designed to give you a challenge that you can’t blast your way through, and are crafted such that they don’t put you through too much of their gameplay. In this blog post, I want to examine EMMI zones in two ways: how the game teaches you about the zones, and what mechanics are used to make the zones work.

Note: There will be discussion of some of the abilities you earn throughout the game, though there won’t be any spoilers about the game’s story.

How the Game Teaches You About EMMI Zones

Looking at EMMI zones as a whole, there’s a lot to consider, and it can be a little complex. Metroid Dread spends a good deal of time in the early parts of the game guiding you through the gameplay, using a combination of in-game cutscenes, scripted gameplay, and overt tutorialization. Through these approaches, the game allows you to get used to how EMMI zones work, while still having fun along the way.

The First EMMI Zone

When you first encounter an EMMI, you are shown a cutscene, after which you are given a moment to try and respond to it with what limited abilities you have. The door behind you doesn’t open, and your normal beam weapon and missiles cause no damage to the robot. You are powerless to stop the EMMI from capturing you. At the moment of capture, however, the game pauses, explaining that you have a chance to try and parry the EMMI before it lands a killing blow, and waits to resume until you press the melee counter button. The EMMI freezes in response to this parry, and you are told that you can slide beneath a stunned EMMI. This sequence teaches you what happens when an EMMI catches you, and that you are not powerless to respond to it.

The EMMI recovers from the stun, and you are left to figure out how to run from it. In the next room, the level is designed with a tall wall and platform that you can bounce up and escape. The EMMI is damaged, so even though it tries to follow you, it cannot. The fact that it tries, however, suggests that this is what the other EMMI robots will try to do.

In the very next room is the Omega Cannon, the weapon you need to destroy the EMMI. First, you get a tutorial that teaches you how the Omega Cannon’s charge blast works, by forcing you to use it to open an exit out of the room. Afterwards, you drop back down to face the EMMI, and you have to use the cannon to destroy it and continue on your way.

The Second EMMI Zone

The next EMMI zone is right next to the first one, so you get a chance to try a less hand-held version of EMMI zone gameplay. It starts with a cutscene that reinforces the fact that your normal weapons don’t work against the EMMI (in case you hadn’t realized that, yet). You then have to escape from the EMMI; however, unlike the first one, this EMMI isn’t damaged, so simply climbing up a wall is not enough. You have to evade the EMMI through multiple rooms long until it loses sight of you. This causes it to leave pursuit mode, allowing the EMMI zone doors to unlock and giving you the chance to get out.

From that point, the game leaves you alone, letting you explore more of the world, discovering some new powers and augmentations along the way. The level design forces you to cross back through the EMMI zone multiple times to get to the places you need to go, giving you plenty of experience avoiding the EMMI.

Eventually, you encounter the first Central Unit room, the place where an entity coursing with Omega Energy (the resource that powers your Omega Cannon) is housed. You have to battle this Central Unit, dodging two attacks while blasting the Central Unit with missiles to take down its armored shell and squishy body. Finally, the entity dies, allowing you to siphon its Omega Energy to power the cannon. This time, you are given a second facet to the Omega Cannon: a rapid-fire blast.

As with the first room, the game locks you into the Central Unit’s room and forces you to use your Omega Cannon’s abilities to break out of it. You have to use the rapid-fire to melt the exit door’s heat shield, exposing it to a charge blast which blows it open. This same combination is what you use to defeat the EMMI. This time, when the EMMI dies, you acquire an ability from it, hinting that future EMMI will do the same.

The last major aspect of EMMI zone gameplay comes after you defeat the game’s first boss, where you acquire the Phantom Cloak ability. This turns you invisible for a short period of time. A short briefing explains how the Phantom Cloak can be used to hide yourself from EMMI detection, which is reinforced by a short cutscene when you enter the next EMMI zone, where Samus goes undetected by the next EMMI when she employs the Phantom Cloak.

At this point, you’ve been given the abilities you need to handle any challenge the EMMI zones can throw at you. Along the way, the game gave you a tour of how gameplay within EMMI zones will work, both teaching you through tutorial and allowing you to experiment and try things for yourself.

How do those abilities come together with the rest of the game design to give great gameplay? That’s what I want to examine next.

The Design of EMMI Zone Gameplay

The bulk of gameplay in Metroid Dread consists of exploration, small enemy encounters, and boss fights. Lots of action, lots of things to discover, lots of abilities to unlock progression to other parts of the world. Compared to this run-and-gun style of gameplay, EMMI zones feel tense. You can’t blast your way out of the problem; you have to plan your next moves carefully, with the occasional rapid, improvised escape. This is a good change-up in gameplay, giving you a different style of experience; at the same time, it doesn’t last too long, so you don’t spend too much time under tension, helping you avoid becoming fatigued.

Let’s take a look at the various components which power this experience: level design, evasion, and encounters.

Level Design

There is plenty of great map design in Metroid Dread. Seemingly impossible paths are unlocked by new abilities you discover, clever use of one-way gating keeps you from getting too lost, and many skillful sections conceal power-ups to reward conquering the challenge.

Likewise, the EMMI zones are carefully designed, not just in and of themselves, but also with respect to supplementing the rest of the map design. Each zone is placed on the map such that you are forced to cross through multiple times to accomplish your other goals. At the same time, the EMMI zones are only one part of a larger world, so you aren’t constantly forced to stay long in these high-tension areas. It helps keep the gameplay novel and interesting, without putting too much emphasis on having to play stealthy.

How about the level design within each EMMI zone? By and large, there are two kinds of map design within EMMI zones: large, open spaces where you get plenty of space to move around, and small, narrow corridors that require specific strategies to traverse through safely. While there are rooms within the EMMI zones which are one or the other of those types, often you’ll encounter rooms which mix both types of design together, providing variety in how you plan and execute your evasion. There are also some special areas to help or hinder your approach, including spider magnet walls and ceilings to cling to, pressure plate doors requiring fast maneuvering to blast through, and waterlogged floors that inhibit your movement.

An important aspect of the level design is that it gives you ways to evade the EMMI. When an EMMI has detected your sound, it will pursue your sounds until it finds you or you get far enough away that it stops investigating. The level design supports many strategies for getting out of this pursuit. For instance, you can lead the EMMI off into an isolated part of the map, then double back down a different path to go around it to try and quickly reach your objective. Another way is to take advantage of your mobility to get to areas of the map the EMMI can’t immediately follow you to, forcing it to take another path to get there and increasing the time you have to get out of pursuit range entirely. You just have to look at the map and figure out how best to make your way through safely.

Finally, the visual look of the levels themselves contribute to how the player should feel inside an EMMI zone. While the EMMI is still alive and patrolling, the entire area is grainy and gray, and haunting tones play in the background. Once the EMMI is defeated, the entire area brightens up, the grainy filter is gone, and you hear no more spooky music. Additionally, the world map changes the EMMI zone area to have green backgrounds, indicating you’ve cleared that zone.

Evasion

You can’t hurt the EMMI without the Omega Cannon, and the level design forces you to move in and out of EMMI zones several times before you gain access to the room housing the energy which powers that cannon. That leaves you with two options for dealing with the EMMI while on their turf: running and hiding.

Running is the most obvious solution, and there are various ways the game encourages you to do so and not move too slowly. Your Aeion energy, the ability which powers your Phantom Cloak (and certain other moves) recharges more quickly when you move, so you have to move around at some point to regain your ability to hide. A lot of the map design encourages hiding in a safe spot to plot your next move, then darting over to the next safe spot to repeat the process all over again. The more quickly and carelessly you move, however, the more “noise” Samus makes, increasing the odds that the EMMI will hear you and come to investigate your position. You can risk running for an exit, but if you’re spotted, the EMMI zone exits close until you break the pursuit.

The Phantom Cloak, once you’ve acquired it, allows you to find a spot safe from EMMI wandering and hide from visual detection. While active, it drains your Aeion energy resource, but standing still makes the resource drain significantly less than if you are moving while cloaked. This, along with Aeion energy recharging faster when you move (if you aren’t using an Aeion ability) encourages you to plan your next moves while cloaked, uncloak to execute that plan, then activate the cloak again in your next safe spot to make the next step of your plan. Of course, things don’t always work the way you planned them, and a quick activation of the Phantom Cloak gives you a quick way out of being spotted, hopefully allowing you time to get away from the EMMI threat.

Another aspect of Phantom Cloak is that the game allows it to drain life energy if you run out of Aeion energy while cloaked, and this functions as a risky way to prolong your hidden state. It makes for an interesting player decision: do you continue using the Phantom Cloak and risk losing too much life to deal with enemies beyond the EMMI zone, or do you uncloak and risk the threat of EMMI detection? It presents yet another interesting facet to EMMI zone gameplay.

All the decisions you need to make while in the EMMI zone are aided by the information the game chooses to give you while in the zone. If the EMMI is outside of the screen, but close to you to be within the minimap, a red dot appears on the minimap to indicate its whereabouts. Once an EMMI gets close enough to detect the noises you make, a big yellow circle appears on screen to illuminate the precise area within which it can hear you, and while in this area your sound-making movements are highlighted by smaller yellow circles, letting you know exactly what you’re doing to attract the EMMI’s attention. A blue cone is projected from the EMMI’s face, representing the area in which the EMMI can see you, allowing you to react to its sight and do your best to stay out of it. Finally, if it sees you, the cone turns red during the short moment of time the EMMI spends transitioning to pursuit mode, giving you an opportunity to get a head start on your escape.

Engaging the EMMI

Much of the time you spend in an EMMI zone revolves around avoiding EMMI interaction, because to do so risks death. You can’t run from them forever, though. Let’s take a look at the two ways you can directly interact with the EMMI: attacking it with the Omega Cannon, and attempting to escape its grasp when you are caught.

The Omega Cannon has two forms: one gives you a rapid-fire stream of energy bullets, or the Omega Stream, the other a blast with a charging period, or the Omega Blaster. You need both abilities (after the tutorial zone) to defeat an EMMI: Omega Stream is required to take down the shielding on the EMMI’s head, and Omega Blast is needed to take out the core once the shielding is down.

It takes a lot of rapid-fire shots to take the shielding down, so you’ll need to maneuver through the environment to find long stretches of corridor to give you enough time to land enough shots to take down the shield. Oftentimes, the corridor you’re in isn’t quite long enough to completely destroy the shield, so you have to judge when to disengage and move away to avoid getting caught, and subsequently where you’ll move to next to renew the assault. The shield also regenerates if you haven’t taken it down and you stop damaging it, so it’s not as simple as firing a few shots, moving away, and firing a few more shots; you inherently have to take some risky positioning to make sure you do enough damage in one encounter to not allow the shielding to fully recharge by the next time you position yourself to strike.

Once the shield is taken down, the EMMI is stunned for a brief period of time. If you’re not too close, you can start charging your Omega Blaster during this period. Otherwise, you once again need to move far enough away that you give yourself enough time to charge up the blast and aim for the head. This can lead to some tense, thrilling moments where you hold your ground and manage to get your shot off just before the EMMI catches you.

(Yeah. That was close.)

Something you’ll need to adapt to is the varying speeds EMMI will use when approaching you. Sometimes, they’ll come at you slowly, giving you plenty of time to damage them; other times, they’ll come at you rapidly, and you may find yourself needing to risk your position to get a few more hits in before you move away. The more you engage with an EMMI, the more of a feel you’ll get for the speeds it’ll employ and when it’ll employ them, giving you more information to use in planning your attack.

What if you are caught? When the game launches you into an EMMI capture sequence, you are given two chances to counter the EMMI’s capture strike: once when it grabs at you, and once more when it goes for the killing blow. In both cases, the timing of when the move happens is randomized, so you essentially have to guess when it’s going to come and hope you guessed correctly. You are told that it is almost impossible to counter these blows, which makes it all the more thrilling when you do succeed in landing the parry and are able to escape the EMMI’s clutches. Most of the time, the counter will fail, but by giving you a slim chance of escape, despite failing most of the time you will still feel as though you had some measure of control over your fate.

As someone who specifically dislikes gameplay where failing at stealth immediately results in total failure, I really appreciated this approach to giving you a chance to get out of trouble, even though most of the time I failed to do so.

Even when the EMMI lands the killing blow and you have to reload, the game spawns you just outside of whichever EMMI zone entrance you came through, instead of forcing you to start from wherever you last saved. This allows you to immediately go back in and try again without having to fight your way back; alternatively, if you want to take a break from EMMI zone gameplay, you can wander off and do something else for awhile before diving back in. This is a great approach to minimize player frustration with failure, by making the consequence of it less severe.

There is also something to be said to having an enemy that constant hunts for you, that you can do nothing about but to evade. The feeling you get when your efforts finally lead to you gaining the power to destroy that hunter once and for all is exhilarating, leaving you fired up and raring to take on the next EMMI.

As shown, there are many different mechanics that come together to make up the EMMI zone experience. From the level design, to the various ways you are given to evade the EMMI, to how you engage the EMMI directly, these mechanics come together to provide plenty of experiences for you within an EMMI zone.

Conclusion

Metroid Dread is an awesome game, in my opinion, and a large contributor to that great gameplay are the EMMI zones. You are taught how handle EMMI zones through a combination of guided experiences, cutscenes, and gameplay. Afterwards, the various game mechanics specific to EMMI zones come together to form a tense, challenging experience that straddles the line between being fun and frustrating.

I think the game would be more boring without EMMI zones, and that is a testament to how effectively they are designed. If ever I want to implement action-based stealth mechanics into my own games, I’ll be taking cues from Metroid Dread’s EMMI zones as an example of how to do it right.

Creating a Debugging Interface in Godot (Part 3)

Welcome to Part 3 of my tutorial for creating a debugging interface in Godot! In Part 1, we created the base for our debugging system, and in Part 2 we created debug widgets to show our debugging information. If you haven’t read those parts, you would be advised to do so before continuing on with this part. Alternatively, if you want to start from this part, and just need the end code from the preceding parts, you can check out the tutorial-part-2 branch from the Github repo.

At this point, we have a debugging interface that we can toggle on and off, and we have a base DebugWidget class to build our debug widget from, as well as a DebugTextList debug widget. We don’t quite have everything we’d ideally want in a debugging system, though. What happens if we want to display different debug widgets, and not have to see all of them at the same time? What if we have a lot of debug widgets, so much so that they take up most of the screen space, making it impossible to see the underlying game beneath the cluttered visuals?

We could try creating multiple DebugLayer nodes, but this would quickly become brittle and clunky. As the DebugLayer is exposed globally for our code to access, any additional DebugLayer nodes would also need to be global, which would pollute the AutoLoad declarations. It would also mean having to remember which DebugLayer you’re connecting to, as well as assigning different keys to show and hide each layer so that they don’t all show at the same time… Suffice it to say, doing things this way is awful.

It would be better to create a system specifically for showing different debugging interfaces, depending on whatever criteria we choose to specify. We’ll do this by creating a new type of node, the DebugContainer, and modifying DebugLayer to be capable of managing multiple DebugContainer nodes.

If you want to see the final result, you can check out the tutorial-part-3 branch in the Github repo.

Ready? Let’s go!

Creating the DebugContainer

Begin by creating a new script file, DebugContainer.gd, in the _debug directory. Have it extend MarginContainer. We’ll begin by adding this line of code:


# The list of widget keywords associated with the DebugContainer.
var _widget_keywords = {}

Wait a minute, you say. That looks suspiciously like the code we added to DebugLayer.gd in the previous part of this tutorial. Well, you’re right! That’s exactly what it is. Our goal is to move management of individual DebugWidget nodes out of DebugLayer and into DebugContainer nodes, so it makes sense to go ahead and store the widget keywords here.

Moving Widget Code from DebugLayer to DebugContainer

In fact, we’re going to move most of the code we added to DebugLayer for managing debug widgets into DebugContainer.gd. Let’s take care of that right now:


func _ready():
    mouse_filter = MOUSE_FILTER_IGNORE
    _register_debug_widgets(self)
    Debug.register_debug_container(self)


# Adds a widget keyword to the registry.
func _add_widget_keyword(widget_keyword: String, widget_node: Node) -> void:
  var widget_node_name = widget_node.name if 'name' in widget_node else str(widget_node)

  if not _widget_keywords.has(widget_node_name):
    _widget_keywords[widget_node_name] = {}

  if not _widget_keywords[widget_node_name].has(widget_keyword):
    _widget_keywords[widget_node_name][widget_keyword] = widget_node
  else:
    var widget = _widget_keywords[widget_node_name][widget_keyword]
    var widget_name = widget.name if 'name' in widget else str(widget)
    push_error('DebugContainer._add_widget_keyword(): Widget keyword "' + widget_node_name + '.' + widget_keyword + '" already exists (' + widget_name + ')')
    return


# Go through all children of provided node and register any DebugWidgets found.
func _register_debug_widgets(node) -> void:
  for child in node.get_children():
    if child is DebugWidget:
      register_debug_widget(child)
    elif child.get_child_count() > 0:
      _register_debug_widgets(child)


# Register a single DebugWidget to the DebugContainer.
func register_debug_widget(widgetNode) -> void:
  for widget_keyword in widgetNode.get_widget_keywords():
    _add_widget_keyword(widget_keyword, widgetNode)


# Sends data to the widget with widget_name, triggering the callback for widget_keyword.
func update_widget(widget_path: String, data) -> void:
  var split_widget_path = widget_path.split('.')
  if split_widget_path.size() == 1 or split_widget_path.size() > 2:
    push_error('DebugContainer.update_widget(): widget_path formatted incorrectly. ("' + widget_path + '")')

  var widget_name = split_widget_path[0]
  var widget_keyword = split_widget_path[1]

  if _widget_keywords.has(widget_name) and _widget_keywords[widget_name].has(widget_keyword):
    _widget_keywords[widget_name][widget_keyword].handle_callback(widget_keyword, data)
  else:
    push_error('DebugContainer.update_widget(): Widget name and keyword "' + widget_name + '.' + widget_keyword  + '" not found (' + str(_widget_keywords) + ')')

Almost all of the code above is code we worked on in Part 2 of this tutorial. If you need any refreshers on how that code works, feel free to review that part.

There are a couple of differences to the code that need to be pointed out; both are in the _ready() function. First, the mouse_filter = MOUSE_FILTER_IGNORE line.

By default, mouse_filter is equal to MOUSE_FILTER_PASS. That value means that, when you render a UI node, mouse interactions are captured by the first UI element that decides to handle it. If you have two UI nodes, and you click on that stack, the “top” node will receive the mouse event first. If it doesn’t handle the event, it gets passed to any nodes below it. If it does do something with the event, however, then the event is considered to be handled, and is no longer passed on to other nodes.

With that information, let’s think about how our debugging system is implemented. We made DebugLayer a CanvasLayer node that is rendered at the highest level possible. Because of this, anything in DebugLayer will receive mouse events before anything else in the game. Since control nodes default to using the MOUSE_FILTER_PASS setting, that means DebugLayer will consume any mouse events while it is being shown, preventing interaction with the underlying game. That is behavior we definitely don’t want. That is why we set mouse_filter to MOUSE_FILTER_IGNORE for DebugContainer, so that it will ignore any mouse events, allowing them to proceed down to the underlying game nodes.

The other thing to note about the code we’re adding is the call to Debug.register_debug_container(). This will be how our debug container registers itself with the DebugLayer, much like what we did with debug widgets in the previous part of the tutorial.

If you’re copying code over from your project, don’t forget to update the error messaging and code documentation to say DebugContainer instead of DebugLayer.

Modifying DebugLayer to use DebugContainers

We’re going to need to add register_debug_container() to DebugLayer.gd. Before we do so, however, we need to make some other changes to the DebugLayer scene, itself:

  • Remove the TextList1 node we created in the previous tutorial; we’re no longer going to store debug widgets directly in the DebugLayer scene.
  • Select the DebugUIContainer node, click on the Layout tab, and select “Full Screen”.
  • Add a VBoxContainer child to DebugUIContainer.
  • Add a Tabs node and a MarginContainer node as children of the VBoxContainer (in that order).
  • Name those last two nodes DebugTabs and DebugContentContainer.
  • Go to the DebugTabs node properties and set Tab Alignment to left.

That takes care of the scene. Let’s move on to modifying the script. If you haven’t done so already, remove the code implementing debug widgets in DebugLayer (aka the stuff we moved into DebugContainer). Once that’s done, add the register_debug_container() function and the related code that is part of its implementation:


signal debug_container_registered


# The debug containers registered to the DebugLayer.
var _debug_containers = {}

# The currently active debug container.
var _debugContainer: Node

# Nodes implementing the debug container tab switching interface.
onready var debugTabs = $DebugUIContainer/VBoxContainer/DebugTabs
onready var debugContentContainer = $DebugUIContainer/VBoxContainer/DebugContentContainer


func _input(_event) -> void:
  if Input.is_action_just_pressed('toggle_debug_interface'):
    # ...existing code 
    _debugContainer.show()


func register_debug_container(containerNode) -> void:
  var container_name = containerNode.name
  if _debug_containers.has(container_name):
    push_error('DebugLayer.register_debug_container: Debug already has registered DebugContainer with name "' + container_name + '".')
    return

  # Reparent the container node to the DebugLayer.
  containerNode.get_parent().call_deferred('remove_child', containerNode)
  debugContentContainer.call_deferred('add_child', containerNode)
  debugTabs.add_tab(container_name)

  _debug_containers[container_name] = containerNode
  if _debug_containers.size() == 1:
    _debugContainer = containerNode

  # Hide this container node so we don't show debug info by default.
  containerNode.hide()

  emit_signal('debug_container_registered', containerNode)

That’s quite a chunk of code. Let’s unpack this and see what everything does.

First, we add a signal, debug_container_registered, which we’ll dispatch whenever a debug container is registered. Next, we add _debug_containers, which will be used the same way that we used _debug_widgets, just for debug containers instead of debug widgets. We also add _debugContainer to keep track of the currently shown debug container’s node.

We define references for two of the UI nodes we added to the DebugLayer scene, debugTabs and debugContentContainer. For now, we’ll ignore these in favor of explaining other parts of the added code. Don’t worry, we’ll explain what these nodes are used for as we progress through the tutorial.

Continuing on, we modify our _input() function to show the current debug container node whenever we toggle on the debug interface. And finally, at long last, we have the register_debug_container() function, itself.

In register_debug_container(), we first get the name of the passed-in containerNode and check to see if that name is already registered; if it is, we show an error and return without doing anything else. Next, we need to reparent the containerNode from wherever it currently is in the scene tree to become a child of debugContentContainer. Note the use of call_deferred(), rather than invoking the functions directly; this calls the specified functions during Godot’s idle time, which prevents issues that can occur when running code within nodes that are being reparented.

We’re going to allow DebugContainer nodes to be added pretty much wherever we want when creating our scenes, so we need to move them inside the DebugLayer at runtime to ensure they get displayed as part of the debugging interface. This should make more sense once we get to the part where we start using debug containers.

After the reparenting is finished, we add a new tab to the DebugTabs node, entitled the debug container’s name. Then we add the containerNode to the dictionary of debug containers; if it’s the first debug container we’ve registered, we set it to be the initially-shown debug container. We want to make sure that our debug containers aren’t visible by default (otherwise, we’ll see every debug container all at once), so we call hide() on the containerNode. Finally, we emit the debug_container_registered signal, so anything that wants to listen for that will know when a debug container is registered, and which one it is.

I have not needed to make use of this signal yet in my personal use of the debugging system, but it seems like a potentially useful thing to expose, so it makes sense to go ahead and do so.

Now that we’ve implemented the register_debug_container() function, it’s time to take a closer look at the DebugTabs node and make it work.

DebugTabs

The Tabs node in Godot is a basic tabs implementation. It does no view switching by itself; instead, when we switch between tabs, a signal is fired indicating which tab was switched to, and it’s up to our code to listen for that signal and respond to it. We’re going to use this functionality to change which debug container is the one being shown in DebugLayer.

Godot does provide a TabsContainer node, which would implement both the tabs and view switching. However, since it is a single node, if you ignore mouse events (as we mentioned needing to add for DebugContainer), then you can’t click on the tabs. If you leave the node able to capture mouse events, it will prevent interacting with the game when the debug interface is open. Thus, I’ve opted to just use the Tabs node and implement view switching manually.

The code to implement the view switching is rather simple:


func _ready() -> void:
  # ...existing code
  debugTabs.connect('tab_changed', self, '_on_Tab_changed')


func _on_Tab_changed(tab_index) -> void:
  var tab_name = debugTabs.get_tab_title(tab_index)
  var containerNode = _debug_containers[tab_name]
  _debugContainer.hide()
  _debugContainer = containerNode
  _debugContainer.show()

During _ready(), we connect to the tab_changed signal for debugTabs and provide an _on_Tab_changed() callback. In the callback, we get the name of the tab (based on the tab_index provided as the callback function’s argument), and use that name to find the registered debug container with matching name. We then hide the currently-visible debug container, switch the _debugContainer variable to be the upcoming containerNode, and then make that debug container visible.

Updating Widgets

We’re still missing one important functionality: sending data to update our debug widgets. Since we moved our previous implementation of update_widget() into the DebugContainer node, we’ll need to create a new version of update_widget() that determines which debug container the widget data should be sent to.


# Sends data to the debug container specified in widget_path.
# API: container_name:widget_name.widget_keyword
func update_widget(widget_path: String, data = null) -> void:
  var split_keyword = widget_path.split(':')
  if split_keyword.size() == 1:
    push_error('DebugLayer.update_widget(): No container name was specified. (' + widget_path + ', ' + str(data) + ')')
    return

  var container_name = split_keyword[0]
  if not _debug_containers.has(container_name):
    push_error('DebugLayer.update_widget(): Container with name "' + container_name + '" is not registered.')
    return

  var containerNode = _debug_containers[container_name]
  widget_path = split_keyword[1]
  containerNode.update_widget(widget_path, data)

Notice that the arguments are still the same: we’re passing in a widget_path and data. However, we need a way to indicate which debug container has the debug widget we want to update.

To do this, we’re going to modify the widget_path API slightly. Instead of starting the string with the name of the debug widget, we’ll start with the name of the debug container, and delimit it with a colon, :.

We implement this in code by splitting the widget_path string on said colon and verifying that there was indeed a debug container name passed in. If no container name was provided, then we show an error and return without doing anything further; we do the same if the provided debug container’s name doesn’t exist in our dictionary of registered debug containers. If all is valid, then we get the specified debug container and call its update_widget() function, passing in the other half of our split string (aka the original widget_name.widget_keyword API), as well as data.

At this point, we’re almost ready to run the test scene to try our changes, but there’s something we need to do first: modify our test scene to support the changes we’ve made to our Debug API.

Adding a DebugContainer to the Test Scene

Let’s go straight to our TestScene scene and add one of our new DebugContainer nodes; name it “TestDebugContainer”. As a child of that, add a DebugTextList debug widget with the name “TextList1”. Finally, go to TestScene.gd and change our call to Debug.update_widget() to incorporate our new syntax for specifying the debug container in the widget_path.


func _process(_delta) -> void:
  # ...existing code
  elif test_ct == 900:
    Debug.update_widget('TestDebugContainer:TextList1.remove_label', { 'name': 'counter' })
  elif test_ct < 900:
    Debug.update_widget('TestDebugContainer:TextList1.add_label', { 'name': 'counter', 'value': str(test_ct) })

Now we can run the test scene and see our changes in action! If you press the debug toggle key combination we defined earlier (Shift + `), you should be able to see the same counting text that we saw before. Additionally, you should be able to see the tab we just added, titled "TestDebugContainer".

If that's what you see, good job! If not, review the tutorial (and perhaps the repo code) to try and identify where things went wrong.

Testing with Multiple Debug Containers

That said, these are things we've seen before (aside from the tab). We made these changes to support being able to show different debugging views via multiple debug containers. Let's go ahead and add another one!

Duplicate the TestDebugContainer node (which will create a copy of both that node and the child debug widget; the TestDebugContainer node will be automatically named "TestDebugContainer2"), then go to TestScene.gd and add two new calls to Debug.update_widget() as shown below:


# ...existing code
  elif test_ct == 900:
    Debug.update_widget('TestDebugContainer:TextList1.remove_label', { 'name': 'counter' })
    Debug.update_widget('TestDebugContainer2:TextList1.remove_label', { 'name': 'counter' })
  elif test_ct < 900:
    Debug.update_widget('TestDebugContainer:TextList1.add_label', { 'name': 'counter', 'value': str(test_ct) })
    Debug.update_widget('TestDebugContainer2:TextList1.add_label', { 'name': 'counter', 'value': str(round(test_ct / 10)) })

As you can see, we're simply changing the widget_path to request TestDebugContainer2 instead of TestDebugContainer. To keep the test simple, our second call is showing the same test_ct variable, but divided by ten and rounded to the nearest integer.

That's it! No, seriously! Go ahead and run the scene again, and everything should "just work". You'll see two tabs, one named "TestDebugContainer" and the other named "TestDebugContainer2". Switching between them will alternate between showing the original counter and the rounded version.

But wait, there's more! We can add these debug containers anywhere in our scene structure, and as long as those scenes are part of the currently-running scene tree they'll register themselves to our debugging interface.

To test this, let's create a quick child scene to add to our test scene. Create a new scene called "TestChild" (based on Node), then add a button with text "Test Button" and place it near the top-center of the child scene. Add a DebugContainer with DebugTextList child to TestChild, and make sure you rename them to "TestDebugContainer2" and "TextList1" (to match the widget_path we've defined in the TestScene.gd script). Instance TestChild into TestScene and remove the TestDebugContainer2 node that was in TestScene.

Run the test scene, and you get exactly the same result as before. You can see both the tabs, and switch between them with ease. The only difference is that one debug container originated in TestScene, and the other in TestChild.

If you see the TestDebugContainer2 tab, but not the counter, that means you forgot to make the debug node names and the widget_key string match, so you're not actually sending updates to the correct location.

Fixing One Last Bug

Before we get too hyped with excitement, however, there is a bug that we need to take care of. Run the test scene, open the debugging interface, and hover over the button we added to the TestChild scene. Nothing seems to happen, right? Now close the debugging interface and hover over the button again. This time, it lights up, indicating that it's receiving mouse events. That means something in our debugging interface is intercepting mouse events.

Fortunately, this is a simple fix: we just need to go to the DebugLayer scene and change the mouse_filter properties for DebugUIContainer, VBoxContainer, and DebugContentContainer to MOUSE_FILTER_IGNORE (shown as just Ignore in the editor interface). Do not, however, change the mouse_filter property for DebugTabs, or you may find yourself unable to click on the tabs at all!

Once you've made those changes, run the test scene again. This time, you should be able to trigger the button hover state when the debug interface is open.

Congratulations!

We now have DebugContainer nodes, which we can add wherever we want, and add whatever debug widgets we want to them, using the tabbed interface to switch between whichever debugging views we want to see. And best of all, it's simple to add these debug containers and widget as we need them, whether for temporarily reporting data or for permanent display of debugging information.

With these things, you have the essentials needed to build on this debugging system and make it your own. Create widgets that show you the information that you need to know. As shown in this tutorial, it's easy to make a new debug widget, and just as easy to register it to the debugging system. Using this system has definitely made my game development much easier, and I hope the same will be true for you!

If you want to see the code as it was at the end of this part, check out the tutorial-part-3 branch in the Github repo.

Creating a Debugging Interface in Godot (Part 2)

Welcome to Part 2 of my tutorial for creating a debugging interface in Godot! In Part 1, we created the base for our debugging system. If you haven’t read that part, you should do so now, because the rest of the tutorial series will be building atop it. Alternatively, if you just want the code from the end of Part 1, you can check out the tutorial-part-1 branch in the Github repo.

At this point, we have the base of a debugging system, but that’s all it is: a base. We need to add things to it that will render the debugging information we want to show, as well as an API to DebugLayer that is responsible for communicating this information.

We’ll do this through “debug widgets”. What’s a debug widget? It’s a self-contained node that accepts a set of data, then displays it in a way specific to that individual widget. We’ll make a base DebugWidget node, to provide common functionalities, then make other debug widgets extend that base that implement their custom functionalities on top of the base node.

Alright, enough high-level architecture talk. Let’s dive in and make these changes!

Creating the Base DebugWidget

To get started, we want a place to store our debug widgets. To that end, make a new directory in _debug, called widgets. In this new widgets directory, create a new script called DebugWidget.gd, extending MarginContainer.


# Base class for nodes that are meant to be used with the DebugLayer system.
class_name DebugWidget
extends MarginContainer

Note the custom class_name. This is important, because later on we’ll be using it to check whether a given node is a debug widget.

You may need to reload your Godot project to ensure that the custom class_name gets registered.

Next, we’re going to add something called “widget keywords”:


# Abstract method which must be overridden by the inheriting debug widget.
# Returns the list of widget keywords. Responses to multiple keywords should be provided in _callback.
func get_widget_keywords() -> Array:
  push_error("DebugWidget.get_widget_keywords(): No widget keywords have been defined. Did you override the base DebugWidget.get_widget_keywords() method?")
  return []

This function will be responsible for returning a debug widget’s widget keywords. What are widget keywords, though?

To give a brief explanation, widget keywords are the way we’re going to expose what functionalities this debug widget provides to the debugging system. When we want to send data to a widget, the debugging system will search through a list of stored widget keywords, and if it finds one matching the one we supply in the data-sending function, it will run a callback associated with that widget keyword.

If that doesn’t make much sense right now, don’t worry. As you implement the rest of the flow, it should become clearer what widget keywords do.

One thing to note about the code is that we’re requiring inheriting classes to override the method. This is essentially an implementation of the interface pattern (since GDScript doesn’t provide an official way to do interfaces).

Let’s add a couple more functions to DebugWidget.gd:


# Abstract method which must be overridden by the inheriting debug widget.
# Handles the widget's response when one of its keywords has been invoked.
func _callback(widget_keyword, data) -> void:
  push_error('DebugWidget._callback(): No callback has been defined. (' + widget_keyword + ', ' + data + ')')


# Called by DebugContainer when one of its widget keywords has been invoked.
func handle_callback(widget_keyword: String, data) -> void:
  _callback(widget_keyword, data)

handle_callback() is responsible for calling the _callback() function. Right now, that’s all it does. We’ll eventually also do some pre-callback validation in this function, but we won’t get into that just yet.

_callback() is another method that we explicitly want the inheriting class to extend. Essentially, this is what will be run whenever something uses one of the debug widget’s keywords. Nothing is happening there right now; all the action is going to be in the inheriting debug widgets.

That’s it for the base DebugWidget. Time to extend that base!

Creating the DebugTextList DebugWidget

Remember that DebugLabel that was discussed at the beginning of the article? Having a text label that you can update as needed is a useful thing for a debugging system to have. Why stop with a single label, though? Why not create a debug widget that is a list of labels, which you can update with multiple bits of data?

That’s the debug widget we’re going to create. I call it the DebugTextList.

I prefix debug widget nodes with Debug, to indicate that they are only meant to be used for debugging purposes. It also makes it easy to find them when searching for scenes to instance.

Create a directory in widgets called TextList, then create a DebugTextList scene (not script). If you’ve registered the DebugWidget class, you can extend the scene from that; otherwise, this is the point where you’ll need to reload the project in order to get access to that custom class.

Why create it as a scene, and not as another custom node? Really, it’s simply so that we can create the node tree for our debug widget using the editor’s graphical interface, making it simpler to understand. It’s possible to add the same child nodes through a script, and thereby make it possible to make the DebugTextList a custom node. For this tutorial, however, I’m going to keep using the scene-based way, for simplicity.

Alright, let’s get back on with the tutorial.

Add a VBoxContainer child node to the DebugTextList root node. Afterwards, attach a new script to the DebugTextList scene, naming it DebugTextList.gd, and have it extend DebugWidget. Replace the default script text with the following code:


const WIDGET_KEYWORDS = {
  'ADD_LABEL': 'add_label',
  'REMOVE_LABEL': 'remove_label'
}


onready var listNode = $VBoxContainer

listNode is a reference to the VBoxContainer. We also have defined a const, WIDGET_KEYWORDS, which will define the widget keywords this debug widget supports. Technically, you could just use the keyword’s strings where needed, rather than define a const, but using the const is easier, as you can see below.


# Handles the widget's response when one of its keywords has been invoked.
func _callback(widget_keyword: String, data) -> void:
  match widget_keyword:
    WIDGET_KEYWORDS.ADD_LABEL:
      add_label(data.name, str(data.value))
    WIDGET_KEYWORDS.REMOVE_LABEL:
      remove_label(data.name)
    _:
      push_error('DebugTextList._callback(): widget_keyword not found. (' + widget_keyword + '", "' + name + '", "' + str(WIDGET_KEYWORDS) + '")')


# Returns the list of widget keywords.
func get_widget_keywords() -> Array:
  return [
    WIDGET_KEYWORDS.ADD_LABEL,
    WIDGET_KEYWORDS.REMOVE_LABEL
  ]

Notice that we’re overriding both _callback() and get_widget_keywords(). The latter returns the two widget keywords we defined in the const, while the former performs a match check against the widget_keyword argument to see if it matches one of our two defined keywords, running a corresponding function if so. By using the const to define our widget keywords, we’ve made it easier to ensure that the same values get used in all the places needed in our code.

match is Godot’s version of implementing the switch/case pattern used in other languages (well, it’s slightly different, but most of the time you can treat it as such). You can read more about it here. The underscore in the match declaration represents the default case, or what happens if widget_keyword doesn’t match our widget keywords.

Let’s go ahead and add the two response functions now: add_label() and remove_label(). We’ll also add a helper function that is used by both, _find_child_by_name().


# Returns a child node named child_name, or null if no child by that name is found.
func _find_child_by_name(child_name: String) -> Node:
  for child in listNode.get_children():
    if 'name' in child and child.name == child_name:
      return child

  return null


# Adds a label to the list, or updates label text if label_name matches an existing label's name.
func add_label(label_name: String, text_content: String) -> void:
  var existingLabel = _find_child_by_name(label_name)
  if existingLabel:
    existingLabel.text = text_content
    return

  var labelNode = Label.new()
  labelNode.name = label_name
  labelNode.text = text_content
  listNode.add_child(labelNode)


func remove_label(label_name) -> void:
  var labelNode = _find_child_by_name(label_name)
  if labelNode:
    listNode.remove_child(labelNode)

_find_child_by_name() takes a given child_name, loops through the children of listNode to see if any share that name, and returns that child if so. Otherwise, it returns null.

add_label() uses that function to see if a label with that name already exists. If the label exists, then it is updated with text_content. If it doesn’t exist, then a new label is created, given the name label_name and text text_content, and added as a child of listNode.

remove_label() looks for an existing child label, and removes it if found.

With this code, we now have a brand-new debug widget to use for our debugging purposes. It’s not quite ready for use to use, yet. We’re going to have to make changes to DebugLayer in order to make use of these debug widgets.

Modifying DebugLayer

Back in Part 1 of this tutorial, we made the DebugLayer scene a global AutoLoad, to make it accessible from any part of our code. Now, we need to add an API to allow game code to send information through DebugLayer to the debug widgets it contains.

Let’s start by adding a dictionary for keywords that DebugLayer will be responsible for keeping track of.


# The list of widget keywords associated with the DebugLayer.
var _widget_keywords = {}

Next, we’ll add in the ability to “register” debug widgets to the DebugLayer.


func _ready():
  # ...existing code
  _register_debug_widgets(self)


# Go through all children of provided node and register any DebugWidgets found.
func _register_debug_widgets(node) -> void:
  for child in node.get_children():
    if child is DebugWidget:
      register_debug_widget(child)
    elif child.get_child_count() > 0:
      _register_debug_widgets(child)


# Register a single DebugWidget to the DebugLayer.
func register_debug_widget(widgetNode) -> void:
  for widget_keyword in widgetNode.get_widget_keywords():
    _add_widget_keyword(widget_keyword, widgetNode)

In our _ready() function, we’ll call _register_debug_widgets() on the DebugLayer root node. _register_debug_widgets() loops through the children of the passed-in node (which, during the ready function execution, is DebugLayer). If any children with the DebugWidget class are found, it’ll call register_debug_widget() to register it. Otherwise, if that child has children, then _register_debug_widgets() is called on that child, so that ultimately all the nodes in DebugLayer will be processed to ensure all debug widgets are found.

register_debug_widget(), meanwhile, is responsible for looping through the debug widget’s keywords (acquired from calling get_widget_keywords()) and adding them to the keywords dictionary via _add_widget_keyword(). Note that this function I chose to not mark as “private” (by leaving off the underscore prefix). There may be reason to allow external code to register a debug widget manually. Though I personally haven’t encountered this scenario yet, the possibility seems plausible enough that I decided to not indicate the function as private.

Let’s add the _add_widget_keyword() function now:


# Adds a widget keyword to the registry.
func _add_widget_keyword(widget_keyword: String, widget_node: Node) -> void:
  var widget_node_name = widget_node.name if 'name' in widget_node else str(widget_node)

  if not _widget_keywords.has(widget_node_name):
    _widget_keywords[widget_node_name] = {}

  if not _widget_keywords[widget_node_name].has(widget_keyword):
    _widget_keywords[widget_node_name][widget_keyword] = widget_node
  else:
    var widget = _widget_keywords[widget_node_name][widget_keyword]
    var widget_name = widget.name if 'name' in widget else str(widget)
    push_error('DebugLayer._add_widget_keyword(): Widget keyword "' + widget_node_name + '.' + widget_keyword + '" already exists (' + widget_name + ')')
    return

That looks like a lot of code, but if you examine it closely, you’ll see that most of that code is just validating that the widget data we’re working with was set up correctly. First, we get the name of widget_node (aka the name as entered in the Godot editor). If that node’s name isn’t already a key in our _widget_keywords dictionary, we add it. Next, we check to see if the widget_keyword already exists in the dictionary. If it doesn’t, then we add it, setting the value equal to the widget node. If it does exist, we push an error to Godot’s error console (after some string construction to make a developer-friendly message).

Interacting with Debug Widgets

At this point, we can register debug widgets so that our debugging system is aware of them, but we still don’t have a means of communicating with the debug widgets. Let’s take care of that now.


# Sends data to the widget with widget_name, triggering the callback for widget_keyword.
func update_widget(widget_path: String, data) -> void:
  var split_widget_path = widget_path.split('.')
  if split_widget_path.size() == 1 or split_widget_path.size() > 2:
    push_error('DebugContainer.update_widget(): widget_path formatted incorrectly. ("' + widget_path + '")')

  var widget_name = split_widget_path[0]
  var widget_keyword = split_widget_path[1]

  if _widget_keywords.has(widget_name) and _widget_keywords[widget_name].has(widget_keyword):
    _widget_keywords[widget_name][widget_keyword].handle_callback(widget_keyword, data)
  else:
    push_error('DebugContainer.update_widget(): Widget name and keyword "' + widget_name + '.' + widget_keyword  + '" not found (' + str(_widget_keywords) + ')')

Our API to interact with debug widgets will work like this: we’ll pass in a widget_path string to update_widget(), split with a . delimiter. The first half of the widget_path string is the name of the widget we want to send data to; the second half is the widget keyword we want to invoke (and thereby tell the widget what code to run).

update_widget() performs string magic on our widget_path, makes sure that we sent in a properly-formatted string and that the widget and widget keyword is part of _widget_keywords. If things were sent correctly, the widget node reference we stored during registration is accessed, and the handle_callback() method called, passing in whatever data the widget node expects. If something’s not done correctly, we alert the developer with error messages and return without invoking anything.

That’s all we need to talk to debug widgets. Let’s make a test to verify that everything works!

Currently, our TestScene scene doesn’t have an attached script. Go ahead and attach one now (calling it TestScene.gd) and add the following code to it:


extends Node

var test_ct = -1

func _process(_delta) -> void:
  test_ct += 1
  if test_ct >= 1000:
    test_ct = -1
  elif test_ct >= 900:
    Debug.update_widget('TextList1.remove_label', { 'name': 'counter' })
  else:
    Debug.update_widget('TextList1.add_label', { 'name': 'counter', 'value': str(test_ct % 1000) })

This is just a simple counter functionality, where test_ct is incremented by 1 each process step. Between 0-899, Debug.update_widget() will be called, targeting a debug widget named “TextList1” and the add_widget widget keyword. For the data we’re passing the widget, we send the name of the label we want to update, and the value to update to (which is a string version of test_ct). Once test_ct hits 900, however, we want to remove the label from the debug widget, which we accomplish through another Debug.update_widget() call to TextList1, but this time using the remove_label widget keyword. Finally, once test_ct hits 1000, we reset it to 0 so it can begin counting up anew.

If you run the test scene right now, though, nothing happens. Why? We haven’t added TextList1 yet! To do that, go to the DebugLayer scene, remove the existing test label (that we created during Part 1), and instance a DebugTextList child, naming it TextList1. Now, if you run the test scene and open up the debugging interface (with Shift + `, which we set up in the previous part), you should be able to see our debug widget, faithfully reporting the value of test_ct each process step.

If that’s what you see, congratulations! If not, review the tutorial code samples and try to figure out what might’ve been missed.

One More Thing

There’s an issue that we’re not going to run into as part of this tutorial series, but that I’ve encountered during my own personal use of this debugging system. To save future pain and misery, we’re going to take care of that now.

Currently, our code for debug widgets always assumes that we’re going to pass in some form of data for it to process. But what if we want a debug widget that doesn’t need additional data? As things stand, because debug widgets assume that there will be data, the code will crash if you don’t pass in any data.

To fix that, we’ll need to add a couple of things to the base DebugWidget class:


# Controls if the widget should allow invocation without data.
export(bool) var allow_null_data = false


# Called by DebugContainer when one of its widget keywords has been invoked.
func handle_callback(widget_keyword: String, data = null) -> void:
  if data == null and not allow_null_data:
    push_error('DebugWidget.handle_callback(): data is null. (' + widget_keyword + ')')
    return
  
  _callback(widget_keyword, data)

We’ve added an exported property, allow_null_data, defaulting it to false. If a debug widget implementation wants to allow null data, it needs to set this value to true.

handle_callback() has also been modified. Before it runs _callback(), it first checks to see if data is null (which it will be if the second argument isn’t provided, because we changed the argument to default to null). If data is null, and we didn’t allow that, we push an error and return without running callback(). That prevents the game code crashing because of null data, and it also provides helpful information to the developer. If there is data, or the debug widget explicitly wants to allow null data, then we run _callback(), as normal.

That should take care of the null data issue. At this point, we’re golden!

Congratulations!

Our debugging system now supports adding debug widgets, and through extending the base DebugWidget class we can create whatever data displays we want. DebugTextList was the first one we added, and hopefully it should be easy to see how simple it is to add other debug widgets that show our debugging information in whatever ways we want. If we want to show more than one debug widget, no problem, just instance another debug widget!

Even though all this is pretty good, there are some flaws that might not be immediately apparent. For instance, what happens if we want to implement debug widgets that we don’t want to be shown at the same time, such as information about different entities in our game? Or what if we want to keep track of so much debugging information that we clutter the screen, making it that much harder to process what’s going on?

Wouldn’t it be nice if we could have multiple debug scenes that we could switch between at will when the debug interface is active? Maybe we’d call these scenes “containers”. Or, even better, a DebugContainer.

That’s what we’ll be building in the next part of this tutorial!

If you want to see the complete results from this part of the tutorial, check the tutorial-part-2 branch of the Github repo.