Working on a little 3D racing game called Trackminia

Made the ghost cars opaque unless they're currently intersecting your car. After seeing that, it made me feel like they needed to cast shadows, because they were looking sort of floaty/hovery compared to the player's car. Not a huge change, but it makes them feel a lot more "real" and "3D-ified," to me!

2024-06-0113-33-47-ezgif.com-optimize

3 Likes

Added some obstacles to give me some more options when doing level-design - these are just regular walls, which gives them some some nice features (like collision detection and ambient occlusion on the floor) for free!
2024-06-0118-31-31-ezgif.com-optimize

I also finally updated the tire tracks to draw with meshes instead of plain 2D lines. This was mostly to make them get hidden behind walls, but I also took the opportunity to add some width variation, which makes them look a bit messier and more textured (though there's no "texture" in the 3D-modeling sense of the word). This is kind of a fun milestone, because the line-tracks were the last part of the 3D scene which were calling a default-Playdate-graphics-API function, so now all the 3D stuff is custom! I'm still using some default SDK calls for 2D UI, but I don't have any reason to change that (yet??).
2024-06-0120-25-18-ezgif.com-optimize

Lastly, I started making some campaign-mode tracks (and their associated ghosts to compete against). I made four of these tracks and then realized that I had some big problems with the car physics. Most notably, driving full-tilt and bouncing off of walls was a far-too-valid strategy - this isn't supposed to be like Burnout, this is supposed to be like Trackmania! After fixing that and tuning up some drifting stuff, I had to start over on the tracks, since changing the car physics means all their replays are invalidated. I'm back up to 10 tracks so far...will probably aim for around 20?
Recording2024-06-02002443-ezgif.com-video-to-gif-converter

3 Likes

Just a thought, but have you considered variable height walls? If not in-level, then per-level. Or maybe some lighting differences to distinguish levels.

1 Like

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.

How about no walls? Palm trees in the desert, etc.

2 Likes

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.

2 Likes

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.

5 Likes

This looks great. Can we raise the camera so that we can see more of the road?

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

Is it one type of wall for the whole course or could there be built up areas (city) and barrier areas (motorway)

also i' be interested to see a GIF exported from the Simulator (video camera icon) rather than the videos you are sharing at the moment. Will be much more crisp

1 Like

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)

1 Like

Yeah the forum seems to resize the gif. Ah well.

The only reason I suggested different walls is because in Trackmania varying the scenery along the track makes it easier to memorise the layout. City was an example but there could simply be different geometric or pseudo-random structures along the track.

1 Like

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!

3 Likes

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.

IIRC it is a simple as having to choose where to invest the limited development resources.

It was thought the chasing accuracy in Simulator (which is not an emulator) may not the best use of dev time.

One or more third party developers were working on actual emulation but not heard anything in a while.

2 Likes

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.

4 Likes

Behold, fish!
therefore Time for fish

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

10 Likes

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?

1 Like

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).

3 Likes