The Development of Cubic Jumping
tirimid
2025-04-14 (rev. 2025-04-23)

Introduction

If you are not already familiar, some time ago I released a game titled Cubic Jumping, an abstract-style speedrun / rage platformer. This game was developed over about six months with on-and-off effort. The game runs on Windows and Linux systems and can be downloaded at the itch.io page. The complete source code for the game is available at the itch.io page as well as on the GitHub. It was an immensely fun project to work on, and I think the result is satisfactory and reasonably high quality.

In this article I hope to explain how I developed Cubic Jumping, what I learnt, and lay out a comprehensive history of the game's development. The contents are also entirely retrospective rather than realtime. The layout of the article is vaguely chronological.

Prehistory

Why did I make Cubic Jumping? Why did I call it that? Why does the art style look the way it does? These questions are all answered by what I call "Old Cubic Jumping". Back when I had just started trying to make games, I was learning the Unity 2D Engine, and I decided to make a simple 1-level platformer game called, well, Cubic Jumping. Due to not knowing how to make quality assets, I just went with simple colored squares - a red player and a black tilemap environment, all on a gray background.

Hence was decided the style for Cubic Jumping, several years before I even decided to create it.

Starting Off

At first, the game was built using my build system, mincbuild, and licensed under the MIT license. Both of these things eventually changed and the project was relicensed under the GNU GPLv3.

Player Movement

In order to even design player movement, you need to decide on an physics tick. In the case of Cubic Jumping, there is a single tickrate, 100TPS, that is used for everything, including player movement and logic. This value directly determines basically everything regarding how the game feels.

At first, player movement was not configurable, and the key input was hardcoded - users could not rebind movement to something they preferred. There was a basic and flawed tilemap collision detection system which endured with minor changes through the entire game's life.

The collision works on this principle:

  1. The player is known to be within the bounds of the map.
  2. This means that the player will always have a tile above, below, left and right of them.
  3. We can get the positions of these tiles.
  4. The player has an x-axis and y-axis velocity which are independent of each other.
  5. If, on any given tick, the player will move into / through a tile due to their high velocity, we can check this by comparing, e.g. (player position + player velocity <= left tile pos + 1).
  6. If the player would collide, we resolve the collision by zeroing the velocity along that axis and setting the players position such that they are directly next to the wall.
  7. Otherwise, player position += player velocity.

This does not work well when the player would run into a corner, and this indeed caused issues on some playtests. However, for quite a long time, this collision algorithm was suitable.

(INSERT DIAGRAM OF ALGORITHM)

There are two other aspects that changed substantially between the initial and release versions. Initially, the jumping and powerjumping would continue indefinitely while the keys were held and did not need to be buffered, and the downwards dash was more of a "fast fall" which did nothing but increase the effect of gravity on the player. The feel of the game was rather different.

Map Editor and Map List

One part of a complete platformer game is a progression of maps on the player side. That is, the player has to click play, beat a level, and move onto the next level of a predetermined sequence. Cubic Jumping uses the Map List to do this. The Map List is literally an array of map structures placed in sequence along with an index into the current map being played. When the user reaches the end, the index is incremented and some behavior is triggered (e.g. move onto the next level).

In new versions, the Map List also provides some information about each map, such as names or par times. User levels, on account of not being baked into the game, cannot be accessed from the Map List.

All maps, official and user-generated, are created using the level editor. Here is the first insight I figured out - if it is applicable, make a map editor for your game as soon as you can. The earliest test levels were manually written out by punching values into the map file. As you can imagine, it was such a pain that I couldn't take it anymore and just make an editor.

The first editor was so rudimentary that it was nearly unusable. No panels, everything is done using undocumented and arcane keyboard shortcuts - in short, terrible. However, it was such an upgrade over punching values into a text file that I was finally able to make real test maps for real features.

(INSERT SCREENSHOT OF INITIAL LEVEL EDITOR)

Make a level editor.

Embedding Data and HFM

One of the things I like doing for my game projects is embedding as much of the game's data into the executable binary as I can. Here, C comes in useful, and combined with utilities such as bin2header (which I used), the process of embedding game data becomes fairly trivial. This works very well for things like music, SFX, textures, etc. because they are created using external artistic tools and then merely need to be used by the game. It is a relationship of strict immutable dependency.

For game maps, however, I can't just do that. If I store the map using in a C header, I lose the ability to read the data in dynamically, because that would require me to write a C parser to do so. That's not impossible, nor is it even necessarily that hard, but I figured there was a better way to do it. But if, on the other hand, I store the map using some binary format, I lose the ability to trivially include the file in C code.

