Working on a little 3D racing game called Trackminia

Different wall-heights inside one level would be tough due to the rendering method (the walls are "Wolfenstein mode" so each wall Must Always block your view of all walls behind it) but it does seem worthwhile to find ways to get some visual variety between tracks...will give that some thought for sure! probably different sky/floor/wall patterns, at least.

Seems worth a try, though as it stands the floors and sky are more expensive to draw than the walls...so I'm a little worried that it'd put me over the performance budget even if there weren't any other decorations. It'd be nice if I could get away with something like "highway guardrails" that you could see over, to get a better view of the upcoming track...but honestly I doubt I'll be able to afford it! Will have to try some stuff to be sure, though.

WELL this is interesting - individual pixels of the floor/sky are more expensive than the walls, BUT the walls have a bunch of other overhead associated with them (most notably, 400 raycasts per frame to do the wolfenstein-thing, and also all mesh-renders have to check for wall-occlusion). That overhead is way more notable than I expected (because a lot of it gets hidden inside other functions)! With no walls at all, I get back to 50 fps when the player is alone, or 45+ fps with the player and four ghost cars visible.

So...it seems like it may actually be possible to do mesh-based walls, after all! This would give me a ton of new options - like different wall types for different maps (or different parts of the same map), and potentially also extra decorations outside of the track. Seems likely that I'll need some LOD management (particularly for walls, but maybe also for cars and other decorations?), but I think the harder part will be getting everything to sort in a correct-looking way for the painter's algorithm, since I doubt that I can afford a full z-buffer.

