I just released Kisa, a 2D hex strategy game inspired by Dice Wars, Risk and World Wars 2. The game is not impressive per se, but it marks a huge leap and a tremendous improvement over my past established process of game development. I feel like I’ve just discovered the perfect balance between low-level control and quality without considerably sacrificing “productivity” (yeah, I hate that word too).
How I Got Here
Game development is what originally got me into programming. It has ACKCHYually been more than a decade since then, but that wouldn’t look good on a title, so I rounded it down. We’re about to delve into the nitty gritty details so make sure your Modafinil is ready.
Pre-Historic Period
My memory is blurry, and I can’t seem to properly be able to track the big-bang, so to speak, of my “carrier”. After some research, I’ve come across this Kongregate account which I registered with my twin brother when we both were 11 years old. Kongregate has spectacularly fallen off since then, rebranding into an embarrassing crypto platform with a horrible website design that turns your CPU suicidal. I met lots of wonderful people there and I’m thankful, although the feedback I got was unsurprisingly harsh, with most of my garbage averaging 2 stars.
I’m certain that something is missing. I did venture into an awful lot of offline game development prior to that, but I’ve got no evidence whatsoever. I distinctly remember messing around with Game Maker and Unity. The discovery of online platforms was indeed a long-awaited paradigm shift since my monkey-brain could barely resist the pleasure of having an impact out there and, in theory, becoming popular.
Scratch Era
My first ever Scratch account must be this one, which I apparently registered when I was just 10 years old (turns out that it preceded my Kongregate carrier, oops!). I’m sharing an embarrassing amount of information here, but my child self is no different than a complete stranger. Scratch, similarly to Kongregate, was plenty of fun for much the same reasons.
I deactivated my games long ago. Most of them had a YouTube vibe, and the awkwardness was just too much for me to cope with. Scratch marked a significant chunk of my life. The influence was so strong that my brother and I ended up returning recently and for a brief amount of time to joke around. Most of our conversations and uploads are community inside jokes with friends around our age. I think it’s time for us to let go.
Roblox and Fame
Around the same time, I signed up to Roblox. This was by far the most defining period of my teenage life. One of my earliest memories is me building the bank obby, the first game listed on my profile’s Creations tab. I vividly recall guiding four players around the maze (some of them having purchased builder’s club), until everything suddenly collapsed to the ground likely because I forgot to disable physics simulation.
Roblox is powered by a dialect of Lua, which I never ever bothered to learn. In search of robux, I taught myself blender and paint.net, rigging and rendering scenes in a profession that got me the prestigious title of a “GFX artist”. It was essentially child labor, with a ten percent chance of being scammed. I communicated with clients through Discord and I think I’m lucky I was not molested judging by how the platform’s long-lasting child grooming crisis has recently unfolded.
This otherwise stupid environment, in tandem with my subsequent deep web development stage, unconsciously contributed to the formation of a decent, albeit still lacking, artistic perception.
My rise in popularity began with the creation of a minecraft obstacle course. This time, coincidentally, I had taken steps to implement in-game purchases. The active player count suddenly jumped to 100 following the release of a Romanian (I believe) YouTube video. This led to Dennis, the most popular Roblox YouTuber at the time, creating a video about it and accumulating over 9 million views. The playerbase explosion that followed was massive, peaking at a thousand concurrent players. Joining the game with the creator tag clearly visible on the screen was an otherworldly experience, exactly what my teenage ego was always craving for. The success got me invited to an obstacle course developers group and to a private chat with one of the most popular developers out there (the name of which I’m failing to recall).
After all that, I briefly got an interest in scripting, leading to the development of Rake Simulator, “simulators” being the Roblox reincarnation of the clicker game era. The game was eventually sold for about a thousand robux (10 bucks). What followed was some additional freelancing and the release of a couple more games, most of them insignificant.
Dark Ages
Next up was a short period of offline game development in Godot. I released a space platformer with an unoriginal gravity shifting mechanic on itch.io that brutally, and rightfully so, went unnoticed. In recent years, I grew disappointed of game engines and started experimenting with “low-level” graphics programming in a handful of unusual languages, including V, Zig, Rust, Odin, and C.
Here, again, community feedback is everything. My Roblox phase was indeed the happiest and most exciting period of my pre-adult life, despite the actual “programming” involved being mundane. That’s something that I’m actively missing as of today.
Thus, I entered the Dark Ages. In high-school, I silently experimented with Vulkan and OpenGL but I gave everything up in favor of the much simpler SDL libary. Truth be told that this sudden shift is to be attributed to my then rising obsession with Linux, as well as my faulty and cheap hardware. You can still find these games on Codeberg but, spoiler alert, they aren’t particularly fun and the code is somewhat of a mess. Vector arithmetic in C is torture and SDL2’s built-in GPU renderer lacks the ability for fine-tuned control and offers no cross-platform shader support.
Discovering Odin
I had fiddled around with Odin in more than one occasion, about two and three years ago. I had heard about sokol in an obscure, niche game development chat room and I was quickly tempted to try it out. Sokol has always been traditionally associated with Odin, especially after the release of Solar Storm.
Life lesson: I came across a Redditor throwing mud at the library and that severely delayed my decision. Don’t ever listen to Redditors, and don’t you ever listen to their polemic mutations (they’re nocturnal, their natural habitat is the mainstream internet and they can be identified by their linguistic preference for ‘WTF’ and an obscene amount of irony and ellipsis)
Odin is much like C (manual memory management, OOP not part of the plan, incredibly fast compile times, explicit over implicit, what you type is what you get sort of programming). Unlike C, it comes with a module system (yay!) and a rich set of intuitive syntactical sugar, turning the bread crust of a language that C is, into a sweet cheesecake without falling for the trap and baking a full-blown fattening cake (not sure where I’m trying to get with this analogy).
Anyhow. Solar Storm was a major inspiration. The game is awesome and the technical article was surprisingly enlightening. With all that being said, I’d like us to take a closer look at some of the core concepts behind the “engine” I put together (more of a chaotic framework, strictly speaking).
Kisa Code
You can take a look at the source code yourself. Let’s focus on the generic batcher abstraction, which powers most of the rendering anyways. What I realized while working on Kisa is that 2D games are pretty darn simple. A proper sprite batcher (as the one included with SDL2) can draw hundreds of entities with just a single draw call! Immediate mode APIs are a silver bullet.
// Overview of generic, immediate-mode batching
// Initialize memory and GPU buffer capacity
// A CPU array is also allocated (explanation will follow)
batcher_create()
while is_game_running() {
clear_screen()
on_scene_update()
on_scene_event()
// Delegate to scene rendering, which fills up the aforementioned
// CPU array of instances. Think of SDL_RenderCopy
on_scene_draw()
if len(batcher.instances) > 0 {
// This operation is expensive (CPU-GPU round trip)
send_cpu_batcher_data_to_gpu_buffer()
clear_cpu_batcher_data()
// Draw all instances
apply_pipeline(shader)
apply_uniforms(uniform_data)
draw_instances(len(batcher.instances))
}
}And that’s basically it! The sprite batcher is backed by a CPU array of instance data that gets dynamically filled and violently cleared after every single frame iteration. However stupid that might sound, it turns out incredibly efficient since it minimizes GPU trips all while offering an API that is elegant, beautiful, compact and concise.
This very same abstraction can be applied to most parts of the
engine, including font rendering, particles and shape rendering, among
others. Now, note that some visual components of the game, like the
tilemap or the swaying trees, have a static, constant appearance and
thus allow for a further optimization. We can utilize the exact same
abstraction (generic_batcher.odin) and just temporarily
discard all immediate mode fanciness. Clearing the instance buffer in
this case is foolish, since it will immediately get fed the very same
data. It would have been wasteful!
Border Generation
Tiles are grouped in clusters which form countries. I pulled much of my hair trying to figure out the most performant and scalable technique for drawing their borders, and I’m really happy with my solution. I initially considered performing some fragment shader wizardry but it turns out I’m too dumb for that. I ended up sort of vibe coding a Python script that, given a hand-drawn 6-element sequence of all sides of a hexagonal tile, produces a PNG file containing all 64 border combinations laid out in a convenient, two-dimensional order. Border data is static, so apart from some insignificant initialization work, rendering is instanced and lightning fast.
Recap and Gameplay Code
So, all you need is a generic batching abstraction, an immediate mode design and cross-platform shaders, the last one possible thanks to sokol and sokol-shdc (resembles GLSL in many ways, especially if you are keeping it simple like me).
What about gameplay code? Odin’s module system, enumerated arrays,
and the Maybe() construct make the process quite enjoyable!
As you might have noticed after browsing through the git repository, I
usually try to group code into independent pieces, each extending for
hundreds of lines, perhaps even a thousand (that’s where I usually draw
the line). Experience has shown me that hyper-modularization can
counter-intuitively damage readability and extensibility. Jonathan Blow
is right.
Thoughts on Game Development
Game development is definitely fun, but is it important? Are video games beneficial to society, or do their evil by-products and side effects end up causing more harm than good? Is there any point in publishing a stupid time killer like Kisa? Game development is certainly a form of art, and as such its power cannot be disputed. Having said that, let’s not forget that there’s just too much junk out there! Indie game developers, unlike their programming relatives who are more often than not subject to corporate law, have a lonely mission of designing bold, shocking creations infused with unprecedented meaning (television marketing voice).
This is just my take, you do whatever you want. Some may argue that video games are meant to be fun and only fun. This feels unjust to me: it effectively reduces yet another form of art into mere hedonism. In any case, there’s no one right answer here. If you’re in for profit go for something established. If you’re in for the sake of it, be eccentrically artistic.
Kisa was a technical exercise, a necessary prelude to something important.