Due to needing to meet both demands, I created HFM, or (H)eader (F)ile (M)aps. This is a format for storing Cubic Jumping maps. Basically, HFM is a C header with a comment at the start of the file which contains the binary data of the map encoded in hexadecimal. Embedding the file is as simple as doing #include "map.hfm", and dynamically loading the map is accomplished by reading and trivially decoding the first line of the file.

(INSERT SCREENSHOT OF HFM FILE IN TEXT EDITOR)

That is the next interesting takeaway: if you want to store binary and C encodings of the same data for non-trivial or mutable dependencies, you should try storing the binary data in a comment at the top of the C header.

More Tiles and Triggers

Over the next days, I added several features, among which were launch tiles, bounce tiles, kill tiles and triggers.

Making simple maps in the level editor, I noticed very quickly that, despite the movement being fun, there was basically no way to design levels past a very rudimentary level of complexity. For any game, the level design is half the experience, and the most obvious thing to do was add a lose condition and some interesting ways to move around. Hence - new tiles.

Another aspect that I quickly realized would be necessary for level design was the ability to begin game events based on the player's position, so I added a trigger system. I mostly wanted this for the sake of adding dynamically appearing text boxes, which I also implemented.

The trigger collision detection is actually entirely disconnected from the rest of the map, and the two are conceptually different systems. In the code, the triggers actually don't even belong to the map from which they are loaded. Trigger-player collision detection is a simple AABB check with no axis sweeping, so you can actually theoretically go through thin triggers without activating them.

(INSERT DIAGRAM OF PLAYER CLIPPING THROUGH TRIGGER)

Despite the primitive nature of trigger collision, it never caused any issues during playtesting. This is because I designed the levels in such a way that the player would never get enough velocity to skip through the triggers, either by slowing the player down or by making the triggers thick.

UI and Redoing Editor

The map editor, as stated earlier, was extremely rudimentary for a short while - better than literally nothing, but horrendously annoying for actual level design, which I obviously needed to do to make real levels. I decided that the editor should have an actual mouse-and-keyboard-enabled GUI with UI buttons for editor functions.

I threw together a quick UI module which would handle UI updates and render for discrete components like labels and buttons. Integrating this into the editor quickly made it more recognizable as an actual editor and enabled my friends to make maps, which I could not really imagine with the old editor.

Thus was born the first version of the new editor.

(INSERT SCREENSHOT OF REVAMPED EDITOR)

Menu Navigation

With the game systems starting to take shape, I thought that the old command line interface for launching the game in different modes (gameplay v.s. editor) was no longer suitable. There had to be a main menu that launched the game's different modes.

The difficulty was figuring out how to switch from a menu to a different gameplay mode. The first solution is simply having a global variable along the lines of g_currentmode or something, and then switching over it in the game loop. However, this is really inelegant and means that all game loop functionality has to be implemented in a single file for any kind of reasonable source structure.

Over time, I came up with something else - reconsidering the menu navigation conceptually. That is, the game is not navigating from a menu to a gameplay mode; but rather from one menu to another menu. All the menus are implemented as functions containing their own game loops (as well as some glue for intermenu communication, basically just global variables for menus implemented in one module). This is not how I conceived of it originally and is a new formulation, but that's basically what I did.

The concept can be simplified like this: when the user enters a new menu, that new menu doesn't assume flow control from the currently active one. Instead, it creates its own "control frame" on top of the active one, and then destroys it when the menu is closed. This has the effect of ordering the active menus in a stack-like structure, although that is just an emergent property of the system rather than anything explicit.

(INSERT DIAGRAM OF CONTROL FRAMES CONCEPT)

You can try this method for menu control flow as it is fairly minimal and maintains menu state all the way down until it is not needed.

First Levels and Playtesting

At this point, I made the first few real levels. I had enough functionality to design levels, switch between them at runtime, and gameplay was satisfactory. I did some work on improving player collisions and configuring some gameplay values like player friction.

For my map naming scheme, I decided on a modified version of the DOOM-esque ExMy (Episode X, Map Y) convention. My levels were named with CxEy (Chapter X, Episode Y), as initially the game was planned to release with multiple chapters. Of course, the game was released with only one chapter, but the designed multi-chapter system still exists and can be seen in map names and at the end of the game. This is one of the things I would do differently if remaking the game now; not implementing these theoretical systems that actually have no real use. Basically, YAGNI.

TODO: finish this article.

This work by tirimid is licensed under CC BY-SA 4.0