Post-Jam reflection journal


So, the GMTK Game Jam for 2025 is over, and it’s been a huge showing of many great games.

While it’s relatively fresh in my mind, I thought it would be worth talking about the various stages of the game as it went through development.

Pre-Jam

There was an excellent post in the discord which offered advice for doing your first game jam, but I’ll skip the real-life aspects and focus on development.

My first question that I had for myself was: Did I want to use a major game engine? I was pretty keen on NOT using any of the major engines. I’ve tinkered with them, but not well enough to be confident that I could create something without spending 30% of my time reading documentation.

In contrast, I was recently working on a game that, fit the game’s theme so perfectly I was sad that it would be against the spirit of the Jam, so, I selectively pulled all of my code out of that game that was relating to Graphics, Sound, and Input. I left behind anything I thought was a game mechanic, including the infinite looping tile map. Here’s what I started my game jam with:

  • Code to Initialize a window and set up OpenGL
  • A way to load and use shaders
  • A way to load and use 2D Texture Arrays
  • A way to setup and configure Vertex Arrays
  • A sprite renderer
  • A 2D Camera
  • A 2D Debug renderer
  • An input system with support for buttons and a 1D Axis (horizontal or vertical)
  • Code to initialize OpenAL
  • Code to allocate and play sound effects

I also knew that I had played a long time ago with getting libOpenMPT to work with OpenAL, so I was crossing my fingers I could resurrect that code for music.

My thoughts were that I could throw together a 2D sprite game without too much effort with this

After that, I had to make sure I had all the software I needed on hand. I’d be using

  • Visual Studio 2022 for coding
  • Resharper
  • GitKraken for Source Control
  • Aseprite for sprite sheeting
  • OpenMPT for music
  • Chiptone for sound effects
  • Affinity Photo for anything high res
  • Blender in case I did anything in 3D
  • RenderDoc for figuring out OpenGL weirdness

Finally, there was some advice for getting familiar with using itch.io as a developer, so I started making a hidden page I could use.

Lastly, I set up a build pipeline on Azure DevOps that would automatically build and publish my game for me with the hope that when I was ready to give a build, I could just set that going and forget about it. I was pretty keen to do a native build for performance and size, trimming away any unnecessary code I thought would be great.

I set up a discord channel for notifications.

With all that done, it was 11 PM, and the game jam was due to start in 3 1/2 hours, but I was already dead tired, and I wanted to get up early and invite my kids to help me come up with an idea for the game.

Day 1

Upon waking up in the morning, the first thing I did was wake up my kids early and we all watched the GMTK theme video to find out our theme was “Loops”.

We discussed a few things:

  • A game that is literally just Satisfactory Hypertube cannons
    • You’d have to try going through a loop a certain number of times and exiting when your timing is right to go the correct distance
  • Something about spirals
  • Something about knitting
  • Repetatively making jewellery
  • Katamari, but you just keep getting bigger
  • Breaking out of a loop

I liked the spiral idea, I thought having someone running on the inside of a spiral might be cool, shooting bullets that flew around the spiral with some platforms, but I couldn’t explain it well to my kids or missus, so I made this:

Initial concept

I decided to run with that. For better or worse that was going to mean giving up on almost all of the 2D stuff I had in my Engine project.

Getting Started

The first step then was to build a spiral, so my first goal was to get the front and top sides of a spiral (those were all the player would theoretically see). We’d need geometry, a shader and a 3D camera.

Day 1 - 10:49AM

This would also introduce a single directional light that would haunt me throughout the whole project with no time to fix it, and with another hour, we had an algorithm to make spirals (the resolution was configurable, but chunky made it easy to see)

Day 1 - 11:46AM

Spriting

Next we needed a running man. To achieve this goal, we needed a few things: A Sprite, a working sprite renderer (which would involve another shader), and to make him run, we’d need to implement keyboard input. In the first pass, he couldn’t even rotate around, and I made the stupid mistake of making him the almost same colour as the loop.

Day 1 - 3:18PM