Diving headfirst into Mesh-Walls Land by trying some highway-style barriers. Added some basic LOD (it's visible in the clip, since the LOD0 range is pretty short), but I haven't done any sorting yet, so you can see some painter's-algorithm-mistakes in the video.
2024-06-0320-45-09-ezgif.com-optimize

Performance seems viable so far, and I've got a whole new category of possible optimizations to scrutinize now, lol (how many segments per wall, how many triangles per segment, what LOD settings, and so on). A fun side effect is that walls can finally cast shadows, which I think is sorta hilarious in a handheld game like this...though maybe that'll turn out to be too expensive eventually. But hey, for now, a man can dream.

The main downside so far is that this adds some new legibility concerns, so I'll need to fiddle with the wall meshes for a while to make sure that it's easy to tell when a turn is coming up.

Ah yeah, higher camera (and maybe different FOV?) seems like it would help!

Different wall-types in the same map are definitely possible now, yeah! Little worried about trying a city environment, partly because it'd probably want a lot of mesh assets, but mostly because the game is already inviting comparisons to P-Racing and I don't wanna push that any more than I need to...

Here's a recording from the simulator (also shows the wall-pieces getting sorted properly):
playdate-20240604-182531

Unexpectedly, this gif looks worse than the earlier ones, but only when it's embedded and playing...if you pause the video, you get a cleaner looking screenshot, and if you open the gif in a new tab, the whole video looks better again

(EDIT: now the gif's images look cleaner, but it seems to play at a lower framerate than before? dunno wut's going on)

Been doing a lot of visual improvements and optimizations lately...here's a new clip with a bunch of new decorations!

2024-06-1019-26-09-ezgif.com-optimize (1)

I had a really unfortunate realization recently: there are secretly two different versions of the Playdate ("Rev A" and "Rev B"), and the one that I own (Rev B) is slightly faster than the other one. This means...even if the game runs at 30fps on my device, it might run at lower than 30fps on someone else's device. This is no good! I don't want people to find out from my game that they have "the slower version" of the machine! To sidestep that, I've been optimizing the game beyond 30fps on my own device, but locking the game to 30fps max anyway. This way, everybody gets the exact same experience, but if you're lucky enough to have a Rev B device, then the game will use a bit less battery power (since the CPU has more idle time during gameplay). According to testers with a Rev A, I've hit the target, at least for now!

The biggest optimizations here are about the ground and skybox: the main thing I've learned about Playdate performance when doing per-pixel rendering is that you shouldn't do it. Instead, you want to be blitting strips of pixels all at once - and now everything in this game can do that! The triangle rasterizer and ground-renderer can blit up to 32 pixels at a time, while the skybox blits an entire 400-pixel row of the screen in one memcpy() call.

Anyway, if I'm really lucky, this might even give me some more headroom to add even more decorations!

Looks amazing! Maybe it’s explained somewhere, but I wonder why the simulator can’t do a better approximation of the actual performance, especially considering there’s two revs that run at different speeds.

Some nice visual changes lately - first of all, a new car and a closer camera angle:

2024-06-1122-01-01-ezgif.com-optimize (1)

Additionally, a second environment which isn't quite done yet, but is coming along - a track inside an underwater glass tunnel:

2024-06-1521-56-39-ezgif.com-optimize

It's a little tough to see in that clip, but the skybox and the meshes outside of the tube get some realtime distortion to make things feel more underwatery. For performance reasons, this is a per-scanline effect (which was popular back in the day!).

And finally, I've been adding some music and sound effects. I can't share those in a gif, and unfortunately the mastodon server I use seems to be down right now, so the best I can do is this twitter link.

I ended up implementing an ADPCM encoder/decoder from scratch because I was having some trouble with the Playdate SDK there...FileLoader played my sounds correctly, but it cost too much performance to do the streaming/decoding, but then when I tried to pre-load the music into an AudioSample, it failed to load at all - I assume I was just doing something wrong, but I tend to punt early on 3rd party utilities because I find it more fun to learn about the problem instead of learning about some particular API. Anyway, the game can now hold three songs in memory at all times, so when one song ends, it can jump right to the next song for free - so it's safe to do that in the middle of gameplay, and the game can basically loop a small playlist continuously.

Up next...probably another round of optimization, lol. The game still runs nicely on a Rev B, but it's back around the threshold where Rev A won't maintain 30fps all the time, so...once more, into the breach.

Behold, fish!
therefore Time for fish

2024-06-1723-07-29-ezgif.com-video-to-gif-converter

Wow, you’re a magician! This is looking more and more amazing.

Although, strictly speaking, it makes no sense for shapes outside a rigid glass tube to appear wavy, when there’s no moving water surface.

It looks like you switched back to a tiled blue noise texture, was that an optimization?

Yeeeep the distortion is kinda nonsensical in this situation, but I figure it helps to sell the "underwater" vibe, and also looks sorta "high tech" (despite being an oldschool rendering effect), so I'm okay with it!

And yeah, tiled noise lets me get away with some sneaky optimizations - for example, triangles are more likely to get cache-hits when sampling the pattern across multiple scanlines. A weirder one is the skybox: to avoid per-pixel sampling when drawing the sky, I generate several full-size ("all the way around," much wider than the screen) variants of the skybox at 1 bit per pixel, with the dithering baked in - each of these variants has the sky details scooted in one pixel increments, but the dither pattern always stays put. By doing this, I can blit a screen-width slice of one of those variants directly to the screen (if the skybox wants to be rotated by 7 pixels, I fetch the "scooted 7 pixels" variant of the skybox). By using a 32-wide dither pattern, I can stop at 32 of these skybox variants, because once you've scooted over by a full dither-pattern, you can go back to the first variant and sample each scanline of the texture from a different start-point (32 pixels over), and you'll still get the dithering locked in screenspace (since the dither pattern always repeats after 32 pixels). This sounds wacky (and it probably is) but it's much less memory/baking than doing "store a 400x120 skybox variant for each possible skybox rotation" (smaller individual variants, but it would need like 2,100 of them!), and it's much faster to draw than the original per-pixel sampling (which requires no variant-baking, but needed 48,000 texture reads per frame, compared to the current 120 memcpy calls).

Been working on a third environment (though I think it'll be the second in the final game's level-progression) - a weirdly-tall bridge, way up in the sky.

2024-06-24_01-28-32-ezgif.com-optimize

This one involved a fun bit of new tech: you don't get a great view of it in the clip above, but the skybox in this area is fullscreen so that it can include terrain far below the track. This terrain is pre-rendered like the rest of the skyboxes, but it's using some 3D projection to do that, so I'm hoping it might trick some people into thinking it's more mode7 stuff (like the ground in the other areas).

Speaking of which - this area can't use mode7 for the track, since it wants to have smooth edges around the track's bends (and my mode7 renderer would give ugly stair-steppy aliased edges in those areas). Instead, it generates reusable track-meshes based on the curves present in the map - one set for the drivable surface, and another set for the vertical edges below the track. It's important for these to be separate, because we need to make sure that all "below track" content gets drawn before any track pieces (and all track pieces need to be drawn before any above-track objects, like walls, the car, and the nearby clouds). The large support beams are regular pre-authored meshes from blender, like most of the other meshes in the game. As a cheeky performance gambit, the game doesn't do any painter's-algorithm sorting between the below-track meshes...because they're all the same dark-grey color, so they look the same regardless of what order they're drawn in. Similarly, the track pieces aren't sorted against each other either...because they're all coplanar and never overlap each other.

Feeling pretty good about the set of environments now - the forest is pretty standard fare (but looks nice to me anyway), and then the other two are more wacky and fantastical.

Taking a second shot at obstacles, because the first try didn't actually turn out so good - for one, since it was just drawing large extra walls, it only really worked when the walls were rendered Wolfenstein-style, which isn't the case anymore, and also, they were just extremely punishing when you hit them (not only killing all of your speed, but also jolting you sideways, so you'd have to fiddle around just to get forward again), which felt pretty nasty.

The new approach is to draw them as more plain-meshes and let you knock them away by hitting them - you lose a lot of speed, but you can keep driving in the same direction. The intent here is that you'd still want to avoid all of them if you were going for the best possible time, but if you're not in an absolute tryhard type of mood, then hitting a few is relatively forgiving.

2024-06-2914-50-52-ezgif.com-optimize

Longer clip (with sound) on twitter

I've mostly been posting progress on the Playdate Squad discord server lately, but really a lot of it is just backend stuff and things that i can't show for spoiler reasons anyway. Still, I wanted to throw out another message here just to reassure forum people that the project is still moving - and in fact, it feels like it's getting close to being finished now!

Campaign Mode:
I've got a full set of campaign tracks (24 races in the 3 settings shown in earlier posts here), though I still might revise or replace a couple of them after doing some more playtesting with other folks. These tracks are all very short, around 30 seconds (because that's my general preference in full-Trackmania maps), which may sound like "only 12 minutes of gameplay" but each of them has four replays to compete against (Bronze, Silver, Gold, Author) and I think most players won't get the gold times on their first playthrough. For a lot of players, I expect them to get "mostly bronzes and a few silvers" on their first campaign-completion, and then (hopefully) they'll want to go back and get tougher medals, to unlock some bonus rewards (which I'll get into later in this post).

Companion App:
This is mostly working now, and just needs a few extra features (stuff like "rename/delete tracks on the device"). The website can load a list of tracks from your device, and shows a thumbnail for each one - these thumbnails are generated by the game, instead of the web service, which is a little odd but it lets me be confident that they'll always look correct between the two apps (since they get generated with the exact same routine that usually draws the level-select previews in the game's menus). Currently you can only share a track via a Huge URL (where the URL contains all of the track data, meaning nothing has to be stored on a server), but I'd like to add an option to upload/download plain files too, particularly for tracks that are too large for URL-sharing (since URLs are limited to about 2KB for browser-support reasons).

Cars:
Along with less-exciting options menu stuff, the game has a car-select screen, and I started working on an old-timey car (based on old versions of the game, where the original car model accidentally looked like one of these from the back). Car choice is cosmetic-only, so there's no strategy involved in picking a model. Another user on the discord offered to make a car model, so if he doesn't find the game's mesh restrictions too irritating, I'll include that one too - or, if the weird rules turn out to be too much of a nuisance, I'll just make a third one myself.

Unlockables
Here's the current plan for stuff you can unlock by playing the Campaign mode (any mention of "get this type of medal" really means "get this medal, or a tougher medal"):

  • Get a Bronze medal: unlock the next track
  • Get a Bronze medal in all 24 tracks: unlock the track editor
  • Get 8, 16, or 24 Silver medals: unlock goofy joke-cars (in addition to the default three)
  • Get 8, 16, or 24 Gold medals: unlock little themed rendering demos (more info below)
  • Get Gold medals in all 24 tracks: unlock author-ghosts in all tracks
  • Get Author medals: reveal pieces of a secret QR code

The "themed rendering demos" have been a fun little diversion lately. Since these are unlockable rewards, I haven't been showing the actual scenes publicly, and instead I'm just showing this doofy example scene featuring Suzanne Blender to show progress on the renderer. This is a completely different rendering setup than the main game, taking heavy advantage of a static camera but letting the player control the sun with the crank (you can also adjust the fog density, but that's not shown in this clip).
2024-08-22_23-18-27-ezgif.com-video-to-gif-converter

If you've heard of Deferred Shading, this is basically "Very-Deferred Shading" - the gbuffer is generated offline, and then only the skybox and lighting (lambert, blinn-phong, rim-light) are rendererd in realtime. You can see some ghosting artifacts on the sun as it moves quickly...this is a result of the renderer only updating part of the screen during each frame - this problem is more pronounced on the device (the ghost-trails take longer to fade away), but it still feels fast enough for this type of usage.

It's hard to guess how much time is left on the project, but my to-do list is finally getting shorter instead of longer, so I feel like the end is in sight! Excited to finally submit this thing for Catalog consideration some time in the not-so-distant future.

Wow! I've been away from this forum for a while and missed a scorcher. Genuinely impressed by the tech going on here!

Getting started on a 3D airplane game and feeling like I need to pick your brain. Your project looks incredible. Godspeed!

Thanks! If you're looking to chat about rendering, my discord username is the same as here.

The game is getting sorta close-ish to being done now, so I've been preparing for trailer stuff.

First off, that meant adding a "watch replay" feature which gives you procgen camera angles, partly because it's fun and partly because it'd be lame if the trailer was entirely "regular gameplay footage" with the camera always behind the car.

Another notable thing is hacking together a way for the game to export greyscale video - this one is an internal tool only, so the final game won't let you do it...really, the only reason I wanted to do this is because of the game's heavy reliance on dithering. It looks good on the real device, but if you blow up a video of it to fullscreen on a computer monitor, it sorta falls apart (like looking too closely at a pointillism painting). Greyscale footage lets you get a sense of how the game looks on the real device, without having to be a tiny little video.

It's gonna be a bit of a puzzle to explain this in the trailer, but I think there's a way to do it without popping up a bunch of disclaimer text...after all, the game doesn't really look like this, but also it sort of does really look like this.

Frame_0001-ezgif.com-optimize

Got some nice news yesterday: Trackminia has been accepted for sale on the Playdate Catalog! No word on a release date yet, but it'll probably be in a few months (I have a small bit of stuff left to do on the game, but it pretty much depends on Panic's release schedule at this point).