A Decade of Game Development (Kisa Technical Overview)
29 of August 2025 (Home)
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. I won’t delve into the nitty gritty details, but I successively experimented with Scratch, Game Maker, Unity, Roblox, Scratch (yep, again) and Godot. In recent years, I grew disappointed of game engines and my last verdict was in favor of SDL, which effectively coerced me into improving my ability to abstract and treat games as code and just that.
Fast forward to the last three years of my life, I ended up releasing four quick games written in C and SDL. My choice was influenced by my cheap hardware (long compile times were brutal), a choice however that grew into a habit even long after I had replaced my malfunctioning laptop. 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
(shader)
apply_pipeline(uniform_data)
apply_uniforms(len(batcher.instances))
draw_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.
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.