We got the guy rotating around in a circle, and then messed around with rotating the camera with the player so it looked like it was the spiral spinning and not the player.

The sprite was far enough away from the camera that lines kept disappearing (I don’t have mipmaps), so I decided to move the camera closer. This served as a pivotal moment for the game as we would iterate on this idea, but not too many changes were made other than tweaking the camera offset and angle to try to give the player a little more notice of what was ahead.

Day 1 - 4:13 PM

After that, I thought I had something I could give to someone to test, so I decided to set a build going, and wouldn’t you know it, the build failed because I couldn’t compile the shaders on Azure DevOps, so we lost a lot of time trying to figure out how to solve that, but while waiting for builds we didn’t let that slow us down, we tried adding a sprite from OpenGameArt

Day 1 - 6:21PM Day 1 - 6:21PM

I wasn’t a big fan of this guy, he looked cool and had the gun I was aiming for, but getting him to fit in my 256x256 sprite allowance wasn’t working too well as all of his animations were arbitrarily sized and required a lot of messing with, but an hour later, we had him animating with a walk cycle. The sprite system was supposed to support flipping sprites, but it wasn’t working at this point, this was due to the sprite system working in a “Tiles” unit instead of Texture Coordinates.

Eventually I got sick of trying to get the fox’s animations working and I instead switched over to an asset pack I bought on Itch a while back that had all the animations I would need. I also noticed that the spiral went from dark to light, but I wouldn’t give myself time to fix that for quite a while.

This guy could not only run, but he could now flip directions and when he was moving slow enough, switches to a walk cycle. I also added support for not resetting the animation frame, so that the switch from running to walking isn’t so jarring.

I switched from hard-changing the player’s position to using a velocity, and that allowed me to gradually slow him down instead of instantly. (It’s still very quick, but it helps a LOT.)

Day 1 - 7:31PM Day 1 - 7:31PM

At this point, I needed to make sure I took care of my kids and myself, I got the build working and encouraged people in my discord to try it out. While I waited for that, I decided to try the game out on my ARM64 Surface Pro 11. The game crashed while loading, but I left it at that for the day.

Day 2

Linux Side-Quest

I saw overnight that there was a fellow in my discord who wanted to test it out, but he was running on Linux, so I decided to “quickly” spin up a Linux build. This took about 3 1/2 hours, and honestly was not something I should have tackled during the Jam. Next time I’ll make sure I have Linux supported from the get-go. (MacOS support is possible, but distributing random apps on Mac seems to be a bit of a task.)

The feedback was reasonable:

  • The loop runs out too fast
  • The game crashes with the same error as my Surface
  • The guy still turned invisible with that build

After I got Linux support in the build, I got to fixing the bug that was noticed on the Surface, specifically, I was using a kind of “Shader reflection” to examine the internals of a shader and figure out what things needed to be attached where, but it turns out that the machines being tested on didn’t support that, so I switched it up so that I still had to hard-code specify the values, but if supported, it would validate to make sure I was setting things correctly when that reflection data is available.

With that, at 11:13AM on Day 2, I published version 0.1.0 and went to lunch.

After lunch, I got back to work on feature development. At 1:14, I added a jumping animation, which needed some fine-tuning. The animation system was designed to loop frames, so I made it so that a jump has three phases, Going up, cresting, and falling, and each section only plays once. Which animation is playing depends on your vertical velocity, switching from going up to cresting only happens when you’re in the air, but your velocity is below a certain speed.

At 1:54PM, I started introducing the concept of a “Floor Height”, and made it so that if you ran to the left, that there was no floor and so you’d just fall infinitely, otherwise if you were below the floor height, it would just reset your height so you’d be on the ground.

Infinite World

After that, I got into a brief slump, not sure how to progress and I ended up procrastinating for quite a while watching Anime, and pottering around refactoring and cleaning up the code without doing anything meaningful, then around 6:15PM, I had started Implemented the “Loop” class, which would be the foundation of the infinite spiral.

