Devlog #1: Learnings from the Jam (Postmortem)


The Game Off 2023 Jam was held from November 1st, 2023 to Dec 1st, 2023. This year's theme was the word "Scale". Now that the jam submission period is over, I wanted to collect some of my thoughts and learnings from this year's jam, as well as lay out some plans for the future.

Interpreting the Theme

When I saw the theme revealed, an image of some gargantuan entity towering over the Earth with its head above the clouds came into my mind. 

CDN media

I briefly thought of some kind of Godzilla monster game (as several others in the jam have tried). However, starting with a giant monster meant that there wouldn't be a scaling process. Then I thought of this amazing fan movie for the sci-fi series "The Three Body Problem":


and the main game idea game to me. 

I would make a game where players can buld their own mech suits, starting from a very microscopic scale, all the way to the gigantic finished product. The main mechanic and feature would be that seemingly small design decision on the smaller scale would add up to a qualiative difference on the larger scale. Moreover, the granularity of the smaller scales will create essentially infinite builds for the final mech suits, resulting in ultimate freedom given to the player.

So while you battle with the mechs, the finer details are too small for you to see. But added together, they make all the difference. (There should be some idiom or proverb for this, drawing a blank right now...)

Main Inspirations

There were many inspirations for the idea of the game, even aside from the media listed in the previous section. 

The first is Mindustry. It's a factory game and RTS game rolled into one. I really enjoyed the fact that you weren't only building factories, but also building them for a strategic goal. This led me to the idea of building chips and modules for a Titan fight.

The second is Cosmoteer. This is a space sim that I'd like to think of has an "Amoeba battler". It is a game where players build spaceships out of individual panels and components, along with a crew which moves and completes tasks on their own. So every part of the spaceship, from start to finish, inside out, is built by the player. Its power is a then a function of every build decision ever made. The main takeaway from me here is how the ammo and energy sources are fed into different modules and affecting their power and fire rate.

And last but not least, I must give credit to Gundam Seed, as it was my first childhood exposure to the mecha genre.


Now I will go a bit into the nitty-gritty details of implementing all of this. Admittedly I ran out of item before the end of the jam, so there should be a much more efficient way of doing what I did. However, I wanted to record my design process so that I can improve upon it in the future.

Tech Stack

I chose javascript for this jam, because this enables me to quickly prototype a playable web build. This is important because form my experience in previous jams, players are much more willing to play a game with an HTML page rather than downloading a binary build. 

Rendering was mostly done in HTML canvas elements, although near the end of the jam I also incorporated some webGL (more about this in the Rendering section). THREE.js was considered briefly, but I found it too clunky to work with (again I will cover more detail in the Rendering section). 

Additional libraries were planned, but I ran out of time to use them. 

  • box2dweb Since I was going to have procedural animation and inverse kinematics, checking for collisions with manually built functions would've been a pain. Therefore I decided to use good old Box2D for game physics (this will mostly be for mechs colliding with each other).
  • Wavefunction Collapse: This is a very useful algorithm originally used for procedural dungeon generation (as in Caves of Qud). However for this game I felt it was more appropriate to use it for procedural generation of textures. I didn't get to use it during the jam, but in the past few days I have been able to generate some fancy sprites for the chip design, more about this in the next devlog!

To speed up the development process, I used some old templates from my previous game jams. 

Implementation Challenge #1: Representing Game objects 

There are perhaps two main philosphies in game programming: Object-driven deisgn and data-driven design. To summarize, the object-drive design philosophy is to abstract all game objects into classes, then do an inheritance tree for different types.

For example, let's say you wanted to have 2 types of monsters, a flying one and a grounded one. You would first create an abstract class in javascript like so:


class Monster {
    constructor(){
        if (this.constructor == Monster) {
            throw new Error("Abstract classes can't be instantiated.");
        }
    }
}

Then derive subclasses for each type:


class Flyer extends Monster {
    constructor(){
        super();
        this.type = "Flying";
    }
}
class Crawler extends Monster {
    constructor(){
        super();
        this.type = "Ground";
    }
}

However as you add more types, the inheritance tree gets REALLY messy, and it's really easy to lose track of everything. For my game, this solution definitely wouldn't have worked well with all the customizable build stages.

This leads me to the second paradigm: represent each game object simply as a piece of data. The nice thing about javascript is that under the hood, each class object is really just a JSON object! For example, the Flyer object from above is really just 

{type: "Flying"}

that's it! So why go through the trouble of all that abstraction just to get the same thing? Plus, this is better for storing preset builds as well, as we shall see later.

So by the end of this process, every single object in the game, from small Chips to gigantic Titans, are ultimately just JSON dictionaries. If 4 Chips are put into the same Module, then the result will be a Module dictionary containing 4 subdirectories, one for each Chip. All data, no logic!

The game logic works by reading the data from a .json file and applying some logic to them. This way most game objects do not have their own draw() or  update() function.

Implementation Challenge #2: Rendering

Initially, like I usually do, I setup my 2D javascript game template with some HTML canvases, and started drawing game assets to those. This worked well for a time.

Rendering in the build panel of the game was fairly straightforward, I could afford to be a bit abstract here (energy cells for Chip building were just rounded squares), and the wires in the Modules were very simple vector graphics drawn by myself. This didn't really stretch the capabilities of the HTML canvases.

However, for the Titan panel, I ran into a big problem. What if the mech becomes too big for the screen? Theoretically, the building stage could allow players to build mechs as large as their resources allowed, possibly with 10 appendages and 5 joints each. How will I accomplish drawing that to the screen?

