Postmortem: Dice Tower

This July, Rebecca and I took part in Game Maker’s Toolkit Game Jam 2022 (an annual game jam hosted by the creator of YouTube channel Game Maker’s Toolkit, Mark Brown), making a game called Dice Tower in only fifty hours. It was our first game jam in a long time, having spent the last three years focusing (and failing) on making our “first big release”. Those fifty hours proved to be an intense, fulfilling experience, and I want to examine how things went. I’ll start with the goals that Rebecca and I made, continue into what happened during the jam, and conclude with whether I felt our goals were met and what we’ll do in the future.

Our Goals

First and foremost, Rebecca and I wanted to actually release the game! Our last jam effort, Rabbit Trails, never saw the light of day because we didn’t get the build submitted in time, and we were determined to avoid making that mistake again.

I wrote briefly about this experience as part of my retrospective on 2019.

To that end, we explicitly wanted to scope our game small enough that it had a realistic chance of being completed by jam’s end. Further supporting that goal, we also decided that we would avoid trying to come up with something “clever”; we would be fine with coming up with feasible and fun ideas, even if they might be ideas that other people were likely to come up with. Finally, we determined that we would specifically make a 2D side-scrolling platformer, because that was the genre we were most familiar with; we didn’t want to waste time figuring out how to do a genre that we lacked experience in making.

Based on what had happened in previous jams, we had a few other goals we wanted to meet. Given that our previous games felt bland, we wanted to make sure whatever we made felt more polished and juicy, thereby increasing how good it felt to play. Previous jams had left us feeling burnt out and exhausted, and, while that is a traditional hallmark of game jams, we strongly wanted to avoid feeling that way at this jam’s end, so we made plans to limit how many hours we’d work per day so that we’d have time to relax and get our normal amount of sleep.

Separate from the in-jam goals, we had one ulterior objective: we wanted to see if the idea we came up with was good enough to develop further into a small release. The game we’d been working on, code-named “Squirrel Project”, was still very much in the early prototyping stage; we’d built and tried a lot of things, but made little progress in actually putting together a full game experience. Both of us felt concerned with how much time we were taking in making a “small game”, so we were interested in seeing if doing game jams might give us smaller concepts that would be easier to develop and release quickly. This jam would serve as a proof of concept for this theory.

Day One (Friday, 7/15/22)

The day of the jam arrived. Our son was off spending the weekend with relatives, we’d prepared our meal plan for the next couple of days, and I’d taken time off from my day job. We sat around my laptop, awaiting Mark Brown’s announcement of the theme. At noon, the video was released, and we saw the theme emblazoned on the screen:

Roll of the Dice

Initial Planning

I hated the theme at first impression. One of my takeaways from working on Sanity Wars Reimagined (our first-ever full release) was that working with randomness in game design was hard, and now we had a game theme which strongly implied designing a game focusing around randomness. In my mind, it would be harder to design a game around dice that wasn’t random in some way. Concerned now with whether we would have enough time to make a good game, I started brainstorming ideas with Rebecca.

We spent that first hour working out an idea; the first idea we hit upon wound up dominating the rest of our brainstorming session, to the point that we didn’t seriously entertain anything else. The game would be a rogue-lite platformer, where you moved to various stations and rolled dice to determine which ability you got to use for the next section of gameplay. Throughout the level would be enemies that you could defeat to collect more dice, which would then give you a better chance to roll higher at the ability checkpoints, thereby increasing your odds of getting the “good” abilities.

I intentionally wanted to minimize the randomness of our game so that players felt they had some level of control over the outcomes of dice rolls, and I liked the idea of using dice quantity to achieve this outcome. The more dice you add to a roll, the more likely you are to get certain number totals. This is known as a probability distribution.

At the end of that hour, we formally determined that this was the idea we wanted to work with. Rebecca started crunching out pixel art, and I got to work making a prototype for our envisioned mechanics.

That Old, Familiar Foe

The next few hours of my life were spent working on the various elements that would serve as a foundation for our mechanics. I threw together some simple dice code, along with dice containers that could roll all the dice they were given. I also pilfered my player character and enemy AI code from Squirrel Project, to jumpstart development in those areas.

But something happened as I started trying to work out how I was going to make the player’s abilities work: my brain began to freeze up. This was a frighteningly familiar sensation: I’d felt this way near the end of developing the original Sanity Wars (for Ludum Dare 43). Dark thoughts started clouding my mind:

There’s not enough time to make this game.

You’re going to fail to finish it, just like your last game jam.

Is what you’re feeling proof that you’re not really good enough to be a game developer?

Slowly, I forced myself to work through this debilitating state of mind. After several more hours, I came up with a janky prototype for firing projectiles, and a broken prototype for imparting status effects on the player (like a shield). It occurred to me that if I was having this much trouble making something as simple (cough cough) as player abilities, then how was I going to have time to fix my glitchy enemy AI, and develop the core mechanic of rolling dice to gain one of multiple abilities, and make enough content to make this game feel adequate, let alone good. Oh, and then there was still bugfixing, sound and music creation, playtesting…

It finally hit me: This idea wasn’t going to work. There was simply too much complexity that was not yet done, and I was struggling with the foundational aspects that needed to be built just to try out our idea. There was no way I would be able to finish this vision of our game on time.

My mind in shambles and my body exhausted, I shared my feelings and concerns with Rebecca, and she agreed that we needed to pivot to a new idea. We tried to come up with something, but we were both too tired to think clearly. Therefore, we decided to be done for the evening, get some supper, and head to bed.

Note the lack of planned relaxation time.

As I lay in bed, I fretted about whether we could actually come up with a new idea. That first idea was by far the best of what few ideas we’d been able to come up with during our brainstorming session; how were we going to suddenly come up with an idea that was good enough and simpler? With these worries exhausting my mind, I fell asleep.

This is the first thing I made, a simple test with a dice container and N number of dice. It felt oddly satisfying to keep pressing the “Roll Dice” button.

Day Two (Saturday, 7/16/22)

The next morning, at 6am, I woke up, took a shower, and played a video game briefly. This was my normal morning routine during the workday, and I was determined to keep to it. At 7am, I grabbed a cup of coffee and sprawled out on the couch, pad of paper and pen resting upon an adjacent TV tray, prepared to try and come up with a new idea.

Rebecca joined me at 7:30am, after she did her own waking routines.

The New Idea

I wrote down the elements we already had: Rebecca’s character art and tileset from her previous day’s work, some dice and dice containers, and a player entity that was decently functional as a platformer character. If our new idea could incorporate those elements, then at least not all of yesterday’s work would go to waste.

After thinking about it for a long while, I hit upon an idea: what if you rolled dice to determine how much time you had to complete a level? As you moved through each level, you could collect dice and spend them at the end-of-level checkpoints to increase your odds of getting more time to complete the next level. If that were combined with a scoring system involving finding treasure collectibles that were also scattered throughout each level, then there’d be potentially interesting gameplay around gambling how many dice you’d need to collect to guarantee that you had enough time in each level to collect enough treasure to get a high score.

I pitched the idea to Rebecca, and after some discussion about that and a few other ideas, we decided this was the simplest idea, and therefore had the best chance of being completed before the deadline. Fortunately, this idea also did successfully incorporate most of the elements that we’d worked on yesterday, so we didn’t have to waste time recreating assets. On the other hand, this idea needed to work; there was likely not going to be any time to come up with another plan if we spent time on this one and it failed.

Our idea and stakes in mind, Rebecca and I once more commenced our work.

Slogging Through the Day

I created various test scenes in Godot. Slowly, I began to amass the individual systems and entities that I’d eventually put together to form the gameplay. Throughout the day, I felt very sluggish and lethargic mentally; this was likely a side effect of the burnout I’d put myself through yesterday. I kept reminding myself that some amount of progress was better than none at all, but it still didn’t feel great.

By the start of that evening, I had a bunch of systems, but nothing that fully integrated them. Taking a break, I went outside for a walk and some breaths of fresh air. As I trod the trails in our neighborhood, I worked out the game’s next steps. I reasoned that if I continued to put each system and entity together in isolation and test them, I stood a realistic chance of running out of time to put everything together into a cohesive game. Therefore, I decided to forgo this approach, and simply put everything together now, and build the remaining systems as I went along. This flew in the face of how I traditionally prefer to program things, but I resolved to set aside my clean code concerns and focus simply on getting the game working.

This might look like a game level, but it was really just a single test scene that I was throwing my creations into. There was no logic connecting any of these things together to form a gameplay loop.

The Late Evening Dash

Not long after I returned from my walk, it became 7pm. This was the twelve-hour mark, and in our pre-jam plans I’d determined that I wouldn’t work more than twelve hours on Saturday. Yet I still didn’t have much that was actually put together. I was now faced with a conundrum: should I commit to stopping work now, and risk not having enough time tomorrow to finish the minimum viable product?

Ultimately, I decided that I would try to get an MVP done tonight, or at least keep working until I felt ready to stop. Three hours later, while that MVP was still incomplete, I had put together the vast majority of what was needed to make the game playable: a level loading system, the dice-rolling checkpoints, dice collection (though said dice weren’t yet connected to the checkpoints), the level timer and having it set from rolling the checkpoint dice, rudimentary menu UI, and player death. At this point, I felt that continuing to work would just cut into my sleeping time, and I was still determined to get a proper amount of sleep. At the least, those few hours had produced enough progress that I felt confident that I could finish things up tomorrow morning.

I quickly prepared for bed, and after watching some YouTube to wind down I fell asleep.

One of three tutorial levels I’d created that night. This one is to teach the player that they can pick up dice; when they advance to the next checkpoint, they’ll see that newly-collected die in their dice pool, and hopefully make the connection between the two actions without having to explicitly spell it out.

Day Three (Sunday, 7/17/22)

That morning, I woke up at 6am, as usual, but this time I skipped all of my morning routine save the shower. By 6:30am, I was at my computer and raring to go.

Blazing Speed and Fury

The first thing I did was export the game as it currently was. I’d been burned enough times by last-minute exports that I was determined to make sure that didn’t bite me in the butt this time. Fortunately, this time there were no export-specific crashes or bugs, so I resumed work on the game proper.

For the next several hours, my mind singularly focused on getting this game done. I finally made the checkpoints accept the dice you collected, thus completing the core loop of rolling dice to determine the time you had to make it through the level. I added game restart logic, added transition logic for when the player was moving between levels, added endgame conditions and win/loss logic, and fix various bugs encountered along the way. I also realized that I wouldn’t have time to implement a scoring system, so I unceremoniously cut it from the MVP features.

Cutting the scoring system also meant cutting the third tutorial level, which would have taught the player about the treasure collectible. Additionally, it would’ve served as a prelude to the intended tension of collecting treasure to increase your score and collecting enough dice to have more time in the next level.

Around noon, I finally had the game fully working. I could boot the application, start a new game from the main menu, play through all the designated levels, and successfully reach the victory level to win the game (or run out of time and lose). If nothing else, we at least had a playable game!

Final Push

The game jam started at noon on Friday, and as it was now noon on Sunday that meant 48 hours had passed. Thankfully, the jam had a two-hour grace period for uploading and submitting games to Itch.io. That meant I had less than two hours to jam as much content and polish into the game as I could before release!

I blazed my way through creating six levels, spending less than an hour to do so. Of course, that meant I had little time to balance the levels properly, beyond ensuring they could be completed in an average amount of time. One thing I did spend time on was adding the various pieces of decorative art Rebecca made to each level. It might seem frivolous to add decorations, but I think having a nicely-decorated level goes a long way towards breaking up level monotony and sameness, and honestly it didn’t take that long for me to add those things.

After a few trials, I hit upon a solution for balancing the randomness for the dice checkpoints: I’d give the player six free seconds at the start of each level, and a single die at each checkpoint (in addition to the ones collected through gameplay). As I playtested the level, that at least felt long enough that, using probability curves, the average player could have a decent chance of finishing each level.