The Loop was in charge of detecting when the player reaches a “LoadPosition” which would trigger the next Segment to be generated. A segment at this point was generally 6 full circles of the helix.

Day 2 - 7:31PM Day 2 - 7:31PM

In this image, you’ll notice a lot of aliasing as the loop went into the distance, so I looked up how to enable anti-aliasing, and after deciding to make each generated segment of the loop a different colour, I noticed there were some segments Z-fighting (therefore overlapping). I also finally fixed the normal for the faces.

Day 2 - 12:21AM

Finally, I switched my logging over to use Serilog instead of Console.WriteLine, which was causing stuttering

Day 2 ticked over, but I felt like I was achieving things and kept pushing through.

Day 3

Trying to find fun

A discussion with my brother came up, and he suggested adding puzzles that repeated until you successfully completed them. With no other bright ideas for how to make the game interesting, I started adding support for buttons, which I decided to use instanced rendering to achieve. Each instance has a position and a colour. The system was designed so that many buttons could share the same state, so if they repeated, you could see the ones in the distance activate when you stepped on their paired equivalent.

Day 3 - 4:44AM Day 3 - 4:44AM

It was super late at this point, so I decided to call it and sleep, which meant I missed most of the morning., but when I got up, I immediately started working on a collision system. I didn’t want to try using Box2D or some other kind of Physics engine as I know they exist, but I’m unfamiliar with them and didn’t want to learn a physics system as part of this Jam.

Instead, I started writing something similar to an Axis-Aligned Bounding Box. This might seem odd because every entity has a different “Up” from every other entity, but my thoughts were that at the point they were colliding, they’d be close enough to parallel that it would work. This turned out to not be the case, and the inability to visualize my collisions caused a LOT of confusion, so next, I added a DebugRenderer. This thing was REALLY hard to use. Because I never updated this to clear the depth buffer, I had to move the rectangles in front of the loop or player in order to see them, and it made it REALLY hard to see if they were intersecting or not.

Eventually I got it working to a satisfactory degree and made it so the buttons go brighter and change their position when the player collides with them.

Day 3 - 2:52PM Day 3 - 2:52PM

I then thought for a palette cleanser, I’d throw a “Jump roll” animation in from that character set as a spawning in animation, which then transitions into the jump falling animation. I loved how it turned out.

Day 3 - 13:44PM - Death Animation Day 3 - 13:44PM The game wasn’t compiling at this point, so this is a recreation

One thing that’d been really bothering me was that there was always the temptation to run to the left, which is always an instant death, I thought to myself I won’t have time to write a menu system, so maybe I could turn it into a quit option, then took a break to go for a nature walk with the family.

When I sat down again and started streaming, I tried quitting the game, and I liked it a lot, when I implemented it, but there was two things, one is that exiting the game still felt really jarring in a way that irritated me, so I hacked together a stupid animation for closing the window.

My stream absolutely loved this, so we iterated on it a little bit and came up with this:

Day 3 - 7:31PM Day 3 - 7:31PM

Naturally, after this, I needed some way to tell the user that going left would quit, so I then started adding a bunch of “sprites” with letters and writing a system for laying out text. The font I used was Kenney Future Square that came from Kenney Game Assets

Day 3 - 9:02PM Day 3 - 9:02PM

Now, during the development of the text, I was starting to freak out a little bit, because I have a visually appealing “thing”, but it’s not a game. Button’s aren’t fun to press, especially when they don’t do anything. More importantly, there’s no REASON for the player to keep moving right. I didn’t want to take away the player’s agency and force them to always run right.

There was about a day and 5 hours left to implement SOMETHING fun, and I was very much keeping in mind that SOME of that time needed to be dedicated to sound, music, bug fixing, polish, etc. I wanted to leave at least 6 hours for that, and I absolutely NEEDED to get a good amount of rest, so realistically, I had less than 16-18 hours to come up with an actual game mechanic.

The idea that saved the jam

I was floating the idea of a spinning sawblade destroying the map behind you. I felt like I could achieve that, and that’s when my close friend and many time collaborator, Rorax, dropped the suggestion that changed everything. She suggested to me that the level itself could be the world-eating snake Jörmungandr

