Compare commits

...

3 commits

Author SHA1 Message Date
b06499d7a2
6 months post 2025-04-18 08:53:34 -06:00
9ee8582390
update + some 88x31s 2025-04-17 22:16:09 -06:00
3b6e441d48
fix atom feed 2025-04-17 21:46:59 -06:00
38 changed files with 423 additions and 16 deletions

View file

@ -4,8 +4,9 @@ base_url = "https://lynxize.dev"
compile_sass = false
build_search_index = false
generate_feeds = false # need to configure it to be at /blog/atom.xml which takes Work(tM)
generate_feeds = false # set to true in the blog _index
author="lynxize"
[markdown]
highlight_code = true

View file

@ -22,7 +22,7 @@ You can find the source for this site [here](https://git.lynxize.dev/lynxize/web
You can find me in the following places:
- Discord - `@lynxize`
- Bluesky - [`@lynxize.dev`](https://bsky.app/profile/lynxize.dev)
- GitHub - [`@lynxize`](https://github.com/lynxize)
- GitHub - [`lynxize`](https://github.com/lynxize)
You can also reach me [through email](mailto:lynxize@gmail.com), though response times will definitely be slower.

View file

@ -3,4 +3,8 @@ title = "Various Ramblings"
sort_by = "date"
template = "blog.html"
page_template = "post.html"
generate_feeds = true
+++
Some random blog posts/ramblings/text walls of mine.

277
content/blog/six-months.md Normal file
View file

@ -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!

View file

@ -0,0 +1,15 @@
+++
title = "Another Redesign"
date = 2025-04-17
+++
I feel like I just did this [a few months ago](/blog/website-v2).
I didn't change as much this time, mostly just the CSS.
Apparently I've become slightly obsessed with the color purple, so... yeah... the whole site is purple now.
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.
- The [RSS (well, atom) feed](/blog/atom.xml) should finally work now.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

View file

@ -0,0 +1,9 @@
yeah to be honest I have no idea where most of these come from
I grabbed a few from aggregator pages like:
https://cyber.dabamos.de/88x31/index.html
https://www.88x31.nl/index.html
https://capstasher.neocities.org/88x31collection-page1
etc.
If you really care about proper attribution for every single 88x31, go find a better hobby/hyperfixation please.
HOWEVER if you made one of them and want credit (or for me to remove it) just let me know!

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
static/assets/88x31/sdl.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -8,7 +8,8 @@ Not AI though, just me! :3
:root {
--color-accent-1: #af8cff;
--color-accent-2: #77aef2;
--color-accent-2: #74cded;
--color-links: #77aef2;
--color-note: var(--color-accent-1);
--color-warning: #ed9a5f;
@ -77,7 +78,7 @@ header {
}
a:hover {
color: hsl(from var(--color) h s 30%);
}
a {
@ -120,12 +121,22 @@ footer {
color: var(--color-accent-2)
}
a:link, a:visited {
code {
font-family: "JetBrains Mono", monospace;
font-size: smaller;
}
/* Color only inline code blocks */
code:not([class^=language-]) {
color: var(--color-accent-2);
}
a:link, a:visited {
color: var(--color-links);
}
a:hover {
color: hsl(from var(--color-accent-2) h s 85%);
color: hsl(from var(--color-links) h s calc(l + 10));
}
}
@ -150,6 +161,13 @@ footer {
background-color: hsl(from var(--color-important) h 35% 15%);
}
#post-list {
color: var(--color-accent-1);
}
hr {
color: var(--color-accent-1);
}
blockquote {
border-left-width: 4px;
@ -172,7 +190,10 @@ figure {
figcaption {
width: 100%;
text-align: center;
margin: 5px auto;
margin: 5px auto 20px;
color: var(--color-accent-2);
font-size: smaller;
font-family: "JetBrains Mono", monospace;
}
/*

49
templates/atom.xml Normal file
View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ lang }}">
<title>{{ config.author }}</title>
{%- if config.description %}
<subtitle>{{ config.description }}</subtitle>
{%- endif %}
<link rel="self" type="application/atom+xml" href="{{ feed_url | safe }}"/>
<link rel="alternate" type="text/html" href="
{%- if section -%}
{{ section.permalink | escape_xml | safe }}
{%- else -%}
{{ config.base_url | escape_xml | safe }}
{%- endif -%}
"/>
<generator uri="https://www.getzola.org/">Zola</generator>
<updated>{{ last_updated | date(format="%+") }}</updated>
<id>{{ feed_url | safe }}</id>
{%- for page in pages %}
<entry xml:lang="{{ page.lang }}">
<title>{{ page.title }}</title>
<published>{{ page.date | date(format="%+") }}</published>
<updated>{{ page.updated | default(value=page.date) | date(format="%+") }}</updated>
{% for author in page.authors %}
<author>
<name>
{{ author }}
</name>
</author>
{% else %}
<author>
<name>
{%- if config.author -%}
{{ config.author }}
{%- else -%}
Unknown
{%- endif -%}
</name>
</author>
{% endfor %}
<link rel="alternate" type="text/html" href="{{ page.permalink | safe }}"/>
<id>{{ page.permalink | safe }}</id>
{% if page.summary %}
<summary type="html">{{ page.summary }}</summary>
{% else %}
<content type="html" xml:base="{{ page.permalink | escape_xml | safe }}">{{ page.content }}</content>
{% endif %}
</entry>
{%- endfor %}
</feed>

View file

@ -20,6 +20,7 @@
<a href="/blog">blog</a>
<a href="/stuff">stuff</a>
</nav>
<link rel="alternate" type="application/atom+xml" title="RSS" href="/blog/atom.xml">
</header>
<div class="content">

View file

@ -4,9 +4,10 @@
<h1 class="title">
{{ section.title }}
</h1>
<ul>
{{ section.content | safe }}
<ul id="post-list">
{% for page in section.pages %}
<li>{{ page.date }} <a href="{{ page.permalink | safe }}">{{ page.title }}</a></li>
<li>{{ page.date }} - <a href="{{ page.permalink | safe }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
{% endblock content %}

View file

@ -3,15 +3,44 @@
{% block content %}
<br>
Hey! You found my little personal page!
I'm a professional waster of time, purveyor of useless activities, and chronic avoider of productivity.
Hey!
<br>
You found my little personal page!
<br>
<br>
I'm an expert waster of time, enjoyer of useless activities, and chronic avoider of productivity.
<!-- br brr brrrrr (bad) -->
<br>
<br>
I also sometimes write bad code for fun and play games.
<br>
<br>
<a href="/about">More about me</a>.
<br>
<br>
Since I don't know what else to put here and have to fill the space somehow, here's an 88x31 dumping ground, because why not?
<br>
<hr>
<div>
<img src="/assets/88x31/catview.png">
<img src="/assets/88x31/gnu-linux.gif">
<img src="/assets/88x31/ieburnbtn.gif">
<img src="/assets/88x31/javascript-zero.gif">
<img src="/assets/88x31/cssdif.gif">
<a href="https://catsta.red"><img src="/assets/88x31/squishstare.gif"></a>
<img src="/assets/88x31/msoffice97.gif">
<a href="https://www.thunderbird.net/"><img src="/assets/88x31/thunderbird.gif"></a>
<a href="https://archlinux.org/"><img src="/assets/88x31/arch.png"></a>
<a href="https://jellyfin.org/"><img src="/assets/88x31/jellyfin.gif"></a>
<img src="/assets/88x31/right2repair.gif">
<a href="https://wikipedia.org"><img src="/assets/88x31/wikipedia.png"></a>
<a href="https://www.libsdl.org/"><img src="/assets/88x31/sdl.gif"></a>
<a href="https://librewolf.net/"><img src="/assets/88x31/anythingbut.gif"></a>
<img src="/assets/88x31/nocookie.gif">
<a href="https://www.youtube.com/watch?v=cErgMJSgpv0"><img src="/assets/88x31/webvirus.gif"></a>
<a href="https://librewolf.net/"><img src="/assets/88x31/firefox.gif"></a>
<a href="https://adoptium.net/"><img src="/assets/88x31/java.gif"></a>
<a href="https://store.steampowered.com/"><img src="/assets/88x31/steam.gif"></a>
<a href="https://ublockorigin.com"><img src="/assets/88x31/ublock.png"></a>
</div>
<p style="font-size: x-small">(If one of these is yours and you want credit, let me know. I just found them Wherever™)</p>
<hr>
{% endblock content %}