Intentionally, I chose to not focus at all on adding sound effects and music. My reasoning went thusly: players would probably prefer to have more content to play through than have sound and music with very little content. It made me remorseful, because sound can make an okay game feel great, but there just wasn’t enough time to do a good job of it, so I decided it was better to just cut audio entirely.

Finally, at 1:30pm, I looked at what we had and determined it was good enough. Rebecca had been working on setting up our Itch.io page, and I gave her the final game build export to upload to the store page. We submitted the game just in the nick of time; shortly after our submission was processed, Itch.io crashed under the weight of thousands of last-minute uploads. While this server strain prevented us from adding images for our game page (we got them in later), at least we already had the game uploaded and submitted, so we weren’t in danger of missing the deadline.

Rebecca and I looked at each other. We’d done it! We’d successfully made a game and submitted it! We spent the rest of the afternoon out and about on a date, taking advantage of our last bits of free time before returning to parenthood the next day.

Feedback

Over the next week, our game was played and rated by people. We received multiple comments about how people enjoyed the core idea, which was encouraging.

A couple of our friends from the IGDA Twin Cities community took it upon themselves to speedrun our game. This surprised me, because I thought the inherently random nature of our game would be a discouragement from speedrunning. They told me they enjoyed it a lot, however, and over the course of that week they posted videos of ridiculously speedy runs and provided good feedback.

By happenstance, the Twin Cities Playtest session for July was that same week, so Rebecca and I submitted Dice Tower to be played as part of that stream. Mark LaCroix, Lane Davis, and Patrick Yang all enjoyed the game’s core concept, and gave us tremendous feedback on where they felt improvements could be made, as well as different directions we could go to further expand and elaborate on the core mechanic. It was a great session, and we are greatly indebted to them for their awesome feedback.

After the play-and-review week had passed, Mark Brown made his video announcing the winners of the jam, and we got to see our final results online (we did not make it into the video, which only featured the top one-hundred games).

For a game jam with over six thousand entries, we did surprisingly well, placing in the top 50% in overall score. Our creativity score was in the top third, which pleased me greatly; it felt like further validation that our core concept was good enough to build on.

Reflection

I want to circle back to those goals Rebecca and I set before the jam, and see how we did in meeting them. There were a couple of other takeaways I had as well, which I’ll present after looking at the goals.

Did We Meet Our Goals?

First and foremost, we wanted to release a game, and we did! That was huge for us, given our last effort died in development. In particular, to release this game after the mental fracturing I endured on Friday night, and having to pivot to a new idea, shows that, perhaps, we’re decent game developers, after all.

Did we scope our game properly? At the beginning, no, definitely not. Fortunately, we recognized this (albeit after spending a day on it), and decided to change to a more feasible idea. Even though we had to cut scope from the new idea as well, the core was small enough that we were able to make it in the time allotted. It gives us a new baseline for how much work we can fit in a given amount of time, which should serve us well in future endeavors.

It’s a similar story for our theme interpretation. Our first idea seemed simple, but turned out to be complex under the hood, as it’s a lot of work to not only add lots of different player abilities, but game entities (such as enemies) to use those abilities on. In hindsight, I laugh at how we thought that kind of game was feasible in 48 hours, with not nearly enough foundation in place beforehand. That said, once again, we pivoted from the complex to the simple, and the new theme interpretation was simple enough to be doable.

Ironically, I thought this would be a common enough idea that plenty of other people would do it, but I actually never encountered a mechanic similar to this during my plays of other jam games, and multiple people commented on the novelty of the idea. Perhaps our focus on coming up with a good idea instead of a unique one managed to get us the best of both worlds!

We stuck to our guns and made a 2D side-scrolling platformer, even though at times I felt like that made it more difficult to find a good theme interpretation. Because we knew how to work in that genre, it made our mid-development pivot possible; I don’t think we could have been successful in doing that if we’d tried to make something unfamiliar to us. Additionally, I think using a genre where dice are less commonly used led to coming up with something more unique than we might’ve if we’d done an RPG or top-down game (two genres I thought would be easier fits for the theme).

One area we did miss on was making this a polished release. The missing audio was sorely felt. That said, I really like the art that Rebecca came up with, especially the decorations, and I don’t regret the decision to cut sound in favor of making levels and placing her doodads. Honestly, despite the missing audio, the fact that this game was fun and playable made this jam release feel more polished than our previous jam efforts.

That dice banner and the dice family portraits were particularly fun to place throughout the levels.

A big success we had was in self-care management. Even though we didn’t stick to the maximum hours per day we set before the jam, we took care to make sure we got enough sleep each night. The end result is that this is the first jam we’ve done that both of us didn’t feel utterly spent at the end of it. That allowed us to enjoy a lovely afternoon and evening together as a couple, and we didn’t feel totally shot for days thereafter. I think that also helped us have the energy needed to push us through to the finish line.

One final goal remains to be evaluated: was our game good enough to base a future release upon? The answer is “yes”. Based on the enthusiastic feedback we received, plus our own thoughts about where the concept could go, we’ve decided to put Squirrel Project aside and focus on making a full, but small, release out of Dice Tower. With some more features and polish, and maybe a dollop of background story to tie things together, we think Dice Tower could make a good starting point for our first paid release.

Additionally, we plan to take part in future game jams, and further explore the concept of making quick games to find small ideas with good potential.

Other Takeaways

There were some additional things we learned from the jam. These emerged as byproducts of the things we tried during the jam.

Our initial thoughts were that we’d spend our brainstorming session coming up with multiple ideas, and then make small prototypes for each of them and decide which of them was the one we wanted to make. It didn’t turn out that way; both our first and second attempts settled early on a single idea, to the point where we didn’t really have many other ideas that we seriously considered. I think that perhaps that’s just how Rebecca and I work; it’s easier for us to jump into an idea and try it rather than piece a bunch of ideas together and flesh all of them out on paper. Perhaps in our next jam, then, we’ll deliberately settle on an idea right away and just go for it, making explicit allowance for pivoting if it starts feeling like too big an idea to work with.

