+++
title = "Game Progress Log - Six Months"
date = 2025-04-18
+++

{% important() %}
This is more of a personal progress log than a blog post.
I wrote it more for my future self than anyone else, though you're obviously welcome to read it if you want.
{% end %}


## Background
Despite what I sometimes like to pretend, I'm not really a game dev. 
I've created and deleted my fair share of Godot and Unity projects, I've messed around with [libGDX](https://libgdx.com/), I've completed three game jams (with varying degrees of success)
but I've never actually made anything I'd consider *finished*. 
I'm great at starting projects but terrible at completing them.

A little over a year ago, I tried following the [learnopengl](https://learnopengl.com/) tutorials, and had a lot of fun doing so.
I got as far as a pig in a spotlight before bad design decisions and my inexperience with C++ caused the project to collapse under its own weight.

*Maybe in retrospect trying to learn graphics programming and C++ at the same time was not the best idea*...

{{ image(src="/assets/6months/pig.webp", alt="Minecraft pig model drawn in front of a space-themed skybox", caption="piiiiiiiggggg") }}

Fast-forward to 8 or so months ago, and I finally thought up a game idea I thought was worth persuing.
Something I could stay motivated to work on in the longer-term, and hopefully stick with long enough to complete.

I really want to love [Godot](https://godotengine.org/).
It's my go-to choice for game jams, but once a project hits a certain size I can't figure out how to structure it.
And I guess I just sort of got bored, or something. 
I made a little prototype, ran into some weird Godot limitation I no longer remember, and thought "hmm, what if I did this myself."

## Stuff
So, for the last six months I've been working on a little game framework thingy in my free time.

[SDL3](https://libsdl.org)+[Kotlin](https://kotlinlang.org) is a bit of a cursed combination,
but Kotlin is my current favorite language and I wasn't going to repeat the mistake of using a language I'm not comfortable with. 

When I started, [LWJGL](https://github.com/LWJGL/lwjgl3) didn't have bindings for SDL (though [it does now](https://github.com/LWJGL/lwjgl3/pull/1033)) so it wasn't an option.
[jextract](https://github.com/openjdk/jextract) works well enough for my purposes, since I was going to wrap most things in Kotlin anyway.

The first triangle was fairly straightforward.

{{ image(src="/assets/6months/triangle.webp", alt="A window displaying a rainbow triangle", caption="hello triangle") }}

And I guess it just spiraled out of control from there. Instead of making a game I've been making a game framework.

I find the SDL GPU API (~~wow that's a lot of caps~~) to be a lot easier to reason about than OpenGL was.
Trying to learn OpenGL was difficult because everything was State Soup and some totally unrelated code somewhere else 
would interfere with whatever I was trying to do, because I wasn't being careful enough.

At least with SDL I have some idea what's actually going on.

{% note() %}
The lack of tutorials for SDL GPU was a little bit frustrating.
Being extremely new, there's not much to go off other than [some examples](https://github.com/TheSpydog/SDL_gpu_examples/blob/main/Examples/ComputeSpriteBatch.c#L305)
and the (actually fairly decent) [documentation](https://wiki.libsdl.org/SDL3/CategoryGPU).

Also, [RenderDoc](https://renderdoc.org/) my beloved...
{% end %}

So let's see, after 6 months what have I got...

### Sprite Batching
Most everything is based around a compute-shader based sprite batcher. 
It was loosely inspired by [this example](https://github.com/TheSpydog/SDL_gpu_examples/blob/main/Examples/ComputeSpriteBatch.c#L305).
I don't have a great way to benchmark performance, but it handles tens of thousands of sprites with no problem on my laptop, 
so it's more than fast enough.

It does have the annoying limitation that all sprites must come from the same atlas texture, and it won't automatically batch sprites from a bunch of different ones.
I should probably change that at some point.

### Box2D
I didn't really want to write my own physics. 
Thanks to [Box2D](https://box2d.org/) 3.0+ being a C library, it was pretty easy to generate bindings for it.

Unfortunately there's now a `conversions.kt` file that looks like
```kotlin
internal fun AABB.b2AABB(arena: Arena): MemorySegment {
	val a = b2AABB.allocate(arena)
	b2AABB.lowerBound(a, this.min.b2Vec2(arena))
	b2AABB.upperBound(a, this.max.b2Vec2(arena))
	return a
}
```
just to convert to and from Box2D's types, but eh, it's fine. 
I wrapped the callbacks with my own event system and it seems to be working well.

### Lighting Experiments
I am *very* new to graphics programming, all things considered, and trying to implement [radiance cascades](https://radiance-cascades.com/) was probably ill-advised.

I did get some cool screenshots before I realized my game wouldn't even benefit from this kind of lighting in the first place.
I probably could have figured it out if I'd spent a little more time, but I was getting bored.

{{ image(src="/assets/6months/idk.webp", alt="Very broken scene", caption="yeah I don't even know lmao") }}

{{ image(src="/assets/6months/brokenrc.webp", alt="Very broken scene", caption="significantly closer") }}

{% note() %}
It wasn't helped by the fact that my test scene was almost entirely text and thin debug lines left over from messing with Box2D.
Even a correct RC implementation would probably struggle with that.
{% end %}

I might revisit this later. We'll see.

### 3D Experiments
After so much 2D stuff I was getting a bit bored. 
My game is 2D, and there's no way I'll be writing a competent 3D engine, but I wanted to mess around a little. 
If I could draw the pig with OpenGL, maybe I could do something similar with what I'd built.

It went... about as well as could be expected.

{{ image(src="/assets/6months/brokenfox.webp", alt="A fox with extremely broken textures", caption="biblically accurate fox or something idk") }}

I did eventually unbreak the fox and get a cubemap skybox mostly working.

It was pretty fun overall, even if not *useful*. 
I learned about [gamma correction](https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/) 
and some [neat tricks](https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/) for fullscreen render passes, so it wasn't a complete waste of time.


### Audio
I initially tried plugging an opus decoder more or less directly into an `SDL_AudioStream`, but it turns out that was a bad idea.
So for now, I'm using [miniaudio](https://miniaud.io/).

{{ image(src="/assets/6months/audio.webp", alt="debug ui for audio, with volume, pitch, and pan options", caption="little sound ui thing I made with Dear ImGui") }}

It supports basically everything I want to do anyway, like audio streaming, looping, pitch and pan modification, and more.

Getting it to compile was bit of a headache because it's not designed to be built as a shared library, but I don't exactly have any other option to use it from Kotlin. 
CMake makes me go insane...

{% note() %}
I've been using stuff from [the FTL soundtrack](https://benprunty.bandcamp.com/album/ftl) and [ESCISM](https://radicaldreamland.bandcamp.com/album/escism-esc-original-soundtrack) 
as test tracks, which are therefore forever burned into my head. There are worse problems to have.
{% end %}

### User Interface
I know basically nothing about UI, and trying to research how other people handle it proved kind of useless.

"Just draw a bunch of textured rectangles! It's easy!"
Yeah, okay, but where and what and why and aaaaaaaaa I don't even know.
I was almost tempted to try to use [Dear ImGui](https://github.com/ocornut/imgui) for game UI, but the lack of theming made that impractical.

I figured drawing [9-slices](https://en.wikipedia.org/wiki/9-slice_scaling) would be a good place to start, using the sprite batch code I'd written earlier.

{{ image(src="/assets/6months/brokenui.webp", alt="Extremely broken 9-slice drawing", caption="Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") }}

I decided to try to decouple the layout and the functionality a little, keeping the logic in Kotlin and the layout in [HOCON](https://en.wikipedia.org/wiki/JSON#HOCON). 

Why HOCON? I don't know. The syntax is nice and [Hoplite](https://github.com/sksamuel/hoplite) provides nice error messages when I mess something up.

It looks like this:
```js
{
    // some stuff (theme declarations, etc.) omitted for brevity
    rootElement = {
        type: PaddingContainer
        padding: {
            left = 30f
            right = 30f
            top = 20f
            bottom = 20f
        }
        child = {
            type: HorizontalListContainer
            children = [
                {
                    type: SizeConstraintContainer
                    max_width = 100f,
                    max_height = 100f,
                    child = {
                        type: NinePatchContainer
                        id: "test-button-1"
                        child = {
                            type: Label
                            font_size = 48f
                            text = "1"
                        }
                    }
                },
                {
                    type: SizeConstraintContainer
                    max_width = 100f,
                    max_height = 100f,
                    child = {
                        type: NinePatchContainer
                        id: "test-button-2"
                        child = {
                            type: Label
                            font_size = 48f
                            text = "2"
                        }
                    }
                }
            ]
        }
    }
}
```

There are lots of other container types like `AlignmentContainer`, `ClippingContainer` and I'll probably need to add a few more,
like `ScrollContainer`.

The Kotlin side looks like this, where the `ButtonBehavior` handles mouse presses and the hovered/pressed/released state.
```kotlin
val gui = Gui(Game.colorTarget, Core.gameDir.resolve("assets/test_ui.conf")).apply {
	addBehavior(getElementById("test-button-1"), ButtonBehavior().also {
		it.onMousePress += {
			println("Button 1!")
		}
	})

	addBehavior(getElementById("test-button-2"), ButtonBehavior().also {
		it.onMousePress += {
			println("Button 2!")
		}
	})
	show()
}
```

I don't think this will scale particularly well, but my game is fairly UI-light so it should be Fineeee™ for now at least.
It's significantly better than just spamming `Batch.draw()` a bunch.

I don't know how I'm going to handle more complex UI elements, like scroll bars and tab groups. HOCON makes it easy to include 
other files with `include required(file("/path/to/thing.conf"))` so maybe I could try to make some templates for things like that.

One of the hardest parts was dealing with `Batch`'s aforementioned everything-must-be-from-the-same-texture limitation.
For now, I'm just generating a separate texture atlas for each UI at runtime, which doesn't feel great.

Text is handled through [SDL_ttf](https://github.com/libsdl-org/SDL_ttf)'s GPU text engine, which does most of the hard work with FreeType and HarfBuzz for me.
All I have to do is keep track of different font styles.
I tried using FreeType directly, and just about went insane on the spot.
Text rendering [is not a rabbit hole I want to go down](https://faultlore.com/blah/text-hates-you/), and this is probably good enough
for everything I'll want to do. 

### Shader Hot-Reloading
There's not much to say about it, other than that it works!
It took too long to get the file watching working properly because Java's `WatchService` is a little weird.

In the future I want to be able to hot-reload textures, though that's a little harder because the texture atlases would have to be rebuilt.

### Misc
There are some other things that I worked on that aren't really worth their own sections, like:
- An overcomplicated asset management system
- Remappable keybind handling
- Most of a [glm](https://github.com/g-truc/glm)-esque math library from scratch

## The Future
This is now my biggest project ever in terms of LoC, which is ...concerning? ...fun?  I don't know. Let's go with [fun](https://dwarffortresswiki.org/Fun&redirect=no).

I'm sure any professional engine devs are... what's the equivalent of "rolling in their graves" for people who are still alive? 
Whatever. That. They're probably doing that.
I don't really know what I'm doing, and I guarantee there are a bunch of horrible design decisions that will come back to bite me later.
To be honest though, I don't really care. I'm having fun and learning a lot, which is what I set out to do.

Once I get a little further along, I want to make the core framework open-source. 
Not because I think anyone should use it, (please no, spare yourself) but because I wouldn't have gotten nearly this far without all the example code on the internet,
and if my project can be of any help as a reference for someone else, I'd like that.

I've experimented with adding the [Fleks](https://github.com/Quillraven/Fleks) ecs (not to be confused with [flecs](https://github.com/SanderMertens/flecs)) and I've been very happy with the results so far.
It seems like a good way to structure the game itself, though I'm not quite sure how to cleanly tie Box2D's physics in.

Here's some things I might work on soon:™
- Shader caching? (probably not necessary, but might be fun to do)
- Getting it running on Windows (90% done already, shouldn't be too hard)
- Basic [i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization) maybe
- Despaghettify the UI code
- Figure out a name for this mess (I'm terrible at naming things)
- **Start working on the actual game!**

That last one is the main thing. It's about time I stop messing around and start actually making my game.
If I want to make the core framework better, I have to try using it, and see what walls I run into.
I'm sure there will be plenty.

Anyway, that's it for now, see you in another six months!