Archive:

Subscribe:Atom Feed

"Eggnob"

Posted: 05 February, 2026

Ok, so this has been a thing, on and off, since I had the idea over Xmas. I need something to kick the tyres on the EH500. Flush out the bugs, and see if my QoL / ease-of-use assumptions actually play out in practice. A little game, basically.

Eggnob.

Yup, I'm going to keep the world's longest running joke, with the fewest people in on it, running for a bit longer and make a game entirely focused on killing that stupid f#cking egg. Which, for copyright reasons, is definitely not that egg. But if you squint hard enough, it might occasionally look a little bit like that egg.

So I wrote a full player controller over Xmas -- wall slides, dashes, jump buffering, variable jump height, corner correction -- and I've got a stupid egg running about.

Not dying, yet, but that'll happen soon enough.

Timelines

I wrote a timeline system for the Day Job, a few months back, and I've been using it in anger ever since. It's a little bare bones, but the reason I've enjoyed using it so much is because it's composable. You create building blocks -- tweens, delays, callbacks -- and snap them together.

Need a tween that waits half a second, animates over two seconds (with Easing?), then fires a callback when it's done? Build it like Lego:

EH_TimelineHandle aSeq[] = {
    EH_Timeline_Delay(0.5f),
    EH_Timeline_Tween(2.0f, MyCallback, pData, EH_EASE_OUT_QUAD),
    EH_Timeline_Call(OnComplete, nullptr)
};
EH_TimelineHandle hSeq = EH_Timeline_Sequence(aSeq, 3);

There's also YoYo, Reverse, and Parallel containers. All in all it ended up being a few hundred lines of code, and it's reasonably battle tested at this point, so I've done a quick port to C. I have a feeling this is going to be the perfect tool to kill eggs with.

TileD

The EH500 is hardcoded around TileD Json files. It automatically loads a tilemap for each level (one layer for the tile data, one layer for the collision data), and leaves the game free to decide what else it wants, from whatever other layers may be in the file.

I had a quick look over the weekend at how I'd place enemies, player spawn points, exits and stuff like that. I think, workflow-wise, it'll be reasonably quick. I can grab things out of the spawn layer by class, with a few baked in helper functions for the common bits and bobs.

EH_Vec2 vSpawn = {64.0f, 150.0f};
Spawns_GetPlayerPosition(&vSpawn);
Player_Init(&s_Player, vSpawn);

Nothing revolutionary, but it moves level iteration out of code. I can drag the player start around in Tiled, move enemies etc, and the triggers I mentioned in the previous post work the same way – define them in Tiled on their own layer, and the library picks them up.

Speaking of collision layers: turns out the engine was rendering them and I hadn't noticed. Derp.

Pixel Perfect Rendering

Despite being a "retro" fantasy console, the EH500 supports sub-pixel positioning. Internally, everything's floats. The problem is that the rendering pipeline doesn't composite to a single, fixed-resolution buffer, before scaling that up to the display. Everything renders directly through SDL's renderer, which means if a sprite's position is a gnat's testie off, it'll render a gnat's testie off.

Cecconoid rendered everything to a texture, then blitted that, stretched, to the display. Sub-pixel positions could exist (although, I quatised most of them), but by the time they hit the GPU, everything was smoothed out for free because of the low-res render texture. More info in this blog post

I may yet switch the EH500 over to this method, but atm the output pipeline composites (copper list, pixel buffers, tilemaps, sprites, text) at whatever position you give it. This is great for smooth scrolling, which is why I opted for it, but not so great when your player sprite sits at 100.7 and the tilemap collision is aligned to integer tile below.

Visual cracks, baby, and I had a couple. It looked particularly janky during wall slides, but the fix is dead simple: round the render position. Woo!

const float fDrawX = SDL_roundf(pPlayer->fPosX);
const float fDrawY = SDL_roundf(pPlayer->fPosY);
EH_DrawSprite(iSprite, EH_SPRITE_FRONT, fDrawX, fDrawY);

The egg's movement is slightly jerkier with this (you can see the pixel-stepping if you look for it), but the collision alignment, the thing you're most likely to see, is pixel-perfect.

Feels like the right trade-off for the time being, but lets see. I think, by the time I ship anything with this, I'll want to do Cecconoid's CRT and Bloom post processing, so I'll have to composite to a render texture to run the shader on it, at which point, none of this will matter very much...

Previous Post: "EH500 Single Header Libraries"
Lumo 2 Dev Journal

Musings, random thoughts, work in progress screenshots, and occasional swears at Unreal Engine's lack of documentation -- this is a rare insight into what happens when a supposedly professional game developer plans very little up-front, and instead follows where the jokes lead them.

Journal Index

Friends:

If you like any of my work, please consider checking out some of the fantastic games made by the following super talented people: