Postmortem: Black Hole Battle

This year, I participated in Global Game Jam. In preparation for it, I decided to make a game in 16 hours, or one week’s worth of my game development time. What I wanted to focus on, in particular, was scoping the game accurately; in other words, I wanted the game’s scope to only encompass what I thought would be feasible within a week.

16 hours is the amount of time I’ve estimated spending doing game development during a typical week.

How did I do? Read on to find out!

The various notes I scribbled roughing out game ideas.

Planning Phase

Day 1 (Saturday)

I spent a couple of hours on Saturday roughing out the game idea. I wanted to make a top-down space shooter, and I wanted the player to fight against a black hole’s pull and escape its gravity well. This theme was meant to be both literal and figurative, with game elements hinting an allegory of a fight against depression. To save myself the trouble of coming up with a good name right away, I used the working title Black Hole Game.

There would be two kinds of movement: rotational, where the player’s ship rotates and moves forward; and slide, which emulates classic top-down space shooter movement, such as Space Invaders or Galaga. There would also be shooting combat, with multiple guns for the player to collect, obstacles to shoot down, and enemies to dogfight and defeat.

A direct influence was a game called Laser Age, but I doubt that one is familiar to most people; I played it a lot when I was a kid.

This was a solo project, so I decided that there would be no custom art made for the game; everything had to be found in pre-existing art packs. I spent some time searching, and eventually purchased a few packs from an artist in a style that I liked (the Void packs from FoozleCC on Itch.io). For sound effects, I wouldn’t do any recording myself; either I’d find the effects online or I’d generate them with BFXR.

I chose to compose and mix the music. I had an idea of mashing the themes from two songs together: Rush’s Cygnus X-1 (Book 1: The Voyage) and Orden Ogan’s Black Hole. Both of these songs are themed around black holes, representing the dual literal/figurative theme I sought to represent. Although it would have been simpler to relegate this to external sourcing (as I did the art), I enjoy making music, so wanted to keep this part for myself.

I’d use Godot 4 to make the game. My long-term project, Dice Tower, is being built with Godot 3.5, so I wanted to get more experience using the latest version of Godot, particularly since that’s what I’d be using for Global Game Jam.

Finally, I set a deadline for the entire endeavour: Saturday, January 20th, 2023 at 5:00pm CST. In the spirit of a game jam, I wanted a clear, strict end time to force me to complete the project.

With my ideas set, I spent Sunday and Monday engaged in other activities. Come Tuesday, I was raring to go.

Part of the game design document I created for the game.

Day 2 (Tuesday)

My work period was in the evening. My only task this day was to create a game design document and a timeline for when I was going to work on certain tasks. I gave myself a one-hour time limit for doing all of it; the idea was that limiting how much time I had to plan would keep me from adding scope creep.

I used a modified version of the Pomodoro technique for consuming that hour of time. I’d do fifteen minutes of work, then take five minutes of break time. For the first block of work time, I spilled out as many ideas about Black Hole Game as I could. After the five-minute break, I spent the next fifteen minutes going through six randomly-drawn cards from my Deck of Lenses, writing down answers to how Black Hole Game might be seen through each lens. The final fifteen minute block was spent furiously typing out the actual game design document, or what was actually going to make it into the game. That brought me to 55 minutes of work; the final five minutes were spent taking my game design document and scheduling out which tasks would be worked on when.

I think timeboxing the game design period proved useful. Only a small amount of the many ideas I’d come up with made it into the design doc, and that was solely because I didn’t have enough time to write them all down. Since there were fewer things, it limited the scope of what Black Hole Game was going to be. All that remained was to see whether or not that small amount of scope was achievable.

Some of the ideas that were left on the planning room floor included the guns and shooting-oriented mechanics; the gameplay would solely focus on movement.

Yes, the background is a noise texture.

Development Phase

Day 3 (Wednesday)

My time period was both morning and evening. Accordingly, I focused on roughing out the core of the game: movement mechanics and endgame triggers. My goal for the end of the day was to have a playable minimum viable product.

After creating the Godot project, I started implementing the player spaceship. I added the rotation-based movement first, spending a bit of time trudging through trigonometry (and the CharacterBody documentation) to figure out a simple implementation. Once this was mostly functioning as desired, I added the alternative sliding movement style, as well as the ability for the game to switch between the two movement styles seamlessly. Finally, I added constant downward velocity, simulating the pull of a black hole.

I added a debug key to let me freely toggle back and forth between the two styles of movement, even though my ultimate goal was to trigger this change through a pickup. I didn’t remember to take it out of the final build, so any player that figures out what the bind is can cheat the game. ;P

Once the player movement was working, I fleshed out my test game world into a fuller experience. I created some simple asteroid platforms for the player to rest their ship upon. I also made a couple of debug endgame zones: a red one for the black hole bottom, signifying defeat; and a green one to indicate where victory would be given to the player. In both cases, I showed a rudimentary test popup to communicate this endgame state. At this stage, the loss and victory zones were not too far away, for ease of testing.

With a game world in place, I worked on creating the Fuel mechanic. This limited how long the player was able to use their forward thrust, and with it the ability to drive against the black hole’s pull, and would create the game loop of moving from resting place to resting place without running out of fuel along the way. The implementation itself was simple: I made a custom resource that tracked how much fuel was in a given fuel tank, how long it took for the fuel to recharge, and signals to indicate when fuel was depleted or replenished. I then gated the player’s forward movement behind whether they had fuel in the fuel tank; if the player ran out of fuel, they couldn’t thrust forward until the tank recharged to full.

The last thing I tried was an experiment to render a black hole through Godot’s shader system. I tried a few shaders I found online, but none of them worked with initial implementation. Eventually, I decided that this wasn’t worth continued pursuit, and I axed it from my todo list.

By the end of the day, I had my MVP working: the player ship moved as designed, they had a fuel resource to manage, and places where they would trigger defeat or victory upon touching. I was feeling good about my chances meeting the planned scope.

The rocket flame is a basic Godot particle effect.

Day 4 (Thursday)

Today, I only had a couple of hours in the morning to do game development work. Knowing this ahead of time, the only work I scheduled for that day was implementing a UI display for the fuel gauge and a Pickup system with three implementations: Fuel Tanks (instant refuel), Tank Expansions (refuel and expand the player’s tank capacity) and Stabilize (temporarily activate the Slide movement style). The day went as planned, and I implemented all of those thing by the end of my game development time. Once again, my confidence in the game scope increased.

The “black hole” is a giant Godot particle effect.

Day 5 (Friday)

Once again, I only had a morning’s couple hours to work. Originally, my plan was to implement sound effects, but I changed my schedule to work on music, instead; I figured the timeboxing would be more useful for roughing out a composition than figuring out sound effects and how to implement them.

This time, I encountered difficulty. I had specific ideas for how I wanted to make the music, and I spent an hour messing around with various virtual instruments to try and get better sounds. Ultimately, most of that time was wasted, as I reverted to using the sounds I’d had in the first place.

Because of that wasted time, I rushed my way through a composition, and at the end of my gamedev work period I still didn’t have a fully composed piece of music, let alone the victory and defeat variants and the actual in-game implementation.

At this point, I became worried about whether I could still meet my planned scope. Were it not for my strict limit on when I would be allowed to work, I probably would have forced myself to finish the music on Friday night, against my work-life balance needs; instead, I forced myself to stick to the plan, and resolved to finish things as soon as I could on Saturday.

I kept the UI minimal, but took the trouble to find custom fonts on Google Font; not using the engine default font looks more polished.

The Final Push

Day 6 (Saturday)

This was the final day for developing Black Hole Battle (the final title of Black Hole Game). I had ten hours, from 7am to 5pm, to finish development. By my self-imposed standards, this included publishing the game and making it available for people to play.

First, I had to catch myself up from where I’d gone off-plan. I spent about an hour finishing the music composition; ultimately, I was very pleased with it, and it decently accomplished my composition goal of merging Rush and Orden Ogan. I also threw together some short themes to play during defeat and victory.

With the music composed, I started work on implementing the music into the game. I thought I’d save some time by stealing some code from Dice Tower’s sound management system and converting it to work in Godot 4; the reality was that this system was built on top of a number of internal systems which I also had to port over to make the entire system work. Altogether, that was another two hours spent. Once the foundational systems were in place, it didn’t take too long to wire up the logic for when each piece of music should play.

Next, I jumped into creating and integrating sound effects. With time being compressed, I opted for using BFXR to create almost all of the needed sound effects (the lone exception being an ambient background noise, which I wound up stealing from Dice Tower). I worked my way down the list of planned effects, crossing out any which I felt I could do without. By early afternoon, all the necessary sound effects were created and implemented.

At this point, I needed to add the bare minimum requirements for UI, menus and game restart logic. I spent 20 minutes finding and adding two fonts: one for stylistic display, like headings, and one for button and paragraph text. Next, I created a Theme resource and added just enough customization to reduce the cost of duplication (like consistently styled buttons). With that theme, I created and styled my main menu, pause menu, and endgame menu popups. Once the menus were made, I added logic for when they would appear. Finally, I created proper game start and restart logic and integrated that with my menus. Once these things were finished, I had a fully functional and minimally polished game. To confirm I had a functional build, I did a test export of the game and proved that it still worked.

Fortunately, I only had one screen resolution to worry about; my experience supporting multiple resolutions in Dice Tower cautioned me against making the effort to do more than that.

By this time, I had about an hour left to add whatever content and polish I could muster. I threw together some simple game objects (based around the asteroids and planet from the purchased art packs) and tossed them into an expanded game world, along with generous placement of player pickups. I also adjusted the player movement mechanics slightly, to make them feel more responsive. Finally, I hid the debug graphics for the endgame zones, and, for the black hole bottom, I added a particle effect to indicate some kind of churn and swirl; hardly a realistic representation of what a black hole would actually look like, but it felt cool and only took a minute to spin up.

With minutes to spare, I created the final export and uploaded Black Hole Battle to a hosting service. The project was finished, and precisely at 5pm! I shared the project with a few friends, then went upstairs to have supper with my family.

I created a single asteroid scene, then created other scenes that let me reuse that one asteroid scene in groups. For these big clusters, I added a script that let the game randomly pick an amount of asteroids to show, adding easy variety.

Takeaways

The primary goal for Black Hole Battle was to practice scoping for a specific amount of time. Given this, I was successful: I accomplished all of the features I’d set out in the game design document.

Did I complete every single task? No, but that was never the goal; it’s impossible to complete a project exactly as drawn up, and there must be room alotted for adjustments. What I was expecting of myself was to implement all the planned game features and to release them in a polished state; in this effort, I succeeded.

Was the game itself perfect? No; the audio balance between SFX and Music was off, I didn’t really nail the planned thematic duality of black holes and crippling depression, and the small amount of gameplay means it doesn’t take long to fully explore what the game offers. My focus wasn’t on making Black Hole Game the best game it could be, but on making it good enough to be releasable. The game isn’t perfect, but it’s “good enough” to feel like a complete game.

At no point did I force myself to work longer than the hours I’d planned. I resisted the urge to crunch when I felt like I was falling behind, and I still found a way to deliver a completed project. This was a rare success, as previous projects have either ran horrifically over scope or had significant cuts to features and quality to release them on time. Hopefully, I can use this as a standard to plan other projects by.

Conclusion

I wanted to prepare for Global Game Jam by making a one-week, precisely-scoped project. With Black Hole Battle, I successfully achieved this goal, and it left me feeling confident going into Global Game Jam.

How did the jam itself turn out? You’ll find out soon, when the IGDA Twin Cities Global Game Jam postmortem meeting is uploaded to YouTube! That said, I consider the work I did on Black Hole Battle an important factor in how my time at Global Game Jam went.

Here is the final result for Black Hole Battle, for those who wish to try it out. I have no plans to make further changes for it, but feel free to leave feedback so I can apply it to future projects.

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!

React Hangman Koji Template Released

If you follow me on Instagram, you probably know that I have been working on a game template for Koji for the past few months. It started out as a portfolio project for me when I was looking for a job, and even after I got the job I decided I wanted to finish it and release it as a template for other Koji users to build their own apps on top of.

Well, today is the day of the first public release! Go play it here: https://withkoji.com/~Jantho1990/react-hangman

It’s intended to be a template, but the base app is fully playable in its own right!

I invite you to play the game and leave a comment on how you felt about it. Any feedback is helpful!

Post-Mortem Ludum Dare 43

Outside my home office window, fluffy flakes of snow drifted down from the night sky, coating the ground in white, hiding the dreary brownish grass. It was a picturesque scene, and I allowed myself a brief moment to enjoy it; but then my focus turned back to the task before me: crafting the finishing touches of a sample game UI. A last practice project, in preparation for the challenging journey I was about to undertake.

In less than an hour, Ludum Dare 43 — a video game competition wherein participants design, develop, and deploy a video game in 72 hours — would begin, and I wanted to be sure my mind was primed and ready to roll the moment the competition began.

Before you continue further, dear reader, let me forewarn you: this is no mere post, simply detailing the development of a video game. This is a tale of hopes and dreams, of fear and despair; a tale of lessons learned, best laid plans, and desperate decisions; perhaps most of all, it is a tale of one man’s journey to stare dread fate in the eye and dare to succeed.

This, then, is the tale of my experience creating Sanity Wars for Ludum Dare 43, in all its horror and glory. Sit down, buckle up, and hold on.

The Beginning

I’d reserved today — November 30th, a Friday — and the following Monday and Tuesday off on PTO, in preparation for this weekend competition. I’d spent nearly a year teaching myself how to create video games, and now was when I felt my skills were sufficiently advanced enough to tackle a real challenge: Ludum Dare, a legendary video game competition where participants are given 72 hours to create a brand-new video game.

Though unconventional, I planned to enter the contest with a custom JavaScript-based engine that I coded myself, from its humble beginnings as a tutorial engine to its current state, with my unique experiments and needs added to it. It was general enough that I felt comfortable putting it to the test in the fires of competition.

This was an important moment for me. One of my dreams has been to create and release a professional video game, and to me this contest felt like the perfect opportunity to test not only my skill, but also my resolve. How would it feel to channel my creative and intellectual effort into making a video game? Would I enjoy the process enough to commit to wanting to make a full-fledged game later on down the road? Could I make a game that people would enjoy playing? Over the next three days, I reckoned, I’d find out the answers to these questions.

Let the Games Begin! (Friday Night)

At 8 p.m. CDT, the theme for Ludum Dare 43, voted on by its participants, was revealed: “Sacrifices Must Be Made”. I was pleased; this was one of the options I’d been voting for. As a creator, I’ve always been partial to the thematic and narrative drama sacrifice offers, and I knew I’d be able to come up with something that would fit this theme.

Pacing back and forth in my home office to get my creative juices flowing, I hashed through various different possibilities. Eventually, I settled on a general idea for the signature game mechanic: a side-scrolling platformer where the player used a resource called “sanity” to cast spells — but their sanity was also their health bar, and casting too many spells would deplete their sanity below zero, causing the player to die.

Additionally, players would not be able to access these spells at the start of the game; they’d instead start with something called a “sanity buff” which increased sanity recharge rate. If the player chose to sacrifice these buffs, they’d gain access to spells, but also decrease the rate at which their sanity recharged.

The narrative theme I created to support this mechanic was that the player would be fighting a malevolent being named the Dread Overlord, who held the entire world under the sway of terror, and that the player’s narrative goal would be to weaken the Dread Overlord’s iron grip, bringing salvation to the world. In this, the game also served as a loose allegory to the struggle with bipolar disorder, a struggle I am intimately familiar with, and that further endeared me to the idea. It was, I felt, a perfect fit with the sacrificial nature of the theme, as well as the sanity-casting and buff-sacrificing mechanics.

Little did I know of the irony this narrative theme would present later on in the competition.

With my initial planning made, I sat at my desk and immediately began prototyping my sanity-casting mechanic. It so happened that I already had a spell-casting mechanic built from a previous experiment. Using this as a base, I managed to hammer out a working prototype of the mechanic by early morning. The player’s hit points, or HP, would be directly tied to their mana — or, as I was calling it, “sanity”. Spells could not be accessed until their hotkey was held down for a long-enough period of time — aka a “sacrificed” sanity buff. Reducing sanity to below 0 would trigger the player death state, though I had yet to code what that actually looked like.

I also came up with a tentative title for the game: “Sanity Wars”. It wasn’t a perfect title, but I’d spent half an hour brainstorming possible titles, and this was the one that felt the least cheesy. I figured I’d revisit it later and come up with something better, near the end of the competition.

Feeling satisfied with the current state of things, I decided to call it a night and get some sleep. My confidence at this point felt solid; I believed that I had enough time to take this vision I’d come up with and hammer it out. Soon, I was fast asleep.

Time Management Issues (Saturday)

Around 8-9 a.m. CST, I woke up to a breakfast prepared by my wonderful, amazing wife, and I enjoyed the family meal before returning to the office to begin the day’s work on my game. The first thing I did was to finish implementing player death, which didn’t take much time. I just tied the player HP stat to mirror the sanity level.

Now that I felt I had the “core” of the game implemented, I thought about what to tackle next. I’d noticed the tileset I’d been working with included sloped tiles, but my engine didn’t have support for moving on a sloped tile. So…why not knock that out real quick?

Three hours later, I had a very buggy implementation of slopes; try as I might, I simply could not get the physics of walking on the slope to work correctly. At that point, I finally asked myself the question that I should have asked myself before I started working on slopes: Is this a feature I really need in my game?

The answer was glaringly obvious: No, it did not. I had been developing just fine with block tiles and jumping around, and while I loved the idea of including slopes, they were far from essential for my game to work. Thus…I nixed the idea and moved on. Three hours, down the drain, over a needless feature…

Lesson Learned: Question whether the feature you’re planning to add is necessary before writing code for it.

I decided my next goal should be to implement an enemy. Hitherto, all I had was a player and the sanity mechanic for casting spells. There needed to be at least one enemy, ideally more, for the player to contend with.

I decided I wanted to create a flying enemy, to avoid having to deal with determining where a ground character could move. After perusing OpenGameArt.com for a considerable period of time, I found a floating eyeball animation that looked delightfully menacing. With that as my art base, I created a floating eyeball entity, which would fly at the player, then back away to a random distance once they got too close, then either wander around for a bit or immediately descend upon the player again. When they were close enough to the player, they would trigger a sanity drain, thus “attacking” the player. It took another few hours, but it was complete and I had it integrated into Tiled (the tilemap editor I was using), so I could place floating eyeballs anywhere I wanted.

By then, the hour was getting late. I took a short break, to ponder my next moves. That was when it finally dawned on me: I had a mechanic, I had a player, I had an enemy…but I had no core game loop. In other words, I had no goal for the player to attain, no objectives to attain along the way. All I had was a map with a sanity caster and a few floating eyeballs. That wasn’t a game, that was a setup.

What was the player’s goal? In order for my game to truly be a game, I needed to answer that question, fast, and then implement the bare minimum necessary to make that goal playable.

Lesson Learned: Hash out the core loop for the game right away. Maybe it will change later, but at least have an initial iteration.

After some brainstorming — I can’t recall exactly how long — I settled on the idea that the player needed to survive and find a Final Exit, but this wouldn’t be considered a true victory unless the player first found some Objective; both the Final Exit and the Objective would be randomly spawned in one of a series of maps. Each map would be connected linearly by portals. I felt these things should all be doable within a day or less, and now that it was past 1 a.m. of the next morning I decided to go to bed now. Hopefully, this would give me the energy needed to bang things out quickly.

Making the World (Sunday)

The next morning, I woke up, scarfed down a quick breakfast, then headed back into my home office to start implementing the Map Portals mechanic. Up until now, I had been working with isolated test maps. In theory, I should be able to write code which would create two portals on a map, each linking to a different map, as determined by the load order in my map configuration file.

I spontaneously decided that I wanted the player to be spawned randomly as well, and that furthermore I wanted the player to spawn only in a map corner. Thinking that this shouldn’t be too hard, I got to work designing and implementing the code that would let me do this.

It took more work than I thought it would. I not only needed to restrict where on the start map a player could spawn, I also needed to check and make sure the spawn location was actually habitable by the player (in other words, not inside a solid tile, like a wall) and directly above a ground area (so the player wouldn’t spawn at the top of a tall map in mid-air). After several hours, I had the mechanic working, and proceeded to work on the Map Portals.

A lot of the spawning logic I used for the player also applied to spawning Map Portals, so I made use of copy-pasta and removed the map corner restriction. I then needed to add logic to prevent map portals from spawning on top of each other (or the player), as well as logic to prevent portals from spawning too close to each other.

Another several hours later, I had the portal spawns complete. Now, for the challenging part: enabling the player to press a key in front of one of these portals, and be teleported to the correct corresponding portal on the linked map.

It turned out to be even more complicated than I expected. First, I had to set up a system by which two portal objects could be “linked” together so that they’d always send the player to the correct location. Then I had to load the next map’s data into the game while swapping out the old map data (but not deleting it, because I’d need to restore it when the player returned to that map). The player also needed to be teleported to the receiving portal’s coordinates, which involved resetting the camera to focus immediately on the receiving portal’s location and preventing the player from accidentally teleporting back to their original location by holding the action key a fraction of a second too long. There also needed to be logic to determine where the Final Exit would be spawned, and only render the object when the player was on the chosen map. Finally, any additional entities (enemies, the Objective) on one map needed to be removed from the game loop, then put back in when the player returned to that map.