Personally, I also learned that I need to let go of trying to test every little thing in isolation. That may be a good approach for building long-lived, stable systems, but for something as volatile as game prototyping it just slows the process down too much, with no game to show for it. By putting things together as early as possible, it makes things feel more real because it is the real game! I’ll keep this in mind for future projects, to just make a go of it right off the bat, and save my efforts to make things nice and tidy for when the ideas are codified and made official.

Conclusion

Ultimately, I’m glad Rebecca and I decided to participate in GMTK Game Jam 2022. It feels exhilarating to not only finish a game in that short amount of time, but to defeat some of my internal demons from previous jams in the process. We feel more confident in our ability to develop and release games, and we’re excited to tackle making the full release of Dice Tower.

It’ll be interesting to compare this postmortem with the postmortem for whatever that game becomes!

In the meantime, though, feel free to try out our jam release of Dice Tower and let us know what you think!

Dice Tower (Game Jam version) released!

Over the last three days, PixelLunatic and I worked on a game for Game Maker’s Tool Kit Game Jam 2022. Our entry is Dice Tower, and you can play it here!

Keep in mind that this is a game jam game, so this is far from perfect (for example, there is no audio), but we think the end result would up being pretty fun to play! We might decide to develop this further in the future, though we haven’t officially committed to doing so yet.

Enjoy!

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

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

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

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 first part of this blog series, we looked at the many abilities and forms of movement the Kongs can do; in the second part, we examined the design of the levels those actions are performed in, and how it compliments them. Now, we’ll look at the final segment of the movement equation: the foes the Kongs must fight throughout the levels. We’ll review the types of enemies that get included in the game, relative to how you are expected to deal with them; next, we’ll examine a few foes that implement some special mechanics on top of their base type; finally, we’ll look at a few levels to see how enemies are placed to take advantage of the Kongs’ movement, their movement, and the level’s design to create compelling, challenging gameplay experiences.

Kantankerous Kharacteristics

When asked about what kinds of enemies are present in Donkey Kong Country 2, one might say “there’s some ground enemies, flying enemies, swimming enemies, some bosses”. That isn’t wrong at all, but I want to approach this from a different perspective; I want to examine enemies by how they are designed to be defeated. This approaches helps us focus on what moves you need to employ to defeat a baddie, which makes it well-suited for this design dive.

Basic Baddies

Many of the baddies are designed to be beaten with one or more of the regular moves in the Kongs’ arsenal. Some specifically protect themselves against particular kinds of moves, forcing you to be more strategic about how you approach them. Let’s break those types down.

Stomp Enemies

As is true in many platforming games, there are many baddies which can be defeated by jumping on their heads and knocking them off the stage; I’ll call this “stomping”. It’s usually pretty obvious which foes can be stomped; they lack any sort of visible protection to their heads which might discourage you from jumping upon them. That doesn’t mean all stompable enemies are the same; there’s plenty of variety to enemy widths, which affects how easy it is to land within their hurtbox. For example, Sneeks and Flitters have quite wide hurtboxes (and they are visually long-looking characters to communicate this), making them relatively easy to land on; other foes, like the Klomp, stand upright and present a smaller hurtbox, and thus are more of a challenge to land on precisely.

I use the term “hurtbox” to mean the collision area where contact results in hurting the entity possessing it.

Stompable enemies are sometimes placed in such a way that you can chain jumping on multiple of them in a row. As discussed before, this is used in some places as the required means of progressing through the level, but it’s also included in less critical areas as a fun thing to do. Jumping as you land on top of an enemy provides a boost to your jump height, which adds to the thrill of the action. Since jumping on enemies is a core part of the game, adding elements like this to increase the fun factor goes a long way to making the game feel good to play.

Spin Enemies

The spin move is also usable as an attack, careening into enemies to knock them away. Unlike falling on a baddie from above, using a spin requires knowing how much time you spend in the spin animation before breaking out into a run, as otherwise you’ll instead run into the enemy (and thus take damage and lose the Kong you control). This makes the spin attack more challenging to pull off, but subsequently it feels a little more satisfying to use than a simple jump attack.

When you successfully spin into a baddie, it actually resets the spin to the initial frame of the spin move; since the camera stops moving forward as part of this, it adds some weight to how the spin attack feels, contributing to the fun of using it. To compliment this, some parts of the game place multiple enemies close enough to each other that you can easily spin through the entire group, which is tremendously entertaining!

This is actually something I don’t think is done as well in Donkey Kong Country: Tropical Freeze. There’s been a lot of times in that game where I’ve tried to pull off a spin attack through a group of enemies that seem close enough to spin fully through, only for my spin to come up short in the middle of the combo through no perceived fault of my own. It makes it such that I trust the use of that attack less than the jump. That’s a shame, since I think DKC games are at their best when the player is able to use both forms of attack equally well.

Many of the ground baddies which can be stomped on can also be spun into; some, however, specifically prescribe whether or not the spin attack should be used. The Spiny is designed to resemble a porcupine, bristling with sharp quills all over its body except for the exposed head area; spinning into this foe from the front is thus strongly encouraged. The Klampon, an alligator-esque foe with big, nasty teeth, communicates the opposite; spinning into those jaws means your death, so you must instead stomp them. This adds a level of depth beyond simply defeating these baddies; you need to understand what they are vulnerable to and specifically use the move which targets that vulnerability.

Protected Enemies

There are a few foes which are explicitly immune to the spin and stomp attacks the Kongs possess, meaning you can only defeat them with a projectile or an animal buddy’s attack. As mentioned in previous parts, that means when you are given access to a projectile or a buddy, you’ll need to keep them around long enough to deal with any protected enemies in your path.

What if you don’t have a projectile or an animal buddy? Then these protected baddies become obstacles which you must avoid. There are plenty of places in the game where this is what you are expected to do. For example, many climbing sections are littered with Zingers, forcing you to climb quickly or make well-timed jumps to avoid their patrols. On the ground, protected foes are often placed in ways that force you to perform a clever move (like a spin jump) to get through (or, alternatively, to access some form of reward that you’d otherwise have to pass on).

