Devlog 2: Maps & movement
Happy January! I hope you’re having a cracking 2026 so far!
It’s Saturday morning, here. I’m curled up on the sofa with my laptop, a comically large mug of coffee, Lofi Girl on the TV, and I’m reflecting on the last few weeks.
It’s been fun! Painful, but fun!
The story so far
In the final weeks of 2025, I laid out some plans for a platformer-Metroidvania codenamed Model of the New Star built in a custom engine developed in MonoGame.
By the start of 2026, I had the guts of an Entity Component System architecture that could slide 50,000 entities randomly around the screen at 60 fps.
And now, the engine’s got maps, collisions and movement! 🎉
Maps
I’ve been building maps in Tiled for years, and I love it! It’s a bit rough around the edges to be sure, but it’s rock solid and I’ve built many good maps in there.
For this project, though, I thought I’d try something new. Enough folks have recommended LDtk that I figured I’d give it a shot.
I’m still just greyboxing (or, in this case, greenboxing) a world to experiment with, but here it is so far:

Resolution
I haven’t settled on a native resolution yet, but I’m experimenting with 480×270.
320×180 is such a classic resolution, with Celeste, Animal Well and Pipsqueak absolutely rocking it, and I probably would’ve settled on it by default not for Scourgebringer.
As best I can figure out, Scourgebringer runs at 480×270. It’s still got those crunchy pixels, but you can fit a few more of them on-screen than 320×180. 640×360, of course, can fit a lot more pixels, but there’s no crunch. I want crunch.
The major downside of 480×270 vs 320×180 is that it doesn’t scale to 720p or 1440p – but I’m not terribly worried about that right now. I’ll experiment later, and I’m sticking with 480×270 for now.
480×270 is also the size of each “room” in the map. The rooms don’t have to be that size; it’s just a helpful guide for me to know how much would be visible at a time.
Binary format
One thing I mentioned in my blog post about textures was my plan to make the game an open world, where textures are swapped in and out without transitions:
No transition between rooms, no fade to black between biomes; just a single world that swaps textures in and out of the GPU without dropping any frames.
So, just like the binary format I built for streaming textures, I built a compiler that validates and squishes LDtk’s JSON files into minimised binary blobs that stream in and drop out of memory as the player moves around.
Each “room” is a standalone file, and the engine keeps track of a sliding window of nine chunks surrounding the centre of the view – though the in-memory cache will likely be much larger when I’m ready to really think about it.
Using LDtk
After years of using Tiled, LDtk’s a learning curve for sure.
I won’t mince words: I’m struggling.
The UI doesn’t come naturally to me. Laying down tiles works about 80% of the time. Removing tiles works less than half the time, but improves if I take my finger entirely off the trackpad and count “one mississippi” between each tap. Tiles appear and disappear as I zoom in and out. The quirks are so odd that I know I must be using it wrong, but my brain’s too goofed to figure it out.
I did consider switching back to Tiled as soon as the frustration hit, but nah: I’m going to stick with it. If the app’s the problem, I’m sure it’ll be fixed. And if I’m the problem, I’m sure I’ll learn.
But mostly, after spending a week writing the map compiler, I really don’t fancy going back and patching it for a different source file format. It was fun enough, but I’d rather move on than retread old ground.
So, I’m going to stick with LDtk. I wonder if a year from now, when the engine’s done and I’m building the world, if I’ll look back and wish I’d shouldered the pain of switching? But, ah, that’s a problem for future-me.
Collisions
Ah, collisions.
Collisions.
I must’ve built a dozen collision detection frameworks over the years by now, and I fall into the same trap every single time.
“It’s just intersecting rectangles!” I say.
“Just axis-aligned bounding boxes!” I convince myself.
“Sure, last time it made me cry and swear off game development for a month,” I admit.
“But I’m smarter now. More mature. And with hindsight, I see now how I’ve always overcomplicated it in the past,” I murmur with a sagely nod.
“This time,” I announce with my head held high, “will be different.”
But, dear reader, it’s never different.
It always starts with optimism. And, I’ll admit, a hint of naivety. The first solution always kinda works, but characters sometimes stick on the floor. I look into it, and the collider area’s a bit too big because of a dodgy float-to-integer conversion. No worries. A quick fix. But now the player’s a pixel into the ground rather than sitting on top of it. What’s up with that? Oh, I’m rounding the integers the wrong way. Right… but now I walk a pixel deep into walls. That’s because only some of the integers were rounding the wrong way. No great shakes, but now the code’s a bit of a mess, so let’s clean it up a bit. Nice! But now, if I jump against a wall, I stick in it. Wait – did I test that before? Is that caused by my cleanup, or was it always there? Is it worth reverting? Nah, let’s just look into it. Ah, it only happens when the floating-point position just happens to be a perfect integer at the point of rounding. In that case, it needs to round up. Or should it be down? Is this even the right fix? Let’s get rid of some of these functions and start again from first principles. Ah, I see where I’ve made bad assumptions! Let’s iron those out… and now the character is constantly offset by 5×7 pixels from where they should be. And every time they touch a wall, they ping off diagonally like Team Rocket blasting off again. Huh? Let’s get rid of some more functions and start again with fresh eyes. And again. And again. And again.
And boy oh boy oh boy, I truly don’t understand the mental block I have with collisions. I’m not the best mathematician in the world, but surely I’m above average? Why can everyone else spit it out in a day and I struggle for weeks?
One little lifestyle change I made last week, to find more time for development, was to start taking my game development laptop to work. I tend to get in a few hours early to get a head start on the day, but I decided to claim back some of that personal time to work on this project instead. But this was the worst week to start that plan. I imagined I’d have JetBrains Rider up and not attract too much attention, but collision work is all playtesting, and I was far too self-conscious to do that in an office of curious colleagues.
In the end, I don’t know if the collision work is done. The best I can say is: I don’t currently know how to break it.
Movement
Right now, the movement is absolutely for function rather than playing. There’s no coyote time or jump buffer, and the acceleration is a bit unpleasant – but it’s good enough for testing collisions, and there’s certainly time for tuning!
In the Entity Component System framework now, I’ve got:
- A Player Desired Movement system that converts the player’s input at the current moment in time to a Desired Movement component.
- A Gravity system that adds downward force to Velocity components.
- A Velocity system that converts Desired Movement values to forces and adds them to Velocity components.
- A Movement system that resolves Velocity components and collisions to final Position component values.
- A Draw system that paints textures at Position component values.
In the future, non-player characters will have some kind of Goal-Oriented Desired Movement system that updates Desired Movement components according to how they want to move – then, since all the other systems are decoupled from the player, they should all continue to work regardless of who’s in control.
And certainly in the future, I’m going to rename some of these systems. I don’t love that I have multiple systems that affect velocity, and one of them is called “Velocity”. There are better names for sure, but sometimes it’s easier for knowledge to emerge than be forced up-front.
Next steps
Before I work on adding coyote time and the like to the movement systems, I think I’ll work on the map and camera.
In theory, the engine already supports the streaming in and out of map chunks, but there’s no camera to follow the player or trigger any of that streaming.
So, I don’t want to set any plans in stone, but I think I’ll expand this testing map out to 5×5 rooms to prove the streaming and tune the camera’s offsets for following the player while they’re moving.
And if that goes well, my stretch goals are to mark-up some special camera actions in the map. For example:
- When the player stands on a high ledge, have the camera dip down a bit to help them see where they’ll land.
- When the player walks through a tunnel, tower or arena, lock the camera’s boundaries.
Until then… yeah, the last few weeks have been pretty painful, and watching a little yellow box bounce around doesn’t feel like the greatest achievement, but I do feel like some of the harder stuff is behind me for the time being – and that feels pretty good!
I hope you’re all having a great 2026 so far! ❤️