In short, I needed to make maps containing bad guys, portals, tomes, and the exit.

I spent the entire rest of the day just implementing all this logic. Along the way, I decided, instead of a single Objective, to create multiple Objective pickups (which I ultimately decided to call Tomes, giving them a bookish sprite), spawning one on each map, and that the player had to collect all of them to get the “best” ending; anything less would result in achieving a subpar ending. I also wound up having to write a custom events handler to run dispatches at the end of the current rendering cycle as part of making these portals work, as well as a WorldMap handler specifically to handle the metadata outside of each individual map.

Working feverishly, hour after hour, I finally had everything functioning as intended, except for enemy persistence. By now, it was nearly two in the morning. I had intended on composing an original song for this game, but some part of me warned myself that I might not have time for that on the final day, so I listened to a few of my older compositions and picked the best-fitting one as my backup soundtrack.

Lesson Learned: Things will take longer than expected, so account for that when planning.

Utterly exhausted, I collapsed into bed and tried to fall asleep. I tossed and turned, and I growled mentally at my body for being so stubborn…but sleep continued to evade me. At that point, thoughts flooded into my mind about how every minute I spent awake in bed would lead to one less minute of sleep I’d get, because I absolutely could not afford to sleep in late this time, like I did the other days.

After hours of this dreadful torture, I did finally fall asleep, but not for very long.

Dread Realizations (Monday)

At 8:00 a.m., the alarm blared and yanked me out of my fitful slumber. Compared to the other days, my mind was besot with grog and lack of clarity from the get-go. I plodded to the fridge, grabbed an energy drink, and started downing it. I had no time to think, no time to reflect; I had to finish enemy persistence, and whatever other little things I’d forgotten, to at least get the game to a playable state.

I thrust myself into the chair before my laptop and got to work. In another couple of hours, I got the enemies to correctly persist across maps. I also implemented a maximum enemy amount per map, and a rate which enemies would spawn/respawn into the map. I also threw in some simple ending screens to test the end-game conditions.

It was past noon by the time I got all this working. Meanwhile, my wife was playtesting builds of the game on another machine, and I would need to run back out there multiple times and pull down the latest builds for her to test with.

At this point, with the core loop finally in place, I decided to start looking around for final art assets. The ones I’d been testing with weren’t bad, but they didn’t quite fit together, and it bugged me enough that I chose to divert time into searching for new assets. Hours passed, and before I knew it the time was 4:00 p.m., and all I had to show for it was a single tileset and new sprites for the character.

The deadline was at 8:00 p.m. In short, I now had four hours to create the actual levels, create music, create sound effects, add story text and cinematics, upload the game to a server, test everything…that’s when it finally hit me: I’m not going to be able to release my full envisioning of the game.

It was at this point that my bipolar symptoms, hitherto under control, started to flare up strongly. Dark thoughts filled my mind about how badly I’d executed this game, how unlikely it was that I would even be able to get it finished. Every tiny little mistake I’d made, from the botched slopes to the overly-long search for a new tileset, flooded into my mind.

“You’re not good enough for this,” my brain whispered to me. “You did your best, but it wasn’t good enough and you’re going to fail.”

I kept trying to push forward with making levels. Nearly an hour later, I only had a single map that looked somewhat decent, and my mental anguish only intensified with each passing minute. There were now entire minutes where I’d find myself paralyzed with derision, my mind crushed under the anguishing weight of despair, my body unwilling to even move a single muscle. It had been a long, long time since I’d felt such intense levels of depression, and this scared me most of all. If this is what it’s going to be like every time, how can I justify making games in the future?

Sanity Wars, indeed. The imagined narrative of my game — of which I had yet to even write a single line of text — proved utterly ironic as the villain of my game threatened to consume the game’s creator.

Somehow, despite mostly working in fits and spurts, I kept forcing myself onward, even as my depression screamed at me about the futility of such gestures. My wife helped out by creating a map of her own, and I threw together a few additional barebones maps with a few randomly-drawn platforms. I found a few open-source sound effects to combine with my sfxr-generated effects I’d already put into the game, and I also took the backup music I’d selected the night before and added it as the game’s soundtrack.