It was obvious that the 2D canvases were no longer good enough for this purpose, so I had to turn to 3D rendering solutions, if only to zoom and pan the camera. 

Luckily as part of my old game template, I had imported the THREE.js library for drawing 3D assets (I had two other game jam games that used this library). So I thought this would be fairly straight forward. My pipeline would then be to still render to the 2D canvas, and then take a screenshot of the canvas as a texture, and use it on a THREE.js sprite object. 

Here's where I really ran into trouble, and this was very pesky to solve. In the canvas, the convention is that going up corresponded to a decrease in the y-value, and going down was an increase in y-value. So if you're at the top-left corner of the screen, your coordinates would be {x:0, y:0}. However for 3D rendering pipelines like THREE.js, up was an increase in y-value and down was a decrease in y-value! This meant that whatever I had drawn would be flipped upside down! An utter catastrophe!

Of course I could try to debug THREE.js and make my own custom Sprite objects, but that would mean digging through pages and pages of source code, possibly getting hundreds of unexpected bugs. At this point I only had 5 days left in the jam, and I thought about giving up on this game altogether.

Then I gritted my teeth and said "You know what? Let's just code my own webGL renderer from scratch. I have to learn how to do it at some point.".  Luckily I again had a messy template from an earlier game jam I could use. I cleaned up the code a bit and with 3 days left on the clock, I managed to draw something to the screen and be able to zoom and pan. So far so good.

But of course, the y-values were still flipped. I spent another day wracking my brain for a solution. I tried to rotate the camera to the other side of the quads and look at it from the underside, but that didn't work. I tried flipping the y-value in the rendering function, didn't work. I almost gave up again. 

Then I found a very simple solution (which was probably obvious to people experienced with this stuff). I'm drawing quads after all, so why not just flip the y-values of the vertices in the vertex buffer? Sure enough, everything worked magically right after.

Disclaimer: Although I fixed the y-flipping for the general positions, in the jam build there are still some sprites that still look upside down (but in the right positions at least!). I have since fixed this bug post-jam. It's as simple as flipping the texture coordinates.

Another huge challenge is getting the rotation of the appendages rendered correctly around a fixed joint. This is a whole other can of worms which I will not describe here (it involved using matrix stacks).

Status of the Jam Version

The jam version is unfortunately, woefully incomplete. I had finished about 70% of the build mode (which wasn't ready for players to build full mechs), so I disabled some buttons. I scrambled on the final day to get a playable scene going for the battle mode with some preset mechs. 

I did not have enough time to make to tutorial for the game, so the build mode might be very confusing for new players. Part of the difficulty, besides the time constraint, was that it was hard to make a tutorial for a part of the game where the player is given so much flexibility. There were several design difficulties I wrestled with:

  1. Do I disable some features until the player learns how to,  for example, make a basic Chip?
  2. How do I show the player what each component does? This is harder than it seems, because it's not enough to simply explain a single component, there must be some way to get the player to see how multiple components work together to produce something else. Again the question of player freedom comes up. Do I just show the player what would happen? Or do I let the player build something and come to a realization on their own? 
  3. How do I present the various formulas? I have a fairly complicated resource combination system. With Tier 1 resources combining into Tier 2, and Tier 2 resources then combining into different ammo types. Also there are different energy types which power different things and decide different attributes on the final mech. Do I just draw a cheat sheet? Can I show the player by guiding them through a build? 

These were not questions I was even close to answering during the jam, and the sheer scale of the tutorial made it feel like I had to develop another game alongisde the main one. Therefore I made the painful choice of leaving out the tutorial entirely and focusing on the main game mechanics. At least this way there would be a playable game at the end. 

So in the end, I ended up with a vastly simplified version of what I originally set out to do. I even had to fake some things: the HP bar was arbitrary set at 500 for both battle mechs, even though my original idea was to have the HP stat be determined by the build itself. However, the walking animation were not faked, and I spent a painstaking amount of time making sure that the mechs could walk anywhere on the map (provided they had more than 2 working legs).

Lessons Taken

Lesson #1: I shouldn't have been so hesitant.

In the first few days of the jam, there was a lingering thought in the back of my mind that I would need a 3D renderer. However, knowing how much work that would take to set up, I convinced myself that I could get away with using HTML canvases. In the end, I was able to crunch out a working webGL renderer in 2-3 days. Which means I could've done it earlier during the jam, during a period where I was still brainstorming specific mechanics and not doing too much coding. This hesitancy cost me about 3 days of work time.

Lesson #2: I could have faked more systems.

In the last section I mentioned that I faked the HP stats. In retrospect I could have faked even more. One reviewer of the game mentioned that I could've just had a Titan build stage with preset appendages to give to the player to battle. This way the build panel would be reduced to just putting together appendages into the final mech. The players would then have gotten to experience both the build and battle modes.

Of course this would mean faking most of the systems that I had set up. I could simply have each appendage shoot ammo at regular intervals, instead of painstakingly building the right Chips and Modules to produce the right resource types to feed into the Weapon mounted on the appendages, and simulating all the resource flows (the little moving squares in the jam version). 

Next Steps

As mentioned, I've already made some progress on making the mechs look better by procedurally generating textures with wavefunction collapse. The main priority over the holidays will be to build a complete tutorial of the game so that people can enjoy the game. I also have a game trailer in the works (which will be heavily inspired by the Three-body Problem fan movie).

I haven't had much of a chance to play other people's games yet, but I'm grateful for those who played my very incomplete game and gave it a chance! Stay tuned for further updates.

Leave a comment

Log in with itch.io to leave a comment.