I loved the idea, however there was no way I would be able to pull that off in the time I had. I’m not comfortable enough with my 3D Modelling skills to achieve that, and so I was going to move forward with the sawblade idea.

The first thing I would need to do was change how I made the Loop. At the moment It was just one 3D model that got generated when you got close to the end of the existing content. It would delete old sections when they got too far from the camera, but I was going to need them to be destroyed bit by bit.

So I started working on a system to used Instanced Rendering to break the world into smaller little arcs. I could also colour each tiny arc separately, making smooth gradients along a generated segment.

Day 3 - 10:08PM Day 3 - 10:08PM

I was about 3/4 of the way through making this when, unexpectedly, Rorax dropped me a picture in discord, followed by a Blender 3D Model a few seconds later.

Suddenly! Snake head!?] Suddenly! Snake head!?

Snek

With a 3D model in my hands of a snake’s head, suddenly everything seemed possible, and the actual concept of the game was born.

First, we needed to export the models in .OBJ format because it’s the simplest thing to parse and would work well with the geometry system I already have. The easiest to access and use seemed to be ObjLoader It had a NuGet package, however, it required a bit of work to make it play nicely. I deleted their custom Vertex/Normal/Texture data objects with the ones from Silk.NET which were native to my game, and I noticed when trying to load the model that it doesn’t understand the .obj format’s o Snake_Head syntax.

It took a bit to figure out how to get all the triangles in the .obj to render correctly, I always struggle with visualizing winding order and so some triangles were defined backwards, or with the wrong vertices, but eventually we got it, and while we were implementing that, I asked Rorax if should would put together the snake’s body for me, but when I got it all working and in the game, we had a snake! I decided to make the tail jiggle around a bit to make it seem a little more alive, and I noticed that something was REALLY odd with the colours. There was no shading on it at all:

Day 3 - 1:21AM Day 3 - 1:21AM

Thankfully, when looking at the details in RenderDoc, I could see that I’d incorrectly calculated the normals when importing the model, we fixed that and made him fade in from the background. We didn’t have any kind of fog, so I just updated each segment’s colours based on how far back it is.

She gave me 3 models, A, B and C, and so we’d repeat them A-B-C-B-A-B-C-B-A, etc. Ideally we’d have flipped every second B part, but there were still issues with lighting and normals. We were still using that terrible lighting setup from when I wrote the first shader, and it was really showing it’s weakness now, especially when I got the snake moving.

The first pass at moving he was moving so leisurely it was going to take 10 minutes to reach the player, so I fidgeted a bit with his speed and how many revolutions deep did it spawn. I also hated it when he spawned in while I was looking at the Quit and Run text, so we delayed him spawning in until after you reached a certain point, but in order to have Snek reach the player in a decent time, I bumped up his speed to be about 10% faster than the player, and the player was moving REALLY fast! (about 90% of a revolution every second), so naturally the snake was EVEN FASTER. Still, it was good for testing, so I left it like that.

I also removed the buttons because nobody has time for pressing buttons anymore.

By this point, I was dead tired and I was going to need a good amount of sleep before I could continue. It was getting harder to solve problems, but I felt like I could see the light at the end of the tunnel.

Day 4

Bringing Snek to life

After waking in the morning, the first thing to tackle was the snake’s normals, which was thankfully just a simple stupid mistake.

Day 4 - 10:44AM Day 4 - 10:44AM

Next we needed to add collision, and the ability to restart the game if Jörmungandr, AKA “snek” touched you, which meant adding collision to it, and making sure I could delete the whole world and add it again without leaking memory and breaking everything.

Because my Sprite Rendering and model instancing are created at the start of the game and re-used everywhere as part of resource management, I needed to make sure these were cleaned up properly, otherwise there would be text or pieces of snake left behind for subsequent replays.

While testing this feature, I realized that if I ran too far, that the game would absolutely TANK in performance because it just created segments of the snake endlessly, which caused a LOT of work for the collision system.

