diff --git a/content/blog/six-months.md b/content/blog/six-months.md new file mode 100644 index 0000000..5022835 --- /dev/null +++ b/content/blog/six-months.md @@ -0,0 +1,277 @@ ++++ +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.png", 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.png", 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.png", alt="Very broken scene", caption="yeah I don't even know lmao") }} + +{{ image(src="/assets/6months/brokenrc.png", 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.png", 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.png", 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.png", 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! \ No newline at end of file diff --git a/content/blog/website-v3.md b/content/blog/website-v3.md index e1fc6a4..5fb6a17 100644 --- a/content/blog/website-v3.md +++ b/content/blog/website-v3.md @@ -11,5 +11,5 @@ Apparently I've become slightly obsessed with the color purple, so... yeah... th Other changes: - Added the [random stuff page](/stuff). - Added some 88x31s to the home page, because why not? -- Fixed the code block font and some other small things.\ +- Fixed the code block font and some other small things. - The [RSS (well, atom) feed](/blog/atom.xml) should finally work now. diff --git a/static/assets/6months/audio.png b/static/assets/6months/audio.png new file mode 100644 index 0000000..340908a Binary files /dev/null and b/static/assets/6months/audio.png differ diff --git a/static/assets/6months/brokenfox.png b/static/assets/6months/brokenfox.png new file mode 100644 index 0000000..a72903c Binary files /dev/null and b/static/assets/6months/brokenfox.png differ diff --git a/static/assets/6months/brokenrc.png b/static/assets/6months/brokenrc.png new file mode 100644 index 0000000..5801b26 Binary files /dev/null and b/static/assets/6months/brokenrc.png differ diff --git a/static/assets/6months/brokenui.png b/static/assets/6months/brokenui.png new file mode 100644 index 0000000..4d6d068 Binary files /dev/null and b/static/assets/6months/brokenui.png differ diff --git a/static/assets/6months/idk.png b/static/assets/6months/idk.png new file mode 100644 index 0000000..29b1a20 Binary files /dev/null and b/static/assets/6months/idk.png differ diff --git a/static/assets/6months/pig.png b/static/assets/6months/pig.png new file mode 100644 index 0000000..25f3929 Binary files /dev/null and b/static/assets/6months/pig.png differ diff --git a/static/assets/6months/triangle.png b/static/assets/6months/triangle.png new file mode 100644 index 0000000..123ba37 Binary files /dev/null and b/static/assets/6months/triangle.png differ