Somehow, with less than two hours until the deadline, I wound up with a playable game. It was nowhere near the game I’d envisioned it to be when I started the competition; compared to that, it was abhorrent, abominable, awful garbage. Yet…it was still a game, and it was playable.

The Dread Overlord was still doing his best to crush my mentality and inconvenience my efforts, but, nevertheless, I slogged on. At this point, I had steeled my resolve. It was going to be a crap game, it wasn’t going to capture the theme nearly as well as I’d planned, and it was surely not going to do well…but I was going to finish and release it, flaws be damned!

The Final Push (Monday Night)

Now that I had something to release, it was time to ensure that the game did get released. Although it felt a little early–I still had an hour and a half of time–I decided to go ahead and set up the release platform for my game, so I wouldn’t be scrambling to do it at the literal final hour. As this was a web-based game, my plan was to host this as an AWS S3 website, where I wouldn’t have to deal with setting up and hosting a server, or learning some other company’s setup for their own hosting service. My decision to tackle this now would prove fortunate.

I mentioned previously that I’d built my game-engine based on a tutorial book. Said tutorial book gave instructions on how to set up a dev server called Budo for use with the testing environment…but, it turned out, had not provided any instruction on how to actually deploy the finished app to production. Scrambling, I rapid-researched how Budo ran under the hood, discovered it was using Browserify, and figured out how to set up Browserify to deploy a production build of my game script. After a few other snafus, I managed to correctly set up a deployment script.

Now it was time to create the S3 bucket-site and copy my production assets into it. In my depression-paralyzed state, I screwed up my first deployment attempt and spent almost half an hour trying to fix my AWS permissions before finally opting to blast the first site and make a new one. The second time through, I set the configuration correctly, and the deployment worked as intended. With less than an hour to go before the deadline, I at least knew I could reliably deploy the game.

I used every minute of that last hour to add as much polish as I dared get away with. By now, the adrenaline of deadline crunch was overcoming the dread weight of my depression, so I was singularly focused on trying to make my game at least not absolutely terrible. I converted my test ending screens into actual ending screens, so that I could at least get some of the story’s context into the game. My wife helped by writing the actual dialogue for the ending scenes, while I crafted the introductory scene establishing the game’s tone. We got the final words in, and deployed, at literally the last possible second, at 8:00 p.m.

At the end of the deadline, Ludum Dare allowed for a single hour to get the game deployed, and to fix any last-minute bugs that come up during this process. I honestly don’t recall much of what I did at this time, other than fixing a few things. At the end of it, I belatedly marked the game as “Unfinished” as I crafted the submission post. It only seemed fair, with the game in a barely-publishable state, to mark it as such. When I made my submission post, however, another fellow Ludum Dare-r messaged me, advising me to mark the game as “Jam” instead of “Unfinished”, pointing out that hardly anyone was going to play a marked-unfinished game, and I thus wouldn’t get any feedback on where I could improve. Deciding that I could at least treat this as an improvement opportunity, I took the person’s advice and changed my submission type to “Jam”.

Shortly after I did this, 9:00 p.m. hit. The competition was truly over, and now no changes were allowed. I collapsed onto the living room couch, utterly exhausted and in a foul state of mind. My wife tried to cheer me up, to focus on the fact that I had actually finished and released the game. I wasn’t quite ready to rejoice over that fact, especially since I found myself dreading the horrible reviews which would surely come.

But she was right: I had finished the game. It was a poor excuse of a game, in my mind, but I had actually done it. I’d built a game in three days, and on my first-ever attempt at such a feat. It wasn’t at the standard of quality that I hold myself to, but it was something.

Ultimately, I opted to push all thoughts of self-review and criticism out of my mind until I had gotten a good night’s rest. Fumbling my way through my regular nightly routines, I fell asleep mere minutes after tumbling onto the bed. It was a deep, deep slumber.

The Judgment

I didn’t get out of bed until nearly 10:00 a.m. the next morning. Immediately I noticed an improvement in my mood; sleep had seemed to help tremendously. At the least, I could finally acknowledge to myself that, yes, I had finished a game, and take some pride in that fact.

Lesson Learned: Never underestimate the value of good sleep. Working rested makes a huge difference not only in how you feel, but in the quality of work you output.

Now that the development part of the competition was over, each participant in the jam was expected to play each other’s games, rate them, and provide feedback. I hopped onto my desktop and started checking out the various games. I played a lot of clever implementations of the sacrifice theme: as a game mechanic, as part of a narrative, as the goal for the game…the creativity from the other developers was on full display.