It was also pretty unsatisfying when the snake got you, so I threw together a REALLY quick and dirty particle system to add some death sparkles. Unfortunately, I was starting to find out that there were some severe issues with the collision detector, the snake was killing the player WAY too early, even though the debug rendering boxes looked correct!

Day 4 - 12:45PM Day 4 - 12:45PM

I also noticed while testing this that there were gaps between the Segments that needed fixing.

I wanted to fix the snake’s collision, but it was “Meh… I could ship the game like this if I had to.”, right now I needed something to to make it more challenging to run from the snake since you had no way to overtake it, and the only thing you could do was move forward.

Mind the gap

The gap in the segments gave me an idea. What if I added them in deliberately, but randomly? Since I made the system that deleted arcs within a Segment, all I had to do was not spawn in a model, and keep track of which parts of the map was holes, but not before I added the ability to prevent Snek from spawning.

Day 4 - 2:12 PM

I noticed that the death sparkles looked bad, but trying to fix it caused a black box around the player which caused a bit of lost time. I ended up reverting the code and just accepting the poor visuals. I wouldn’t realize until later that I haven’t separated my semi-transparent sprites from my opaque sprites, and that will always cause trouble.

I also encountered an issue with the calculation of what was a gap and what was floor was out of sync with the visuals, so we had to fix that.

At this point, I really needed to think about how fast the player and the snake were moving, but I was finding it really fun with just the player’s speed as it is, so we didn’t end up changing much for balance. There was something addicting about trying to find a platform to jump on when it was just out of sight. You had to keep your eye on upcoming platforms to know where to land.

At this point, I felt like I actually had a game I could ship, and my relief was palpable, which is good, because at this point it was 2:30pm there was only 12 hours until the Game Jam was over.

I still had 6 hours before I needed to commit to finalizing everything. I knew adding the sound effects wouldn’t take long, but picking a sound effect and music could be really hard, but I thought 6 hours would be enough to do it, so let’s spend the remaining 6 hours improving the game in whatever way made sense.

Collectables!

First up, I wanted to add some kind of collectable, to distract you from how fast the snake moves.

We quickly searched through some game assets I purchased on itch a while ago, and found a coin from Kenney Game Assets 3 that seemed like it would do, and after loading the model, and creating a system for creating “Coin Chains” randomly by picking a chain length, starting height and an ending height and interpolating between them.

Next I started keeping track of how many coins were collected and added text to the start of each run to say how many coins you collected.

Day 4 - 4:41PM Day 4 - 4:41PM

While we were implenting all of this, Twitch Chat and I were discussing other power ups. Someone suggested having items that could be picked by by the player to benefit the player, or picked by snek to penalize the player, this would be the only way that players could actually affect how long the game went for, so I was all for it.

I couldn’t find any models that were immediately obvious as a speed-up, so I turned these trees from the same kenney pack on their side. Unfortunately they required a bit of finagling to get them oriented correctly, and as a result, they visually didn’t match their collision bounds. I didn’t like how they looked at all, but we had < 4 hours left before we start doing audio.

Day 4 - 7:19PM Day 4 - 7:19PM

I decided to start implementing these from the player’s perspective first, Originally picking up one of these would apply an immediate and forced burst of speed to the player, removing agency, but it was so hard to control that it was painful to use. I axed that approach and decided to instead give the player +3 seconds to an accelerator that would gradually, over about half a second, increase the player’s top speed by 2x, and then when the time runs out, slow back down again. This was MUCH more controllable and fun to play.

We then tackled decelerating Snek, and used the same system to decelerate the Snek to about 25% for +10 seconds every item.

After testing all this, it was obvious to me that allowing the snake to pick up your missed items would just be unplayable. The only reason I could land on platforms was because I was in control of my speed, but having the snake slow me down would cause me to fall in pits too often, and having him speed up would just make it end quicker. You would be struggling to make any progress at all, so the idea was dropped.

Polishing