Enemies which are protected from spins and stomps are given a visual design that communicates this. The hornet-like Zinger is covered in sharp, triangular stingers, making it quite clear that death awaits those who try to spin or jump into it. Klubba is big and muscular, and the Kongs simply don’t have enough mass to do any damage to him; instead, when you try to jump on or spin into him, you bounce off him, and he turns red, making the next time you touch him cause damage. Kutlass sports a pair of oversized cutlass swords, and carries them in such a way that it makes it clear that he can cut you down from any angle you choose to try and attack. Identifying which enemies are protected from spins and stomps is an important part of gameplay, and forces clever use of your moves to either avoid them or keep projectiles and animal buddies alive long enough to get to those baddies and defeat them.

Komplex Kharacters

We’ve examined the basic archetypes of enemies by how they are intended to be defeated. There are a few special cases which differ enough from the basic types, which we’ll look into now.

Ranged Enemies

Most of the baddies in DKC2 use their brawn to try and hurt you, but a few fellows got the clever idea of shooting projectiles at you, instead. That forces you to keep your eyes peeled for when such baddies appear in the level, and react quickly to the projectiles they shoot at you to avoid them. Sometimes, these ranged foes are positioned such that they shoot projectiles through an area of the level you must pass through to make progress, requiring you to time your moves to the pattern of the projectiles so you pass while the baddie is reloading. Thanks to how responsive your basic movement is, dodging the projectiles, while a challenge, is rarely a frustration.

Even within the ranged archetype, there is variety to be found in what projectiles they shoot. The Kannon baddie (not to be confused with the samely-named Kannon that shoots you into bonus areas) sometimes shoots simple wooden barrels, which you can bounce on top of if you aim your jumps correctly; this is used in some levels as a required means of progression, bouncing across from barrel to barrel over a gap until you get to the other side. Other times, they fire iron cannonballs which hurt you irregardless of how they touch you, and you must simply avoid them. The Krook, meanwhile, fires its metal hook hands when you pass within line of sight; the twist is that those hooks function like boomerangs, returning to the Krook after reaching maximum distance, forcing you to not only keep an eye for the initial shot, but be prepared to avoid the projectile as it traverses its return trajectory. Finally, the Kloak hovers around and is capable of throwing many kinds of projectiles, from barrels and chests to Spinies and Zingers; sometimes, if you wait long enough and dodge the projectiles, a Kloak will throw out some form of goodie, like a banana coin or a chest with a 1-up balloon, so it’s not always in your best interests to kill them right away.

Swimming Enemies

While swimming, the Kongs cannot jump, spin, or throw; this makes all the foes underwater a protected enemy, because touching them means death. Thus, underwater sections tend to evoke avoidance gameplay, where you swim carefully through rooms of patrolling baddies. Not every foe is content with simply patrolling, however; the Lockjaw, an orange piranha, will charge towards you if it catches you within its line of sight, while the Shuri, a spinning starfish, will wait for you to enter an area, then attempt to launch themselves in your general direction. The Puftup, a bloated pufferfish, will sometimes explode when you get within range, launching its sharp spikes in predetermined directions and forcing you to dodge to avoid them.

Both the Shuri and the Puftup also have variants which simply patrol an area, so you have to identify which variant you’re facing when they appear on-screen!

A lot of times, in swimming levels, Enguarde the swordfish is hidden somewhere, and using him makes the underwater sections far easier. Instead of having to avoid every enemy you see, you can instead line up Enguarde’s sword bill to stab them and take them out. As mentioned before, it’s a reward for players who explore levels carefully and check the nooks and crannies for hidden passages.

Bosses

Boss fights are the apex stage of every world, the final challenge that you must overcome to progress to the next world area. All bosses are immune to stomps and spins, being vulnerable only to projectile attacks. Often, the projectiles you need aren’t immediately available to you, requiring you to wait for them to appear. These big baddies aren’t content with sitting around and letting you get those projectiles freely, however; they have many forms of attack in their arsenal to use on you. These attacks are patterned, so you can learn how a boss is going to attack you and develop strategies for dodging them. Even when you acquire the projectile, however, you still have to aim correctly to get the projectile to hit the baddie, or you’ll have wasted your shot and made the boss fight take longer, increasing the odds that you’ll make a mistake and take a hit.

The stages you fight each boss on ask for the use of different abilities as part of the pattern avoidance. Krow, the foe of the first world, flies around the stage and tries to drop eggs on you, some of which can later be picked up and thrown back at him. Kudgel, the gatekeeper of World 3, jumps around the stage and tries to land on top of you; when he hits the ground, anything standing on it is paralyzed, so you not only have to run around to avoid getting landed on, you also have to jump in the air to get off the ground to avoid being unable to respond to his next jump. To fight King Zing, the defender of the fourth world, you transform into Squawks and must not only dodge the king’s flight path, you must hit a specific part of his body (the tail stinger) with your egg projectiles in order to cause damage. Even though the underlying pattern of dodge-the-boss-until-you-can-use-your-projectile is the same for all boss stages, the different takes on that pattern keep the gameplay fresh and interesting, while requiring you to make good use of your movement to win.

This was a notable issue with bosses in the original Donkey Kong Country, where nearly every boss had to be fought in the exact same way, by simply jumping on them.

Special Shenanigans

There’s more to a good foe than simply meandering around a level. A few simple stooges like that are useful for establishing a baseline to gameplay, but often, as game designers, we want to make enemies that take expected archetypes and add an unique twist for the player to deal with.

Donkey Kong Country 2 does this with many of its foes; even though the baddies can be classified into a small set types of how they can be beaten, most still get one or more unique traits that make it more interesting to deal with them beyond a simple spin or stomp. We’ll examine a small subset of DKC2’s roster of baddies, observing the mechanics which make them function and how they engage with the moves given to the player.

Neek

