Published: September 21, 2025
I've been quietly plugging away making the easy/hard mode charts for all of the existing levels. Back in June I got done with recharting level 4-1, but since then I've gotten through another 7 levels and am now finished with those additional charts for level 5-3, plus some of the bonus levels!
Since everyone loves seeing videos, here's one that demonstrates some of the harder patterns in action. Some of these later levels are getting quite tough!
I especially appreciate the patterns where the ghost enemies jump forward into grouped sequences of flying enemies, like in this sequence from level 4-5:
The quick pattern of hold - flying enemy - release has been a really common pattern in the hard difficulty charts. In level 5-2 it gets enhanced to feature green combo enemies at the start:
In the normal difficulty, sixteenth-note rhythms aren't introduced until the yellow ghost enemies in level 6-1, and rapid sixteenths aren't used until level 6-3, but in hard mode they start showing up in level 5-3. This was both the point at which I was having trouble making things any harder without using sixteenth rhythms, and also a point in the music where it just made sense to throw them in:
In the last devlog I went over my big rework of the settings menus so that everything is available in one unified/paged menu:
After a few weeks of actually using this new menu, I found that while it was great for exploring the plethora of miscellaneous options, I actually found it a little tedious for doing common tasks, like toggling auto play or changing music speed, etc. It helps a =bit= that I added page up / page down bindings to flip through the pages quickly (alternatively, you can use the mouse wheel or gamepad shoulder buttons), but I didn't necessarily want to rely on those.
The new iteration is a hybrid approach, where I have the most common options in a basic menu format like before, but the "Advanced Settings" button drops you into the paged version where you can access all of the more complicated stuff:
Hopefully this approach will combine the best of both worlds, though I'll have to sit with it for a while before forming a new opinion of it.
I totally broke the level editor a while back when I was doing my big refactor where backdrops and tilesets were specified per checkpoint instead of for the entire level. I've since gotten the editor working again, and turned the backdrop/tileset dialog into a per-checkpoint tool:
I did another pass on screenreader fixes and support, including making the new advanced settings menu work with screenreader navigation, fixing the level 1-1 tutorial. There was also a fun bug where I was trying to disable built-in screenreader support on Steam Deck, but I was calling that API incorrectly, resulting in a completely different bug where screenreader functionality was broken on Windows if you had a screenreader running...
There was also a screenreader issue on Windows where unicode text wasn't being encoded properly, leading to something like "你好" being sent to the voice synthesis as "ä½ å¥½", which was of course unintelligible. I somehow managed to make the appropriate fix to the C++ side of the accessibility plugin that I'm leveraging, along with pulling in code that lets you switch the voice used for the speech synthesis (only works if you have that Windows language pack installed...), and even fixed a race condition.
The basic settings menu will also show you a quick shortcut for Accessibility options if you have screenreading enabled or have one running:
I don't have any fancy stuff to talk about regarding performance, mostly just a lot of little things here and there that I took care of. For example, I've completely ripped Unity's animation system out of all of the obstacles in the game and am manually controlling each sprite frame using my own implementation (those animations are quite simple anyways). There were some other random things here and there -- caching some variables here, changing out a data structure there.
One other thing that I noticed performance-wise was that the in-game settings menu was struggling to render efficiently, because there are a whole bunch of big transparent dialogs layered on top of all the actual gameplay and backdrops that have to already be rendered in the background. This was especially bad on systems like the Switch, where fillrate and overdraw (having to render a bunch of different layered transparent things on large swaths of the screen) was already an issue.
Except...when the settings menu is being displayed, the game is paused and nothing is moving! So there's no reason we need to keep re-rendering the backdrops every frame, we can just turn off the backdrop cameras and updates (except for special cases like when you toggle certain settings) and save on all that rendering time.
There's actually something else really big related to performance that I forgot to mention (or even think about) last year when I was doing a big refactor on how backdrops are rendered:
The big change I made was that I composite all of the backdrop layers into a temporary texture first, and then draw that final result onto the screen. This might seem like the same amount of work as before (or actually one additional step), but there's one important optimization that past me made: the temporary texture is sized at 1x (no pixel upscaling at all). So if you're playing the game at 1920x1080 (which by default uses a 3:1 pixel ratio), the temporary backdrop texture is actually only 640 pixels wide! Rendering at 33% dimensions means we only need to render 1/9th of the pixels as we originally did, which is huge for performance. It does mean that we lose out on subpixel backdrop scrolling, but that's something that I honestly wasn't a huge fan of to begin with (subpixel scrolling is still supported elsewhere).
With all this done, I'm happy to report that the game runs smoothly on Nintendo Switch, even in the later levels where I've got many backdrop layers overlaid on top of each other. Hooray!
I've also optimized level load times...this was a funny one as it turns out that the grass (!) in levels 1-1 and 1-4 were slowing down ALL level load times (d'oh!). The grass got added to those two levels about four years ago, and I guess at that time I somehow thought that I needed the grass to be dynamically generated by spawning many different small patches of it because otherwise it would look too patterned or something? Or perhaps that was how I solved the issue of how to place grass on slopes? (make lots of small short patches of flat grass across the entire slope)
So I had code that spawned a ton of different grass objects at level generation time...and unfortunately, because I had refactored the level generation to use "universal" tiles that supported transitioning to =any= tileset for every level, this happened for every single level load, not just levels 1-1 and 1-4. Well, the grass is no longer dynamically spawned, it just tiles statically and I just made some sloped grass graphics that are super duper straightforward. Not sure why I didn't go with this solution initially, but...there you have it.
Because I was already doing some performance optimizations on the song density chart in the level browser, I had a little fun and the game now keeps track of where you died in your fewest-deaths run, marking those with little red "X"s in that chart.
This is just a small little throwaway feature at this point, but maybe I could do something a little more fun with it at some point, like after every play show you a more exciting visual animation of all of the times you died. Not really something I want to sink a bunch of time into right now, though.
I meant to get this devlog out earlier but kept on working on actual development instead of writing about what I had done so far. I suppose that's not really a terrible thing, probably more efficient for me to spend time actually working on the game instead of writing about it after all. There are a bunch of other random things that I also tackled since my last update 3 months ago (did you know that the values you get from scroll wheel input are completely different on Mac and Windows, and depending on whether you use a trackpad or a mouse? Or have inertial scrolling disabled? Fun...), and I also did a bunch of Steam Deck-specific fixes, but that's all that I'm going to write about this time around. Hopefully I can continue to make good progress through the remainder of the year!
<< Back: Devlog 78 - Unified Settings, Camera Smoothing, etc.