We were now and we spent the rest of the time tweaking numbers, boosting the ambient light’s colours, changing the light position so everything wasn’t so dark.

I also added coyote time (allowing you 0.12 seconds after missing the edge of a platform to jump) which dramatically improved the playability of it. I also tweaked the level segment colours.

Day 4 - 8:06PM Day 4 - 8:06PM

This was something I could honestly be proud of. There were definitely issues, but it was SO FAR from where we were 24 hours earlier I was over the moon.

There were about 6 1/2 hours left, it was time to finish this up.

Tension in sound

I knew I could add sound effects pretty quick as I had working examples from the previous game I was working on, so I instead thought I should focus on the music side of things, which was a little less certain.

I was under the impression that had written an unreleased C# library that would wrap libOpenMPT so that I could retrieve an audio stream from some kind of Tracker Music . Because these would not be pre-rendered audio, but a generated audio stream, it can loop seamlessly and in theory, we could do some fancy things with it, time permitting.

Thankfully, the library not only worked, but apparently I also included a working Silk.NET OpenAL wrapper, so it just needed a small amount of adapting to load and play a song.

Unfortunately, we soon discovered that looping infinitely was not something that the player did natively, so we had to write some additional code to support looping infinitely.

All we needed now was to find some music that was appropriately licensed, so I went with my Twitch Chat modarchive.org’s cc0 page and found Deciphering by K. Jose which starts off interesting but builds up enough with tension.

After deciding on a song, I had an idea. I felt that the song’s tension really ramped up when I increased the tempo in OpenMPT. So, I decided to also add support for modifying the tempo, so we could make the music speed up when the snake started getting closer.

With music sussed, we needed to turn to sounds. We started poking through Kenney’s sounds, but I remembered a tool I used for making sounds for a previous game: ChipTone by SFB Games. This allowed me to quickly generate a whole bunch of sounds and pick something we liked, after slotting all of these into the game and triggering them at the right times, It’s 10:36PM and there are 4 hours until the end of the Jam, so now it’s time to turn to making sure the build works and putting together the itch.io page.

Getting prepared for the release

Rorax helped me put together the main hero image for the game, and after another hour we were done. Unfortunately the builds were missing the libopenmpt.dll libraries, and so we needed to do a number of iterations on trying to solve that, only to find there are no linux libopenmpt.so library I can include, which goes against the rules of the Game Jam (sorry linux users! Best I’ve been able to do is disable music probably?)

Last minute bug fixing

But with that working, we had 2 hours left to cram in as many other little things as possible including:

  • The Game title in-game
  • Credits for coding, art and music
  • Improved the level layout to not spawn so many upgrades and prefer coins.
  • Rorax made some better graphics for the speed up/slow down collectables
  • Fixing a couple of crashes I found

And Finally, we changed the name of the .exe file.

Day 4 - 02:06AM Day 4 - 02:06AM

The last thing we had to do was submit the game to the game jam, and I almost made a mistake, I thought I submitted it by submitting it in the Discord channel, I didn’t see that I needed to submit it on itch.io! (I was tired and frazzled)

But we found the button and clicked it 3 minutes before the deadline (before they extended it an extra hour)

Post Release

With the Jam over and done with and the game out, I came to realize that I’ve been messing with writing games since I was using Visual Basic 6 and Direct3D 5 on Windows 98 (or there abouts).

This is the first time I’ve ever released a game I wrote from scratch to the public. The number of things I’ve achieved in this game jam are beyond what I knew was possible for me to achieve within 96 hours.

After taking a break to check out some of the other Jam entries, I’ve been doing numerous fixes and improvements to the game for the moment uploading is no longer locked.

  • Walls between gaps
  • Better collision when falling out of the world
  • framerate independant physics
  • Better text management
  • Fixed scoring system now correctly in Loops
  • Gamepad support (including late connecting controllers)
  • Improved that stupid quitting window animation

There’s still a bunch more I want to do, but it’s time to return to my family and real life.

Get Jörmungandr

Leave a comment

Log in with itch.io to leave a comment.