We’ll start with the humble Neek. A small, bandana-bearing vulture, Neek hovers at a predetermined area in the level and waits for you to get near. Once you enter his threat radius, he lets out a screeching cry and dives down a planned flightpath, starting with an arced curve and flattening out into a straight line. You need to quickly predict where that flight path is going, and whether you’re in it. If you are, you’ll need to duck or jump to get out of the way. If you want to remove the Neek, you can jump up and stomp on its head as it flies by.

As far as special mechanics go, this one is relatively simple. Even so, it adds an element of intrigue to how you plan to deal with him. Until you enter the threat radius, he doesn’t move, so you can’t wait him out; on the other hand, since it won’t attack right away, you have time to prepare for your reaction. While all Neeks perform some form of dive and flatten out maneuver, the angles involved can vary with each Neek, so you have to figure out quickly what kind of pattern it’s going to adopt. Thus, even though the dive isn’t that complex a behavior, the Neek’s simple mechanic is still enough to provide interesting, fun gameplay.

Click-Clack

A small, yet oversized, dung beetle, the Click-Clack has a small hitbox to accompany its tiny frame. This makes it so that you have to be more precise with your jumps in order to land on them. Additionally, when you jump on one, it flips onto its back instead of becoming defeated; you’ll also get knocked back a little bit. If you ignore that Click-Clack, it’ll revive and move around faster than before; only a second jump will dispatch it for good. On the other hand, while it’s flipped over, you can pick it up and throw it as though it were a projectile (although throwing it down to the ground will cause it to revive). Finally, if you spin into it, you defeat it instantly.

There’s all sorts of interesting gameplay that arises from the Click-Clack’s mechanics. The knockback force you experience when you jump on a Click-Clack can make the stomp a little risky; that force could push you into another enemy, or into a pit. Spinning into it, where you have room to do so, is certainly the safest way to dispatch them. On the other hand, it can be used as a projectile; if you’re about to face a horde of enemies, particularly ones that are protected from your other attacks, it’s well worth your while to stomp on a Click-Clack for the purpose of carrying it around as ammunition (or a shield, since holding projectiles protect you from damaging attacks to your front). You can’t sit around and wait while it’s on its back, however; you have to decide whether you’re going to kill it or carry it, or it’s just going to get up again, and since it now moves faster it’ll will be more deadly to deal with.

If you look at the Click-Clack in terms of how it is beaten, it’s a spinnable, stompable foe. The small hitbox and unique mechanics around what happens when you stomp on it make it a much more interesting, versatile foe to fight than it might otherwise be.

Klobber

The Klobber is a Kremling that hides in normal-looking barrels. When you get close enough to it, the Klobber will pop up and try to run into you, knocking you around if it succeeds in doing so. The fact that baddies in barrels exist means you have to be cautious whenever you see a barrel; is it only a barrel, or is a Klobber hiding within? Oftentimes, Klobbers are placed around other enemies or hazards, making it easy for you to get shoved into a source of damage.

How do you stop the Klobber? You have to leap into the air and stomp on its head; spin attacks do nothing against it. Once it’s been stomped, it hides inside its barrel for awhile before popping back out to charge after you anew. The only way to prevent it from returning is to pick it up and throw it into something and break, or offscreen into the bottomless void. You don’t need to throw it right away, though; you can carry the Klobber’s barrel around, and it functions exactly like a regular barrel. Like the Click-Clack, that makes the Klobber a source of projectiles, making it worthwhile to deal with one.

There’s more than one kind of Klobber, too. Different-colored Klobbers have different effects when they hit you. The green Klobber just knocks you around; the yellow Klobber steals bananas from you, which disappear if you wait too long to recollect them; the black Klobber takes away life balloons, which float away if you leave them alone. The red Klobber hides in TNT barrels—with the special name Kaboom—and blows up if it runs into you. These increasingly deadly side effects heighten the stakes of engaging with them; be careless, and you could lose valuable life-giving resources (or, in the case of Kaboom, your life).

Kutlass

Finally, we’ll bring up Kutlass. We’ve briefly mentioned it already; it’s the double-cutlass-wielding Kremling. Those oversized swords, as mentioned before, make it clear that jumping on the Kutlass is a suicidal maneuver. What about spinning into it from behind? It’s too smart; as soon as you get close, it’ll turn to face you and charge at you. If you don’t move out of the way, those massive cleavers will come down on your Kong’s head and take them out of the picture. The only option you have, if you don’t have a projectile, is to dodge out of the way. When you do this, the force of Kutlass’ swing gets its cutlasses stuck into the ground for a few moments, exposing an opportunity to strike and defeat it, either by stomp or spin.

This enemy is an unique blend of the protected and basic types, and this makes it interesting to engage with. You need some amount of space where you can get near the Kutlass without getting immediately into its threat radius; otherwise, you have nowhere to dodge when it swings the swords at you. Even if you do have enough room to dodge, you have to react quickly, making the Kutlass a test of how well you’ve mastered using your movement over the course of playing the game.

Level Design Kase Studies

Having baddies, no matter how interesting they may be, is nothing without creating level designs that take advantage of how they behave, just like having good movement is nothing without a level design that makes those moves fun and entertaining. As with many other aspects, Donkey Kong Country 2 does a great job with enemy placements in its levels, starting with simple schemes to ease new players in and ramping up to complex challenges that truly test a player’s mastery of their moves.

There are a total of 40 levels in the game, each with enough depth to go many thousands of words into. For the sake of brevity, however, we’ll restrict our gaze to portions of a few levels. Even these microcosmic glimpses should prove illuminating as to how well DKC2 uses enemies in its level design.

Pirate Panic

We’ll begin at the beginning: the first level of the game, Pirate Panic. As is typical of the first level of many games, Pirate Panic is designed to introduce you to the many mechanics you’ll be using for the rest of the game. Fittingly, the first enemy you come across is a Sneek, who is easy to jump on and moves slowly; he’s just begging for you to jump on him or spin into him.

Having the first enemy you encounter be simple, easy, and uncomplicated gives the player less to worry about. The Sneek isn’t going to suddenly rev up speed and charge into you, or jump super high out of nowhere; he’ll just keep moving your way until you either deal with him with your moves or let him run into you. It’s a great way to introduce the basic moves to you.