All the while, I kept checking back on my own game. To my surprise, people were not only playing the game…they didn’t completely hate it. Sure, there were critiques on numerous aspects of the game, many of which I was expecting; but there were also plenty of positive comments about some things which they thought I’d done well: the choice of game genre, the incorporation of spellcasting, the visuals and audio… I hadn’t been expecting anything positive, so these comments pleasantly surprised me.

Over the course of the month of December, I continued to play other games (though not as many as I’d have liked, due to work obligations and the holiday season) and give my own ratings and feedback. Other people continued to play my own game and leave their feedback.

At last, at the beginning of January, the ratings period was officially over, and the scores were released for every game. I logged onto the site, and found my results:

The final results from my debut game entry, Sanity Wars.

With my own numbers known, I went to check on the overall stats for the jam, from which I’d learn how high my score truly was:

The overall statistics from Ludum Dare 43.

There were 2,514 submissions. Of these, 1,494 had received enough ratings (20 or more) to be awarded an official ranking.

Thus, my final placement: 842nd out of 1,494. Right in the middle of the pack.

To me, the final score was both surprising and expected. Expected, because this was within the middling range where I felt I’d wind up with my first-ever game entry. Surprised, because I never expected this flawed, imperfect game to score so highly. It still amazes me even now, as I type this retrospective.

It just goes to show: Never give up, even when all hope seems lost. This adage is a mantra that I try to live by, contending with my bipolar disorder, and time and time again I find it to be a true maxim. With my emotions screaming failure, I persevered and found triumph.

I Am Now a Video Game Developer

It’s official, now. Having designed, built, and released a video game, one which people were able to play, I am now a video game developer. It feels good to say that, after all the learning and preparation I put in to get to this point.

Overall, this competition was an incredible experience for me, in many ways. I set out to make my first-ever game, and I not only succeeded, I did better than I expected of my entry. Though I still wish I had been able to release a more polished game, the experience I’ve gained was invaluable, and will prove critical to my future endeavors to publish a full-fledged video game.

Among the things I learned: coding a game engine from scratch is hard. I mean, I knew that, and I knew that the main reason I did build an engine was to learn how things worked from a general perspective, but it gave me a whole new appreciation for the work game engine developers put in to make usable, reliable engines for others to use. Although I like tinkering with systems and how things work under the hood, Sanity Wars drove home a well-worn adage in the games industry: make games, not engines.

I also think I tried too hard to come up with a complex narrative for a game that, ultimately, did not need much narrative involvement. Presenting an allegory of the bipolar struggle may be neat for short stories, but it didn’t translate well into a game. I’m a narrative-oriented person, so I like creating complex narratives; however, by focusing too hard on cleverly implementing sacrifice in Sanity Wars‘ narrative, I instead sacrificed the fun of the game mechanics. In the end, I felt neither the narrative nor the spell sacrifice was well-implemented, and I could have done both more simply by highlighting the sacrificial nature of tying spellcasting to your health.

My goal with taking part in Ludum Dare was to gain experience, and in that regard I achieved success. Most of all, I learned that I can, indeed, create video games, and that this is something I want to keep doing, depression be damned

To put it in a melodramatic way…I faced the Dread Overlord, and conquered him

What’s Next?

I am currently working on rebuilding Sanity Wars in Godot, an open-source game engine, and comparing the experience to coding everything by hand. I suspect it might ease many of the pain points I had developing my game and engine.

Why did I choose Godot, over something more familiar to my background, such as PhaserJS? Well, I read plenty of good things about its ease of use, and the node-based architecture is very similar to the structure I used in my own engine. Plus, I thought it’d be nice to have a GUI to work with, instead of coding everything by hand. I plan on releasing the Godot version of Sanity Wars once I’ve finished the port.

The next Ludum Dare is at the end of April. I’ve already made sure to take time off for the dates of that contest. I look forward to taking on whatever challenges it throws my way, with the goal of releasing a more polished game.

As for my dream of making a professional video game? That dream is alive and well, and as I continue to learn and grow my skills, my confidence that this dream can be realized continues to grow. And as long as I keep moving forward, and don’t give up, I have no doubt that one day this will no longer be a dream.

It will be reality.

You can play the Ludum Dare version of Sanity Wars here.