You encounter a pair of Sneeks as your next foes; you’ve already learned how to deal with one, so now the game asks you to deal with two of them. Hopefully, knowing what to do, this proves to be an easy task for you.

Afterwards, the next foe you see is the plodding Klomp. This is a new baddie, with a differently-shaped profile (and hurtbox), but the way you deal with him is the same as how you dealt with the Sneek; the jump just needs to be a little bit higher. Alternatively, if you’ve happened to learn how to hold down the Y button to run, you’ll pick up a crate projectile right before you see the Klomp. It’s not far-fetched to think from there that you might be able to throw this at the Klomp, or even hold it to run into him.

This Klomp is also on top of a row of platforming barrels, so you could even choose to run underneath him if you don’t feel like engaging him. It’s also an introduction to enemies on different height levels, something you’ll encounter often throughout the rest of the game.

Once you pass the Klomp, the next baddies you have to contend with are a row of Sneeks, but this time there’s a twist: the foes are advancing beneath a bunch of stacked platform barrels (so named to distinguish them from projectile barrels). If you try to jump on them, you’ll most likely land on top of a barrel instead of the Sneeks, avoiding them entirely. If you just want to progress, that might be good enough; for many players, though, it’s irking to leave any foe undefeated, if it was within their power to handle. That row of barrels serves as a discouragement against jumping, in that case. The way to defeat these Sneeks is spinning into them; hitting the first one will result in demonstrating the fun art of spinning through all the nearby enemies, exposing a new strategy for you to use and cementing it in your brain with a rush of fun gameplay.

By now, you’ve had multiple chances to practice your basic moves. The rest of Pirate Panic continues in much the same way, introducing slightly more challenging scenarios and foes, giving you a great taste of the gameplay to come.

Lava Lagoon

Next, we visit Level 3 of World 2, Lava Lagoon. This is the first time a level gimmick gets introduced: the water is, literally, lava, and you must jump on the back of an all-powerful, almighty seal named Clapper to convince him to turn this lava into water, making it safe for you to traverse. You can’t stay in the water too long, however; after a brief period of time the water will start heating up, until it once again becomes lava.

If Clapper is powerful enough to turn lava into water, why doesn’t he help the Kongs out in more substantial ways? Good question.

The way this level gimmick is introduced is clever. You are presented with a small branching path. At the start of this fork sits Clapper, waiting for you. The path above is patrolled by a Klampon; the path below is lava-logged and, at the moment, impassible. Earlier in the level, you had to contend with a Sneek approaching you from a higher platform, so that jump to the upper platform feels dangerous. Meanwhile, Clapper the seal is barking at you, and, given how often jumping on things solves problems in this game, you’ll probably feel inclined to do the same here, just to see what happens. Thus, you discover Clapper’s magical powers of liquid and temperature transmutation, making the lower path accessible to you. It is far easier to swim through this area, so you’ll likely take it over the upper path.

This fork in the road thusly introduces the level’s gimmick to you in an intuitive way, inviting you to figure it out on your own. Even if you ignore Clapper entirely and brave the upper platform, it’s not a big deal; you’ll soon reach a point where you cannot progress further without using Clapper. Seeing him multiple times increases the odds that you’ll engage with him and figure out his shtick.

One other thing that underwater path teaches you: that you’ll have swimming enemies to contend with underwater, as a lowly Flotsam patrols the floor of the lower path. It’s easy enough to swim above him, but it prepares your expectations for what kind of gameplay is to come.

The rest of Lava Lagoon is an increasing progression of a simple pattern: introduce lava-water sections that you need to cool down with Clapper, then travel through before the water heats back up to the point of damaging temperatures. In each water section, you have a cadre of underwater foes to swim past and dodge; the combinations become trickier and trickier to navigate the deeper you get into the level, ending with a mad dash through a maze of puffing Puftups (the non-exploding kind) to make it to the end of the level before you run out of time. For good measure, you also get tossed a Klobber at the level’s end that you have to avoid or defeat, lest it push you back into the lava. Overall, the level is a good example of taking a level gimmick, teaching you how it works, and ramping up the challenge of that gimmick through good enemy placements.

Bramble Scramble

As you progress further into the worlds and levels of Donkey Kong Country 2, the challenges the game puts before you becoming increasingly more difficult. I think Level 3 of World 4, Bramble Scramble, is a quintessential example of the game giving you a great challenge to put all your skills to the test. This level is oriented both around climbing on ropes and flying with Squawks.

In the other two levels we’ve discussed, the opening enemy was something simple and easy to deal with. Not here; your first foe is a Krook, and you encounter him the moment you move forward in the level, needing to deal with his hook shot right away. Even if you beat him, though, he’s right on the edge of a thicket of brambles, so you have to be careful that your jump doesn’t lead you to fall into them. After a long jump to the next safe wooden platform, you’re faced with a tall, thorny vine, with a Zinger hovering on the other side waiting to sting you upside the face, assuming you manage to fly over the thorns without getting your Kongs’ butt pricked along the way. You get one DK barrel to try and take out the Zinger, and if you succeed your passage over the thorns is a little safer. The shot isn’t easy to make, however, and you only get one chance; the game is testing you to see how skilled you’ve gotten with your aim.

Skip past a climbing section patrolled by Zingers and a Krook, and another jumping section where you need to use running jumps to have any hope of making it between platforms, and you’ll encounter your first Squawks section. First, you ascend upwards, with a few Zingers floating off to the side which you can use as target practice for your egg shot. You’ll need it, too, because the next section is flooded with flying Flitters. Normally, these blue dragonflies are relatively harmless, but in such large swarms, you’ll quickly get overwhelmed if you don’t fire away your egg bullets to take them out. The eggs aren’t fired in a straight line, either, so you have to remember how the egg trajectory is arced and use that to guide your aim. While doing all that, you must also avoid flying into the brambles above and beside you as you move through the area.

Thankfully, while flying with Squawks you can rest on flat stretches of bramble without taking damage. Given how difficult it is to shoot down the Flitter swarm and keep yourself up in the air, this section might be where you learn this ability!

Eventually, you get to the no-Squawks sign and the halfway barrel, where the next climbing section starts. First, you climb upwards into the face of a squadron of waiting Neeks. Almost immediately after you see them, they dive bomb across your path. If you jump, you fall back down to the ground, so you have to shimmy up and down the rope to keep out of their way. At the top of the rope is a junction with a horizontal rope, allowing you to continue onward. Here, however, you encounter a Kannon firing a rapid burst of cannonballs; you cannot pass yet, as the rate of fire is too fast. Instead, you must wait until there is a short pause in fire, then dash across as quickly as you can before the Kannon begins firing anew.

You continue across the path of ropes, and soon you end up on another horizontal rope, square in the path of another Kannon. You don’t see the Kannon on screen, but you can hear him open fire, and soon you’ll see a cannonball flying across the length of rope, straight at you! Luckily, there’s some gaps in the brambles above that give you just enough room to jump up into in order to avoid the shot. You must advance down the rope to the next safe jumping zone, before another cannonball comes screaming across the screen to take you down. Do this a few more times and you’ll finally get close enough to the Kannon to take him out and end the threat for good.

There’s plenty more challenge left in this level, but there’s one more pattern I want to look at. At several junctions throughout this level, you’ll encounter a group of Zingers. Some of them will be red, meaning they are immune to your egg shot, while one or two will be normal yellow Zingers. With all the Zingers alive, the passage through will be near impossible; you need to shoot down the yellow Zingers to clear your path enough to make it through with relative ease. It’s a great way of presenting players with a simple, yet meaningful strategic choice that yields a tangible reward.

Castle Crush

Our final foray will be into the realm of World 6, Level 3, named Castle Crush. This is another gimmick level. This time, the floor is rising up beneath your feet, and you’ll quickly find yourself running out of head room! To stay alive, you need to keep your eye atop the screen to see where in the ceiling the continuation paths are, and position yourself to be under them before the floor smashes you into the ceiling.

I wasn’t intending for most of these examples to be Level 3s, but that’s how it shook out. 😛

As if that weren’t time pressure enough, the foes populating this level will do their best to come at you from nearly out of nowhere and force you to make quick decisions on how you’re going to handle them. The first enemy you encounter is, once again, a Sneek, dropping down on you from above; a quick and easy enemy to defeat that you’ve, by now, beaten dozens and dozens of times. It also introduces you to a pattern that will be very common in this level: small alcoves containing enemies that drop on you from above.

The next baddie you encounter is a Spiny, also falling down from above. You must defeat him with a spin attack, but you have to time it right; spin too early and you’ll finish the attack animation before the Spiny gets close enough to take damage, resulting in the Spiny landing right on top of you. Some time later in the level, you’ll encounter Klampons dropping in on you from above as well. These you cannot deal with by spin attack, so you must allow them to fall and land, then quickly jump in the air to stomp on them before they close in on you in the tight quarters you keep.

Eventually, shortly before the halfway point, you get introduced to a long hallway that contains both a Spiny and a Klampon. The Spiny is first; if you spin into him, you’ll wind up near where the Klampon patrols, so you’ll have to be careful to ensure you stop your spin in time to get airborne and stomp the Klampon. This section is specifically designed to introduce you to this particular pairing: Spiny and Klampon. It keeps it spaced wide enough that you have a good chance of surviving the encounter while teaching you how difficult it is to deal with both these foes in the same area.

Sure enough, later on in the level, you encounter your first section where a Spiny and a Klampon patrol right next to each other. The Spiny leads, with the Klampon right behind. You now have a difficult choice to make: do you try and leap over the Spiny to jump on the Klampon and defeat him first, or do you instead spin into the Spiny and try to jump right before your spin carries you into the Klampon’s gnashing teeth? Both maneuvers are equally challenging, and with the rising floor you are forced to decide quickly, lest you get squished. It’s one of the most challenging parts of the game, and it feels awesome to correctly execute whichever decision you made.

There are plenty more sections in Castle Crush where the Spiny and Klampon patrol is used. There is even one section where you have alternating Spinies and Klampons dropping on you from above, forcing you to time your attacks so you spin into the dropping Spiny, then quickly jump to stomp on the Klampon before the next Spiny falls onto your lap. All this challenge, and this is only one single part of the many myriad challenges Castle Crush throws your way. By the time you finally ascend to the top of the level and stomp that exit target, you’ll feel exhausted, yet exhilarated at having gotten through.

Kong-clusion

Whew! At last, we’ve made it to the end, not just of Part 3, but this entire series!

In this part, we dove headlong into examining the enemy design and how it was created with the aim of players using their fine-tuned moves on them. The ways each enemy can be defeated provides a framework for understanding what an enemy means in the context of the moves the player can do, from the basic stompable, spinnable baddies to the foes which embody special protections and attacks. Even enemies sharing the same defeat type can have differences in gameplay by exhibiting one or two special behaviors on top of that. Finally, each level in the game, of which we examined several, is tailored to take these enemies and place them in locations that make optimal use of what they do, all in the aim of providing a fun, challenging experience for the player.

Over this entire blog series, I’ve shown why Donkey Kong Country 2’s movement is special. The moves themselves are finely tuned and feel great to perform, and there’s a great many ways to modify the basic movement to provide fun gameplay variety. Every level is designed to take full advantage of these great movement mechanics, rewarding both skillful play and observant exploration. In each level lives enemies that are designed to make using your moves to conquer them, whether by defeating them or avoiding them, fun and exciting.

In short, I think Donkey Kong Country 2’s movement feels amazing. It’s why I loved playing the game as a kid, and it’s why I keep coming back to playing it, nearly thirty years after it was released. Nothing else I’ve played comes close to how this game feels.

As a game designer, it is my hope that I’ll one day get to make a game that pays homage to the movement mechanics of this game. After writing all these blog posts, I’ve certainly squirreled up enough techniques and ideas to take inspiration from!

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!

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

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.

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:
  _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 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!

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.

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.