No fancy new level to show you all this time, but I've been doing good work regardless this month. I have a silly little -- ok, maybe it's really not little anymore, actually quite the opposite -- Trello board where I track tasks and work items that I accomplish for the game, divided into weeks, so you sorta track the number of things that I end up tackling per week. This past month had some columns that are longer than can fit in my screen...
Weblate Site
I mentioned in a previous devlog that I had been working on migrating off of Crowdin for managing community translation efforts due to exceeding their free plan limits. I'm happy to report that my self-hosted Weblate site has been up and running and seems to be fully functional with no issues!
Currently (subject to change) this runs on an Amazon t3a.small EC2 instance, which costs me $0.0188 an hour on-demand (was easiest to go with AWS since I already have other stuff running there), which is pennies compared to the non-selfhosted options out there. More importantly, it all seems to work, and it's even synchronizing everything to a github repository in XLIFF format, so I can breathe much easier knowing that rollbacks or tracking down issues are going to be that much easier.
No More Unity Branding
I finally bit the bullet and did a major engine upgrade to Unity 6 (aka Unity 2023). This involved fixing a couple of bugs that cropped up (the upgrade exposed some issues that were mostly my fault that had never surfaced before). Upgrading isn't really painless right now because I have all of these custom changes to Unity packages (hacks to make things actually work like they should...), but I managed to handle it well enough. Probably the biggest relevant change (hah) in Unity 6 is the removal of the Unity splash screen requirement for free Unity licenses (huzzah!). I also took a few minutes to update the WebGL template for the web version of the game, so now we get a nice loading graphic and we're totally devoid of any Unity branding!
I can probably stand to make this even nicer later on, but this is already a welcome change, and nobody has to wait those extra 5 seconds sitting through the Unity logo before the game starts, woo~
Loading Icons
Speaking of loading graphics...I also took another few minutes to implement a simple change that's been on my list for a while: the loading indicator in the corner of the game now changes to reflect which character you have selected!
(More) Backdrop Optimizations
I was tired of seeing framerate drops when testing on lower-end devices (like the Switch...), so I went in and did another pass at performance optimizations. Besides random trivial "cache this lookup" or "use a binary search here" improvements, the main change that happened was with regards to how the backdrops are rendered (again).
A long time ago I did some nifty optimizations around backdrops that had large opaque areas (obscuring big chunks of what was behind them). Essentially, if we know that the entire screen below a certain point is covered up, we don't need to bother rendering any of the backdrops below that point.
...eeexcept if the backdrop in question is transparent, in which case we =do= have to go ahead and still render everything. Now of course, I have a lot of backdrops that are opaque, so this is still a good improvement.
...eeexcept when I'm crossfading between two sets of backdrops in the menu. This special case has caused me a lot of headaches in the past and it was never performant on the Switch despite my best efforts. There's multiple problems at play here: - We're simply drawing twice as much stuff (twice as many texture lookups, twice as many "pixel paint" operations). - If you want something that's a halfway fade between set A and set B, you can't just render set A at 50% opacity and then render set B at 50% opacity on top of that. That's not how blending works. So instead you need to reorder everything -- you can render set A at 100% opacity, and then set B at 50% opacity on top of all that. That requires a lot of bookkeeping logic around the sort order of all of the different backdrop layers. - Rendering backdrop layers at less than 100% opacity throws all the nice optimization described above out the window. Really what I wanted to do was just treat backdrop set A and backdrop set B as pre-rendered (moving) images and then fade between those two, instead of having to deal with the complexity of each one being made up of 20-30 different layers. So, I did just that!
In the new setup, each set of backdrop layers is rendered at full opacity to two separate temporary render textures -- one for foreground layers, and one for background layers (for those of you who are familiar with how rendering works, we need to use premultiplied alpha here to handle transparency correctly). We can then easily crossfade between our temporary textures without having to worry about weird transparency blending artifacts. This took a bunch of work to set up, but actually massively simplified the menu backdrop handling code as I don't have to worry about doing weird juggling of the sort order of a bajillion different individual layers. There's still more rendering optimizations I can potentially look at in the future (some of the world 6 backdrop sets are particularly heavyweight and could benefit from some additional techniques around intermediary textures), but this is already great progress and we successfully eliminated the framerate drops on Switch that were caused by menu transitions.
Editor Improvements
I also made some optimizations for the level editor! Previously I described how each time you make a change to a level, the editor currently needs to regenerate the level in its entirety. To make this faster, I have object pools for all of the level objects, so that I can reuse the instances instead of destroying all of them and recreating them from scratch (which would be much slower). ...except, somewhere along the way I ended up breaking that entirely, lol. When I started supporting different level tilesets, I had to swap out the "level generator" object that contained all of the tileset data, and the easiest way to do that was to just replace it every time you recreated a new level. Except, that object also contained the object pool...which therefore was getting destroyed every time...[facepalm] Anyhow, that's all fixed now. I actually found this bug because I was testing out the new fine-grained editor grid, which lets you place obstacles using a 16th-note grid. This is mostly so that you can create interesting syncopated rhythms, but yes, if you wanted to, you could just go wild and make "random note-spam" type charts:
Virtual Cursor
I also added a virtual mouse cursor implementation for the level editor that you can control using either the keyboard or a gamepad. Technically this isn't really required for any platform (even for something like Nintendo Switch you can still use the touchscreen, unless of course you have it docked), but navigating the editor UI via keyboard doesn't really make any sense anyways so I figured I'd try this out.
As usual, Unity gets you kinda halfway there but then you have to pick up the pieces, leaving you wondering whether you should have just implemented the entire feature yourself in the first place. Unity provides a virtual mouse cursor implementation that (fortunately) will hook into their UI input system, except: - It doesn't properly handle UI canvases that are scaled (like mine) - Disabling and re-enabling the cursor causes a HUGE lag spike as it re-registers a new virtual input device and does god-knows-what-else - For whatever reason it seems to not interact with UI (yet still move around fine) if you're using specific parts of the new input system (device-based control schemes) But after some cursing and debugging, and some added hacks, it seems to all be working fine! The system is smart enough to enable the cursor when you use keyboard or gamepad input, and turn it back off if you start using a mouse, plus you can even scroll the camera by putting the virtual camera at the screen edges (or via right analog stick).
Gamepad Fixes
As is common, gamepad support is yet another piece of functionality that has landed firmly in the bucket of "I should have just reimplemented this myself manually from the beginning instead of using the Unity-provided solution". So far I've been using Unity's newer "Input System" solution, which actually has a number of advantages over older implementations -- including automatically switching between control schemes based on player input, as well as supporting input rebinding. After (sigh) some hacks here and there, including one to deal with the fact that Steam forcibly takes controller input and translates it to keystrokes (thanks, no thanks...), I actually had that all mostly working. ...eeexcept for the fact that on some systems (??), certain controllers would simply not register input under Unity's new input system whatsoever. Not just a "my implementation" problem either, as I downloaded some sample projects and the issue shows up there, too. But...clearly the device has connectivity, and Unity even throws up a helpful message reporting when the controller connects.
Gamepad input is understandably something really hard to deal with (given the number of hoops you have to go through sometimes to get a given console controller working with your system), but I'm going to blame Unity for this one given that their old input manager implementation still picks up the gamepad's input in this case. I hope I'm not going to be regretting this later on, but for now I'm leaving the old (new?) input system implementation in place (with my hacks, it does end up switching more-or-less gracefully between input schemes, which I like), but I implemented a legacy gamepad fallback implementation for when the new input system claims to not have gamepads attached but the old one does. Of course, with the legacy input system, there's no standardization for how the buttons and sticks on various controllers present themselves, which is probably part of why Unity had that old input configuration dialog that used to pop up every time you started up a poorly-made game jam game:
Ah yes, the telltale sign that you were about to play somebody's first Unity game. Anyways, fortunately for me, Incontrol's old open-source package contains device profiles for many known controllers. Granted, this hasn't been updated in the past 3 years (since Incontrol became a commercial closed-source asset), but I found that it was good enough for my purposes, at least for now. I integrated Incontrol's input handling and mapping, updated it to work with Unity 6, turned on XInput support, and "just like that", I have gamepad input testing and working for my XBox360 controller on Windows and on my PS3 controller across Windows and Mac. I mean, forget the fact that I needed two separate programs to even get the PS3 controller to hookup to those systems successfully, and the fact that the XBox360 controller support is just plain borked on Mac...
Font Rendering Fixes
Updating to Unity 6 meant some small shifts in how Unity packages its text-rendering system, which exposed a few issues with pixel snapping and sorting order that I ended up ironing out. While I was at it, I noticed that the unicode pixel font that I use (unifont) rendered OK when the game was at small resolutions, but at higher resolutions it got all blurry and the sampling didn't seem right.
This one is kind of on me. Unifont is designed to render minimally at size 16 (or 12, depending on what you're plugging it into), but because my game pixels are often already upscaled, I was having it sample at that size and then setting it to display at 50%. The UI canvas would for example be scaled up by 2x, so it ends up balancing out to present at 1x scale and render crisp pixels (which are 50% the size of the rest of the game's "pixels"). Unfortunately that just doesn't extend to other canvas scaling situations. In the end I just decided to get rid of that 50% factor, so now unifont displays one to one with the rest of the pixels in the game UI:
Of course, now the font is just bigger, so I run into issues where the glyphs can't really fit properly into the areas they're supposed to. But if you're using a language where the unicode font is used, you reallllyyy ought to not use the pixel-based font anyways (and the game is smart enough to default you to smooth font rendering in this case). Unless of course, you're playing at like 500x300 resolution for who-knows-what reason. In that case, this is actually an excellent change because the unicode pixel font now reads really well at that size, and you probably don't mind that the lettering is bigger!
What's Next?
Phew. There's still an endless list of things to take care of, and we're already two months out from the end of the year, but I'm happy with the amount of time I've been putting into things, at least. Moving forward, there are some remaining big-ticket items that'll make me feel much better if I can knock them out, including but not limited to: - Unifying the selection menu for both bonus songs and custom levels (and main campaign levels, why not) into one big "song select/free play" menu - Doing some thinking about whether I want to support multiple game difficulties (and more importantly, how I'd like them to work) - It's awkward that the game settings are half split between "settings" and "game mods", I'd like to reorganize things if possible - I still need a proper export flow for the level editor... With any luck I will be able to at least get a few of these done by the end of the year. Thanks for reading, as always!
The Rhythm Quest Demo has been updated to version 0.34.0! This patch includes some major internal improvements, including a major Unity update, better gamepad support, performance optimizations, and more!
Full changelog:
Version 0.34.0
- Updated to Unity 6 (removed Unity splash screen...)
- Reworked rendering for menu backdrops to improve performance, especially during transitions
- Other performance improvements
- Add alternative gamepad support to improve gamepad compatibility
- Decreased coin sfx volume
- Loading indicator now matches selected character
- Attempt to handle audio device changes/resets in menu scene
- Fix softlock on changing audio device/improve audio reset behavior
- Fixed spike enemies not responding to skin changes immediately
- Fixed transitions for non-tiling backdrops in low-quality graphics mode
- Localization/text updates and fixes
- Added menu option to skip 1-1 tutorial
- Disabled assist menu during 1-1 tutorial
- Fixed track freezing causing softlock in 1-1 tutorial
- Reset menu backdrop scrolling on transition
- Potential rendering fixes
- Fixed unintended menu description label changes during transitions
- Fix rendering on unicode pixel font by making it much larger
Level 6-4 is sickkk. [previewyoutube=kpZHFdCXuX8;full][/previewyoutube]
New Patterns
This is the second-to-last level of the 30 main ones, so I can't hold anything back now! Even this late in the game, I'm actually still managing to introduce a new curveball or two...starting off with combining speed zones with ghost enemies:
Since this isn't immediately obvious how it would work, I've provided the tutorial icons below to show where the ghost ends up. It's actually simpler than it looks, as the timing is just equivalent to regular eighth notes (like a normal double-hit enemy). But now I can use this triplet->eighth note rhythm as a satisfying way to cap off a phrase. In a similar vein, I also combine speed zones with spike enemies:
This is another instance where I'm secretly making the rhythm easier to parse by adding extra elements -- the green combo enemies allow you to have a more obvious indicator of when you need to jump. Besides that, I'm making full use of the 16th-note rhythms that I introduced in the previous level:
I feel okay using this tricky purple + yellow ghost combination as a "fixed pattern" since I already introduced it in level 6-3 at a slower tempo. As you can see, I'm also leaning more into variations on 16th-note flying enemy patterns as well. Honestly, the new rhythms are a blast to play. It's crazy to think that over a year ago I thought that speed zones would be the last mechanic added to the game, until I was inspired to make the yellow ghost enemies (partly due to some custom level work I was doing!) which started to open up the opportunities for more variations.
Backdrop Design
I'm quite pleased with the backdrops for this level, not just because they look great but because I managed to do something different than just a 4th version of "outer space with a bunch of stars, plus something extra". I started things off with these spraybrush layers, actually very similar to some of the cloud layers in previous worlds, just with a lot of dithering going on (that's the main stylistic thing going on in world 6):
I liked the way that looked, but wanted to add something more to it. My first attempt was just to do some mountains (clouds + mountains...just how many of my previous level backdrops are variations on this?):
It wasn't the worst (the dithering on the shading of the mountains was a nice touch), but I felt like it wasn't quite hitting right, plus it didn't really feel like it vibe with the whole space theme. I decided to try using translucent triangles instead, kind of like how I did in world 5, and that looked much better:
I'm scared at how this might perform on the switch (this is the kind of backdrop set that requires toggling off some layers when in low-quality graphics mode), but it looks beautiful when you see all of the translucent layers scrolling at different rates and blending together.
Audio Design
The element that kicks of the track and plays through its entirety (and the one I wrote first) is this gated pad sound. I layered some different sustained synths and then applied a rhythmic volume automation to it: https://rhythmquestgame.com/devlog/71-gatedpad.mp3 There's two different basslines going on in the first section of the song -- one main "plucked" bass synth, and then a simple pulse wave bass that's a bit higher in frequency: https://rhythmquestgame.com/devlog/71-bass.mp3 The drums aren't anything too fancy, just a drum loop that I've chopped up and rearranged, plus some extra hi-hat layers: https://rhythmquestgame.com/devlog/71-drums1.mp3 Here's all of that coming together in the first chorus along with the lead melody: https://rhythmquestgame.com/devlog/71-chorus1.mp3 In the second second of the song where the big drop happens, I switch to a different main bass sound. This one is really thick, made by layering a handful of different synths -- some providing a really "meaty" low-end, and others providing a more middle-range detuned sound. https://rhythmquestgame.com/devlog/71-bigbass.mp3 The drums get a little changeup too! Most notably, I switch to a halftime beat, but I also use a beefier kick drum, and there's an extra drum loop layer, which mostly adds extra strength to the snare hit. https://rhythmquestgame.com/devlog/71-drums2.mp3 There's this heavily-reverbed chirp sound too. I featured this same sort of thing in the previous level too, but I guess I just can't get enough of it; it provides this sort of nice melodic "shimmer" in the top-end without getting in the way of the lead melody. https://rhythmquestgame.com/devlog/71-echoarp.mp3 In the buildup I also turn on a "notch filter" (removes a narrow range of frequencies) and sweep it from high to low, for an almost phaser-like effect. Here's all of that coming together: https://rhythmquestgame.com/devlog/71-chorus2.mp3
Other Bonus Songs
I've also been working on adding some more classical music pieces to the game as bonus songs -- this time by composers other than Mozart! [previewyoutube=ntfHInDWD4w;full][/previewyoutube] Starting off with a classic, "Flight of the Bumblebee". While this one isn't quite as well suited to Rhythm Quest as it is to some sort of 7+ key rhythm game where you can just chart out all of the 16th notes, it was still a fun one to do.
Past the very start of the song, you spend the entire time in the air through combinations of air jumps and flight patterns, as a nod to the theme of the song. The scrolling speed also increases bit by bit with every checkpoint, adding to the frenetic nature of the song. (PS: I spy mountains + clouds in the backdrops...) Then we've got a song that I heard about a billion times at music recitals for young piano players, "Fr Elise":
This one is more laidback -- kind of a repetitive song, to be honest, but I made sure to chart each repetition a little differently so that you don't end up falling asleep (like I did during those music recitals...). It's also notable for being the first song I've charted that makes use of a non-4/4 time signature. (Hm? Yes, that functionality totally worked the first time that I tried it, there were no bugs to speak of, nope, not at all. ...why are you looking at me like that?) That's it for this month's update. Comparing level 6-4 to level 1-1 is wild; I can't believe how far I've come in these past 8 years and how deep I've gotten into Rhythm Quest charting and level design. I'm honestly almost glad that the journey to get here has taken this long, as it means I get to put the knowledge and skills that I've accumulated over the years into use. Well, it also means I probably need to revisit some of the older levels to touch them up, but I'm going to not think about that just yet... Level 6-5 feels like it might be a little intimidating to work on, so I might try and work on a few other things next month to start with. Knowing how I work, though, I feel like it's going to be the sort of thing that I just decide to just sit down and knock out most of it in like a day or two. Just has to be the right time, I guess.
Working on new levels has consistently been my favorite part of Rhythm Quest development! As I continue to flesh out my roster of bonus stages (I've got 15-20 at this point!), I've been thinking about other musical styles that I haven't yet represented. One of those is DnB (Drum and Bass), so I made a track called "Midnight Moon" inspired by "Liquid DnB" soundscapes:
[previewyoutube=Xr3riHuEvTU;full][/previewyoutube]
This one is at a very brisk tempo of 170 BPM, but doesn't feature any of the mechanics from worlds 4-6 (water/speed zones, green enemies, yellow ghosts), so it's a good exercise in simply testing your ability to read basic patterns. I'm making more liberal use of syncopated rhythms in the bonus songs throughout, though, so they are going to be a little trickier to sightread than the main stages.
As I've probably mentioned in the past, even when I do use offbeat rhythms, I'm trying to keep them relatively readable, often favoring airjump patterns over basic enemies as they tend to be easier to read at a glance based on height differences.
For the music side of things, the basic structure of the DnB soundscape is actually not very complicated. The foundation consists, of course, of a nice drum loop, and then a prominent low bass synth with a little bit of "wetness" (growl/detune/warble) to it:
https://rhythmquestgame.com/devlog/70-drumsbass.mp3
The cool thing about the liquid DnB-inspired soundscape for me is that the tight drums contrast with the other melodic layers going on, which are either drenched in lush reverb, or have softer textures. Here I'm using a droning bell-type pad loop sample, some electric piano-ish plucks, and then a chiptune arpeggio that's put through a ton of reverb:
https://rhythmquestgame.com/devlog/70-texturelayers.mp3
Here's all of that coming together in the main drop of the song, which also adds in a few other new elements (an additional drum loop layer, a high blippy synth):
https://rhythmquestgame.com/devlog/70-drop.mp3
Rondo Alla Turca
I'll eventually try to get someone else like Bach in the mix, but for now we've got some more Mozart! This time the very well-known "Alla turca" movement of Piano Sonata No. 11 in A Major, also known as the "Turkish March": [previewyoutube=UXJo9hTwE0c;full][/previewyoutube] This chart is simpler (doesn't even use ghost enemies) and should be a fun, light-hearted playthrough for anybody who has gotten to world 3-ish. Being familiar with the melodies of the song should also make sightreading a bit easier, which is nice. There's not much else to say about this one so let's just move onto talking about other stuff!
Pausing while Respawning
Finally!!! This seems like such a trivial thing but has literally been on my TODO list for years...you can pause in the middle of a respawn now:
It's...actually still not perfect, as instead of immediately resuming the respawn where it left off, it restarts the respawn transition so it might take a few extra beats. But that's certainly good enough for me! You might be asking why this took so gosh-darned long for me to implement and the answer is that the respawn logic is actually really complicated with a bunch of different moving parts. Briefly speaking, during a respawn the following things happen: - Switch over seamlessly to the respawn music loop (which was already playing, just at 0 volume) - Find a seamless point in the future to reschedule the main music - The pause music loop and respawn music loop also need to be scheduled (at 0 volume!) - Take care of the actual camera and player animation, which needs to be smoothly interpolated - At the end of the respawn, the entire audio timeline needs to be shifted Because of how audio needs to be scheduled in advance (otherwise you run into audio buffer/latency issues), pausing that entire process isn't as simple as "just pause here and resume later" -- the scheduled music transition needs to be cancelled entirely. Of course, pausing the game also involves some special logic (keeping track of when in the music the player paused, so that we can later schedule the unpause seamlessly and drop you in on the right beat). This was really daunting, so I kept putting it off, but recently I was working on handling an edge case where switching the audio device (e.g. plugging in a pair of headphones) would cause all currently-playing audio sources to stop (sigh...) and need to be rescheduled, so I went in and tried to just solve for all of this at the same time.
Weblate Setup
For a while now the community translation/localization efforts for Rhythm Quest have been managed via its page on Crowdin. This has mostly sufficed (actually was a headache at first as their Unity integration wasn't robust and I had to patch it up a lot...fortunately they have patched all of that up since then), but despite my impressions of the game not having a lot of text, I eventually started adding enough options and level editor UI flows that I quickly started running into the limits of their free plan (word count X number of languages can be 60,000 at most). The next tier up would cost $150 USD a month (yeouch) and only increase that limit to 100k, so I chose to look for alternative solutions for hosting and managing community trnaslations (hopefully something better than just reverting to using a scrappy shared google sheet). I ended up looking into Weblate, an open-source solution for this. Weblate's cloud-hosted option costs ~$40 USD a month, but you can also host it yourself if you have enough know-how to administrate a web server and set up all the infrastructure for it. After some failed (but educational) attempts at bringing that online earlier this year, I managed to actually get that mostly up and running on my web/cloud infrastructure stack:
This is not really production-ready yet (I've disabled new user registration and everything), and there are still some aspects of it that I need to follow up on, but it's good to know that this has a good shot of panning out for my localization needs moving forward. It's still not free, of course, as I need to pay for the server resources and all that, but the cost is an order of magnitude less than I would be paying otherwise, so it's a win there nonetheless. Translation is actually quite an important thing for my game if you look at the Steam wishlist breakdown by region, as the majority of my wishlists are actually from non-English speaking countries...
I had some plans initially for transitioning to using a professional agency and/or service for localization, but got a little discouraged after thinking about the costs involved. I guess it's a hard thing to commit money toward something that hasn't really yet cost you anything nor made you anything either...
The Rhythm Quest Demo has been updated to version 0.32.1! This patch contains a few bugfixes, but also finally allows you to pause during the middle of a respawn.
Full changelog:
Version 0.32.1
- Allow pausing during respawn (finally)
- Fixed pause music continuing to play after exiting practice mode
- Fixed music desync when pausing during level 1-1 tutorial
- Attempt to handle audio device resets/changes by restarting music
- Fixed minor menu navigation issues
- Fixed some text strings
The Rhythm Quest Demo has been updated to version 0.32.0! This patch adds in various assist features to help if you're struggling with particular levels or sections of the game.
Full changelog:
Version 0.32.0
- Added track preview, practice mode, track freeze assist features
- Added assist prompt when failing a section too many times
- Added control settings to in-game menu
- Added setting to enable/disable coin collection chime
- Tweaked/increased respawn timing
- Increase coin loss on respawn
- Decrease coin bank amount on checkpoint
- Fixed pause drum loop played after unpause
- Fixed pause drum loop synchronization issues when music speed was altered
- Fixed missing blur screen filter description
- Fixed jump arc calculations
- Fixed doublehitenemies always awarding coins even after respawn
- Fixed wrong ghost helper beat-matched sprite colors for second helper
- Fixed beat-matched colors not updating correctly via in-game menu
- Fixed visual hitch for ghost helpers on respawn
- Fixed keyboard rebinding not being available on web build
- Updated localizations
Another month, another new level. Hopefully I can at LEAST keep this pace up, if nothing else, haha. There's some other stuff I worked on that's not ready to show off yet, but here's the preview for level 6-3, titled "Quantum Orbit": [previewyoutube=irJiF6vO960;full][/previewyoutube]
Chart Design
I don't have any more game mechanics to introduce (we're nearing the end of the main levels, after all!), but this level still introduces some new rhythms into the mix to spice things up. I'm hoping to introduce two "combination rhythms" involving the yellow ghost enemies, one for A.bA ..A. and another for A..A b.A., where A is the ghost hits and b is a basic enemy inserted in the middle of the pattern. Level 6-3 features the former of the two:
A little later in the level I also feature the same rhythm, but this time with a green jump+attack enemy:
I originally had these as ghost + jump combinations (without the green combo enemy), but I was worried about the visual readability for that rhythm because the ghost =looks= like it's a half-beat after the jump (even though it's only a sixteenth-beat after the jump). My hope here is that by adding the green combo enemy it's actually easier to recognize as being the same rhythm as before (with the red enemy). Things are definitely getting trickier here near the end of the game! Level 6-3 has what I believe is the slowest tempo out of all of the main songs so far (95 BPM) due to featuring these fast sixteenth patterns. This also gives me the opportunity to introduce sixteenth-note strings of flying enemies:
We've already seen this same grouping inside of water zones, but this is the first time we're seeing them outside of them, meaning the pattern is twice as fast. I like how despite being a very fast rhythm, it should be very easy to pick up due to being so similar to something you've already gotten used to recognizing throughout the previous 2 worlds. This is where my consistency in pattern usage will hopefully pay off! Last but not least, towards the end of the level I throw this super-interesting purple + yellow ghost combo rhythm at you:
This is another instance where I'm trying to help the player out by first introducing the same rhythm, but with basic enemies instead of the purple ghost. Since the musical rhythm repeats the same way both times, there's a higher chance that you'll be able to get it -- if not on the first try, then hopefully without too much trouble after practicing it a few times.
Visual Design
I went kind of wild with the number of transparent layers for the backdrops here (I have to be careful not to do this too much as the backdrop textures will end up eating up more memory and storage space), which is why the colors look to have such a gradual gradient when they're all blended on top of each other.
I'm staying consistent with the general visual theme of world 6 and using the spraypaint tool everywhere for the "nebula cloud-like" dithering textures. That combined with the gratuitous layer blending really makes this world's backdrops feel different than any of the other worlds. It's working out nicely that the colors involved are the most complex compared to the other worlds as well. The main focal point of the backdrops is the big rimlit planet. Honestly at this point I'm just searching for random sci-fi/space-themed drawings and photos and using those as inspirations. It's working out okay so far! I tried to draw the stars a little differently this time -- have to do something to set apart all these generic "dark space backdrops"...
Music Design
I'm trying to use more varied bass synths through world 6. Here we have a thicker "plucked" bass sound that kicks off the song, as well as plays through the first "verse" section. Note the use of triangle wave tom fills to accentuate the triplet speed zone pattern (that's becoming pretty consistent musical signage for me): https://rhythmquestgame.com/devlog/69-verse.mp3 I'm trying to keep my lead sounds diversified as well. I still use my trusty square wave "bell" tones, but I also use more complex synths like this synth patch. Here I'm using an added square wave "pluck" layer to accentuate the onset of each note, and also adding extra "cool spacey" vibes by slapping on a reverse effect on the delay/echo trail: https://rhythmquestgame.com/devlog/69-reversedlead.mp3 For the main chorus section, I'm using a bog-standard VI-VII-i chord progression (probably the most common progression in the entire soundtrack) with some added sevenths/suspensions to spice things up a bit. Dropping into this specific progression at the main climax of a track is definitely a go-to techinque of mine, both inside and outside of Rhythm Quest, so I'm well, well acquainted with it. The pad synth is sidechained against the kick drum to give it that "ducking/pumping" sound, which you can hear here: https://rhythmquestgame.com/devlog/69-sidechainedpads.mp3 I'm trying to let loose a little more with reverb through world 6 as well, which means some heavy use of my favorite huge-reverb plugin, ValhallaSupermassive, which can turn any sort of basic/dry sound into an entire soundscape of washed out echoes. I use it mainly on this "call-and-response" arp synth: https://rhythmquestgame.com/devlog/69-reverbarp.mp3 Here's the entire main drop into the chorus of the song. The echoey arp that I mentioned above is definitely a background element here since it needs to make room for the actual gameplay-based melodies, but it's my favorite element of the track by far. https://rhythmquestgame.com/devlog/69-chorus.mp3 That's going to do it for this update! I only have TWO MORE LEVELS to make before I can celebrate at having all 30 songs in the main 6 worlds done! (and then go back to the million other things that I have to do, ha ha ha...)
New levels! In addition to charting out "Song of the Sea (Expert Mix)" from Melody Muncher, I've added two Mozart compositions to Rhythm Quest. Here's the video showing off some excerpts from those levels: [previewyoutube=ZGWfzXa-7y4;full][/previewyoutube]
Mozart
The two Mozart pieces that I added are the first (Allegro) movements from Piano Sonata No. 16 in C Major, K. 545, and Eine kleine Nachtmusik (Serenade No. 13 for strings in G Major), K. 525. (whew, that was a mouthful) Various classical pieces have been featured in other games (see: Pop'n Music, Taiko no Tatsujin, etc etc) and I wanted to see how they might work in Rhythm Quest too (given that I could find some public domain recordings or simply take a MIDI arrangement and create my own).
Overall, they're pretty energetic and make for a fun playthrough! The rhythms aren't necessarily perfect for charting in the Rhythm Quest system, but they work well enough and there are a few phrases that came out particularly nice-feeling. The trickiest part about charting them was making sure that for all of the repeated sections were charted slightly differently so that it doesn't get stale. I don't want to just have the exact same sequence repeated a bunch of times over and over again! (looking at you, Rock Band...) Probably the best aspect of these songs is that they're pretty widely known to a general audience, so that factor of "hey, I know this song!" is nice to have. Perhaps I can look into some other public domain compositions in the future...
Song of the Sea (Expert Mix)
Like with "Beneath the Surface", this one is at a slower tempo of 90 BPM (the two are very similar actually), so I wanted to focus on more complex rhythms in the chart, including 16th note patterns!
In the above gif you can see a couple different trickier rhythms being used. The sixteenth-note flying enemy patterns are something that I haven't actually used in the main game (yet??), as I've only done that grouping in the middle of a water zone. However, it's actually quite easy to read once you're familiar with it, so that might end up showing up in World 6 at some point (?). The trickier patterns is the one with the yellow ghost intermixed with red basic enemies. Fortunately, the tempo is slow enough here that even if you're just visually reacting to the spacing, you can kind of get it even if you don't parse it ahead of time. It's very satisfying to hit, though! This makes me think that maybe when I work on level 6-3, it should be one that's at a slow tempo like this, so that I can introduce some (maybe not all) of these more complex rhythms. One last note is that currently these types of 16th-note based patterns are impossible to create in the level editor, which obviously isn't ideal given that they're actually quite fun to work with when used tastefully. I'll have to make a note to see if I can add a setting for snapping to 16th-note placements, which I'm sure will also involve a bunch of work on validation and bugfixing, haha...
Assist Features
Feature-wise, I've actually been doing a bunch of work on game assists -- functionality to make the game temporarily (or permanently) easier, for players who are struggling or get stuck on a certain song but want to continue to progress. First off, I've added a dialog that shows up if you fail the same section of a track enough times (currently, after your 30th attempt). Because this is an unexpected prompt that happens in the middle of gameplay (I try to avoid those!), I made sure to fade it in and disallow input during the fade, to prevent you from accidentally selecting something without having the chance to read the prompt.
One of the options here is a "track preview" feature, inspired by the "binoculars" in Celeste, which let you view upcoming sections of the level in case you want to get them into your head and see what's coming up:
There's also "practice mode" (UI still being worked on) which will let you repeatedly practice the current section on the fly (while controlling the speed, etc.) until you're ready to attempt it for real. "Temporary cheats" just contains settings for slowing down the music speed, lengthening timing windows, and enabling autoplay, but they automatically disable themselves once you reach the next checkpoint.
Track Freezing
The new assist feature that I've just developed is something I'm calling "track freezing". When this is enabled, the level scrolling will automatically pause and wait for you to make the correct input, if you haven't already done it in time:
The music keeps playing even during this pause (trust me, it would be wayyy too complex to try and pause the music and reschedule it on the fly), so after you make the input, the scrolling tries to speed up to "catch back up" to the music. The end result is actually pretty cool! If you aren't missing any inputs, then the track freezing doesn't kick in at all and you just play normally. But if you miss some notes or press something too late, you'll be able to just correct it on the fly and have the music keep playing instead of having to restart the section.
Life...
...happens. I'm happy that I was able to put together a progress update for y'all this month, but I honestly wasn't sure it was going to happen as I had some tough stuff that I had to handle in my personal life over the past weeks. This is kinda just the way that things go (I'm a normal human being just like the rest of you), but it's also kind of tough to account for the in the grand scheme of things. Now that we're at the end of June, 2024 is officially halfway over! (gulp) Looking back over the past six months, it seems like I mainly worked on a bunch of bonus levels and custom level import/publish flows, but there were also some other great features added such as the companions, plus the eighth-note ghosts (a new core mechanic!). If we're being totally realistic, it's probably inevitable that I won't be able to do a full release by the end of the year =(...but part of me wants to keep that deadline fixed because I think it's good to have a goal to shoot for! Honestly, though, I've always been in "marathon mode" for this project -- as long as I'm continuing to progress forward at a steady pace, that's going to be considered a success for me. Hopefully seeing more and more levels and features is also enjoyable for you guys, even though I know y'all just want to hurry up and play the game already. I guess part of the reason I wanted to call out the whole "real life" stuff is that recent events have reminded to step back a bit in terms of perspective on what is important to me. At the end of the day, as much as I wish I could say that releasing Rhythm Quest is going to be something that brings lasting happiness into my life, it kind of isn't, in the grand scheme of things. ...which is not to say that it's not important to me or that I won't keep working on it! I'm still having fun working on the project and every now and then (usually when I'm charting new levels) I'm reminded of just how fun the gameplay actually is, and of COURSE I'm going to finish it, guys, I'm very committed to that. But at the same time, it's something I'm trying not to get too worked up about, because it's only one part of my life. I hope whoever ends up reading this will also keep in mind what the "the real important stuff" is as they go through their day to day struggles and efforts. Anyways, personal segment over, I'll see you back here next month with some more updates!
The Rhythm Quest Demo has been updated to version 0.31.0! This patch includes bugfixes and some new mod options, including the ability to switch the color mapping for obstacles in levels.
Full changelog:
Version 0.31.0
- Added color skinning visual mod settings to aid readability
- Added visual mod to adjust horizontal placement of enemies
- Added timing mod to scale timing windows by music speed setting
- Added visual mod to re-show collected coins after respawn
- Tweaked respawn timings, especially across different music speeds
- Fixed not being able to buffer jumps out of pause
- Fixed ghost enemies not respawning quickly enough when offscreen
- Fixed some level select logic when levels are played out of order
- Fixed flight paths jumps pulsing with incorrect beat offset
- Minor backdrop scrolling tweaks
- Fixed mis-sized character shop graphics
- Fixed checkpoint activation sounds sometimes replaying on respawn
- Fixed missing coin animation frame
Well, first things first -- I've got a new level to show off! I decided to move the speed zone level (formerly 6-1) to become level 6-2, and instead have this new level 6-1 which introduces the yellow ghost enemies in their basic state. Here's the video: [previewyoutube=p8gRCw3QZMc;full][/previewyoutube]
Visual Design
I've been a bit worried about all of the world 6 backdrops looking similar since there's a space theme and it's really easy to just do dark backgrounds and stars over and over again. At least this backdrop set has different focal elements (rim-lit planets vs. a spiral galaxy) than the other one I did, but I think I'm going to have to change it up a little more in the future.
Besides lots of little point stars, a lot of the "nebula" texture here is achieved by layering rough transparent splotches of color -- I'm just using the spray tool here so it makes more of a textured/fuzzy cloud rather than smooth shapes. I build up the "glow" effect on the large sun and planet using multiple layers, which seems to work well! Of course, in action all of the different layers are scrolling around, which makes things look even nicer:
Music Design
World 6's music style isn't =fully= fleshed out yet, but I have a few ideas that I've been trying to push in the two tracks that I've made so far. One of them is the use of more interesting bass synths -- here we have a plucked synth bass that gradually opens up using some filter automation over time: https://rhythmquestgame.com/devlog/67-bassline.mp3 There's nothing too crazy or special going on with the drums, just a pretty basic pattern, though at one point I layer in some heavily-reverbed claps for some extra spice. The classic triangle-wave bassline comes in halfway here, along with some chiptune-style noise drums: https://rhythmquestgame.com/devlog/67-drums.mp3 I only have so much leeway that I can get with lead melody sounds because I really need to make sure they cut through the mix effectively (usually means no massive detuned supersaws or anything like that), but even then, I'm still trying to push the variety a little bit. Here I'm using a square-whistley synth with a gratuitous amount of pitch bending/portamento so it's really bendy. There's two different patches layered on top of each other, which contributes to the slightly-detuned sound. https://rhythmquestgame.com/devlog/67-bendylead.mp3 Later on in the chorus I try to introduce some thicker synth arp patterns. Here's a pretty classic detuned sound and a simple arpeggio pattern: https://rhythmquestgame.com/devlog/67-arps.mp3 And here's that chorus drop, with everything playing: https://rhythmquestgame.com/devlog/67-chorus.mp3
Chart Design
As I mentioned, this is the level that introduces the yellow "dotted eighth-note" ghosts, so there's plenty of those thrown in by themselves, but I also wanted to be sure to mix in purple ghosts (to contrast the two). The most complicated yellow ghost pattern is this one where there's an air jump immediately before it:
I'm hoping to add a few very specific 16th-note patterns involving yellow ghosts in later levels here, but for now this will suffice.
mod.io Publishing Flow
In addition to working on world 6, I also took some time hammering away at trying to build out a publishing/export flow for mod.io. Mostly this meant a lot of UI work as I integrated with the mod.io SDK/APIs. Here's what it looks like at the moment to login via email, submit a security code, and the publish a level:
If you launched the game through Steam you also have the option of using Steam as an authentication method -- it should automatically pull your steam ID and userinfo from the account you're playing with, and display the (localized) mod.io terms of service if needed.
It's not perfect, but it's a good start! The next major step here is to actually work on a "publishing checklist" where the editor will check to make sure your level is actually complete before letting you export (you need to fill out the tile/author information, decide whether the music will be distributed as part of the level export, probably also some copyright disclaimers, etc.).
20k Steam Wishlists
Last, but not least, I created a little banner image to celebrate passing 20,000 wishlists on Steam (crazy!).
Everything has really come such a long way! Even my artwork has really leveled up since the last time I tried to make a banner image like this:
Hopefully Rhythm Quest will continue to get even better from here on out...
Incredible - Rhythm Quest now has over 20,000 wishlists on Steam!
I just wanted to take a moment to thank you so much for all of your support and interest...I'll continue doing my best as I work towards a full release!
Another month, another devlog. At least I'm consistent, if nothing else. That consistency is what I've relied on for the past 8 years of development (has it really been that long!?), so hopefully it'll continue to carry me through.
Rain Girl
I shifted my focus (again!) this month, this time working mostly on new character graphics! First up is the girl from our Cocoa Moss game "Rain", released back in 2014:
This was a somewhat more complicated sprite set to put together since the umbrella adds an extra element of complexity. I tried to fit in some subpixel movement here and there to get across the side to side movement without seeming too distracting. The saturation is also upped a ton to make sure that she fits into the more colorful world of Rhythm Quest (which has a completely different aesthetic).
The jump animation was pretty simple, just removed the side to side motion and tried to retain some of the movement of her hoodie. As with some of the other characters, she has a very brief crouching frame that helps to provide a brief flash of movement when a jump starts.
Flying is just the same thing, with the wing graphics included.
This one was where I really had fun! I feel like I've really started to get the hang of how to build this sort of attack animation. Using Sayuri's attack as a reference, I made sure to make ample use of motion smears, and a little bit of stretch on impact before she settles into her main "recovery" pose. She also turns her entire body into the attack, which helps give it a feeling of weight.
Here's the winged version, this time played at a faster speed. You can start to see how the smears carry a lot of the information in those quick first two frames.
Companions
Of course, adding the girl from Rain wouldn't feel right without also being able to add the black meowmie from Rain, too:
While I could have just added this as another element in the girl's spriteset, that would probably feel too static -- I wanted there to be a bit of visual lag between you and meowmie, so you can feel like they are following you around like a little companion. I ended up building a system to accomplish just that, so now you can have a companion that follows you around, tracking and matching your movement:
Of course, now that I have this system in place, I can use it to add other little cute companions, too. Here's a little chicky that I made, perfect for following around Ducky:
Meowmie Princess
As I was thinking about other games that I've worked on, I realized that the princess from "Watch for Falling Rocks" might also be a nice fit to put into the game:
I wasn't exactly sure how her attack animation (?) was going to work, but I decided to just see what I could do with her run/walk cycle and go from there.
Here's my initial draft. The thing I really wanted to nail here was the floppy feeling of her arms, as I felt like that was a key component of her design. I actually ended up needing some extra frames of animation to express that here, so princess actually has 8 frames in her run cycle as opposed to 6 for most of the other characters!
Here's the touched-up version. Her head felt a little too static/robotic since it was fixed in place, and her dress is basically a cone so it doesn't really express the side-to-side-movement as well, so I worked on a bunch of subpixel movement to give her face some very light side-to-side motion. Having the extra animation frame helps it feel more fluid; it's nice!
For the longest time I wasn't sure what to do with her jumping and flying animations. I initially tried having her arms and feet just stay static, like with the girl from Rain, but it felt a little too stiff, especially compared to her run cycle, so I instead stumbled upon this idea where I have her flapping her arms and bicycling like she's trying to fly...I love it, haha. Part of what sells this animation is that her head is positioned an extra bit forward, like she's just really trying to lean into it. Playing the animation at a faster rate than the run cycle also helps the "flailing" feeling:
Flying has the same arm-flapping motion, but more relaxed now since she actually has wings. The wing animation cycle only has 3 frames and I needed 4 frames for the arm flaps to look good, so there's a little bit of a mismatch there, but I think it ends up looking pretty ok despite that.
The attack animation needed like a dozen different passes before I settled on something that I was happy with. This was my very first initial attempt, which honestly sucked (haha). I wasn't even sure if the idea of hurling a spiky ball would make sense visually at all, so I mostly just wanted to get something rogh in place to test with. The idea was to actually spawn a spinning ball that bounces off screen after hitting an enemy. I liked how fun the spinning projectile looked (even if it's somewhat unusual), but the throwing animation wasn't reading that well; it just looked like princess was headbutting the enemies.
For attempt #2 I tried to go for more of a baseball pitch type of motion and make it read more clearly. I made an effort to have her head not bend over so far to the right so it doesn't look like she's headbutting anymore. This was a definite improvement, but I felt like it still read kinda weirdly, the whole concept of throwing a projectile didn't seem to be making sense at such a close range. (also, if she could throw the balls like this, why can't she just attack the enemies from further away...??)
Based on the previous drafts I decided to go a different direction and just have her do an overhead slam. This both made more sense as it's a close-ranged attack (not a projectile toss), and also read better as I'm able to get a nice big 180-degree motion arc above her head. The animation definitely needed to be cleaned up, but I was happy with this new concept.
Here's the final version! I swapped out the spiky ball graphic (initially was based on the colors in Watch for Falling Rocks) to something more immediately recognizable, and made sure to add some generous motion smears so the arc above her head is obvious even though it only appears for a couple of frames. When the animation reaches the appropriate point, the ball projectile is spawned as a separate object, positioned correctly, and given some random parameters so it starts spinning and traveling through the air. It took a lot of work to get here, but I'm happy with the results!
It's been a while, so I definitely have some cool stuff to show off from this past month of work! Let's start with everyone's favorite, looking at some more new levels...
More Bonus Levels
Last time around I showed off some new bonus levels that I had written for Rhythm Quest. I've actually switched gears a bit for bonus levels and instead ended up making charts for a bunch of existing songs from my past releases. Here's a video sampler showcasing these: [previewyoutube=YP5LQPUZNfA;full][/previewyoutube] Super Mega Zero is a fast-paced song from the Super Mega Zero OST. Nothing too fancy here, just the standard charting that you'd come to expect by now, just at a high tempo. This one will really get your blood pumping!
Standing Here Alone (Expert Mix) is from the Melody Muncher Deluxe OST. The Melody Muncher songs are naturally pretty easy to translate over to Rhythm Quest as Melody Muncher was already based on 8th-note rhythms to begin with. This one is the "Expert Mix" version of the song, which works better for formulating a harder chart.
As the Cherry Blossom Falls is from Volume 2 of the Samurai Shaver OST. This one is a much slower song, so it's definitely on the easier side. As such, I made sure that it didn't feature any obstacles from worlds 3 onwards, so that it can be enjoyed pretty early on.
Let's Have an Adventure! is from my older album, The Ecstasy of Life. It's fast and upbeat, featuring dotted eighth-note ghost patterns that go right along with the melody (really glad that I decided to introduce this as a mechanic!). Some of the rhythms in this one are a little tricky, which led me to implement some readability mods that I'll talk about later in this post. There are also some 4-note dotted eighth patterns here that I had to chart with one basic enemy plus a yellow ghost. Because of this, I briefly contemplated what it would be like if I had designed the ghost enemies with variable counts, so for example with some sort of counter, that way you could have 4-hit ghosts, or 5-hit ghosts, etc. In the end, though, I think I like the simplicity of the ghosts as is. There is something to be said about trying to make the most of a limited toolset!
What Lies Beneath is from the Ripple Runner Deluxe OST and is the oldest of these songs (released 10 years ago, wow!). Because this one has a low tempo, I experimented with using some more complicated 16th note patterns. Like I've mentioned before, I want to be sparing with my use of these since they're not as easy to read, but with slower tempos I think it's okay.
Enemy Spacing Adjustment
This was a minor quality-of-life tweak/setting that was on my backlog -- you can now adjust the horizontal offset placement of enemies, in case the default (around 30 pixels) feels off to you. This comes with its own fancy little live simulated preview:
Color/Beat Mapping
This one was a pretty big undertaking that I only recently wrapped up. You can now modify the colors of obstacles and beat grid markers to help decipher rhythms, a la DDR noteskins. Here's an example of that in action during gameplay:
I don't know how popular this kind of thing will be, but as I started to experiment (carefully!) with more complicated rhythms, I didn't like the fact that some of them can be a little ambiguous to sightread on the fly, so I wanted to at least provide some sort of nod toward players who want things to be a little more readable. Floor beat grid markers (and flight path markers) can be colored according to their beat offset (red = downbeat, blue = offbeat, yellow = 16th rhythms), or according to the action on that beat (red = attack, blue = jump, green = both). This coloration will override the default color specified by the level palette.
Enemies can likewise be colored according to beat (probably most useful) or according to action type:
There are some separate settings for wings and spiked enemies, as well as an option to use a different shade of green in case you find that better (I can put more color variations here in the future if needed):
Overall one nice thing about Rhythm Quest is that color isn't actually necessary to differentiate anything, so all of this coloration stuff is hopefully just for people who want to tweak their experience or are having trouble with songs that use weird rhythms. Either way, I'm glad to have this feature finally complete, as it took a good deal of work. The preview UI for this was also perhaps more of a pain to put together than the actual feature itself! It's not even perfect (ideally it would display an actual level section being played in realtime), but this was what I came up with when I was balancing my effort/benefit ratio.
Other Odds and Ends
Previously checkpoints could only ever be placed on solid ground, and I made it a point to always chart all of my levels to accomodate that. I still think that's best practice, but for situations where that isn't a good option, I've added a little floating cloud platform for the checkpoint flag to sit on:
Finally, per a community request from a while ago, I've added the option for timing windows to scale in size based on the music speed multiplier:
While I'm personally not a huge fan of this, I also understand the rationale behind it, and it wasn't too difficult to implement. It doesn't apply neatly to all obstacles (rolling spikes...) and it might possibly cause some weird behavior since the collision boxes are so unexpectedly large, but in most cases it should (?) work okay. Note that this doesn't fix the duration of timing windows across different song sections, so faster-scrolling sections will still have (slightly) tighter timing like normal. I think that covers all the stuff I've been working on lately, minus one other thing which won't be ready to show for quiiiiteee some time. It certainly *feels* like I'm putting together a lot because of all the bonus levels, but that's mostly because charting existing songs (and not having to draw additional backdrops) is very easy. At some point I'll have to get back to actually finishing out world 6, but maybe after having made so many other charts I'll have a little more confidence in doing so...
Sometimes it can be good to bounce around and work on some different things here and there. This time I worked on some additional levels! I'm still feeling hesitant to finish out the levels in world 6, so I decided to work on some bonus levels instead. This is relatively low-pressure for me since the more bonus levels I have, the better, and they don't really have to fit a particular level of difficulty or anything. I also wanted to explore using the new yellow ghost enemies a bit to get to know how to chart with them.
Violet Hyperwave
The first level I made is called "Violet Hyperwave". It's a synthwave-styled track that uses all of the mechanics in the game except for the triplet speed-zones. Here's the video of the full track: [previewyoutube=yBsnyYEjYE0;full][/previewyoutube] For now I'm not making new backgrounds for these bonus tracks (maybe later...?), which helps make the process a little easier; I'm able to just churn these levels out in 1 or 2 days, which feels nice. Music-wise, there's a synthwave preset bank that I pull from a lot that slots right in here, and there's plenty of analog/vintage emulation to warm up the sound a bit. I really like the FM arp sound that comes in halfway through! For the chart I didn't want to do anything crazy, so most of the patterns you'll see are pretty familiar, but as I mentioned earlier, I did try to make use of the new yellow ghost enemies. By themselves, they're actually not very difficult! It's a new rhythm to deal with, but hitting a yellow ghost enemy is slower than hitting double-hit enemies, so it's not actually that hard. This makes me wonder whether I should just introduce the yellow ghost enemies at the very beginning of world 6, and then shift over level 6-1 to be level 6-2 instead (?).
Combining yellow ghosts with other rhythms is in theory, possible, but I want to be careful with them, as 16th-note rhythms are quite a bit faster than anything else currently in the game. Maybe they'll be relegated to very specific recognizable patterns...? Something else I've tended to avoid up until now is having a ghost enemy (either purple or yellow) on the same beat as a jump, but I added a few instances of that in this level. Unlike with the basic red/green enemies, there's no special visual distinction for these combo presses, which I'm not the biggest fan of, so I might have to think about whether I can come up with a nice way of making that a bit easier to read. In terms of estimated difficulty, this level clocks in at a 59 (Hard), which is a bit harder than levels 5-3 and 5-4, but not as hard as levels 5-5 and 6-1.
A Single Leaf Flutters in the Wind
The second level is called "A Single Leaf Flutters in the Wind". It a fast song featuring East Asian instrumentation such as taiko drums and guzheng, and uses a pentatonic scale, just like the songs in world 4. Here's the track video: [previewyoutube=UwwnfFw6zeE;full][/previewyoutube] So one thing with the bonus levels is that a bunch of them are going to involve mechanics that the player hasn't necessarily been introduced to in the main levels yet. I'm not yet sure how I'm going to handle this exactly (initially I was going to gate access to bonus levels, but I might actually just show a warning instead of hard-locking them), but I'm going to try and include some levels that only feature the simpler mechanics, so you have something that you can play if you go to the shop before finishing most of the game. As a plus, these can also sort of serve as "challenge missions" (a la Celeste B-Sides) for players who are finding the main progression a little too easy. To that end, I made this song that only uses the mechanics from worlds 1 and 2 (no ghost enemies, no water paths, etc), but is really fast-paced to present a challenge. It's at 135 beats per minute, so definitely on the faster side considering that it uses a ton of 8th-note rhythms. As you can see from the first few checkpoint sections, I wanted to really pull the gloves off on syncopated 8th-note rhythms here, encouraging you to read the airjump patterns in order to parse out the timings. The song itself was really fun to write! Working on new levels like this always reminds me of why I enjoy working on the game, so this has been a nice break from figuring out the complicated stuff around custom levels. I appreciate that the songs are relatively short (1.5-2 minutes), so writing them doesn't really feel like some sort of gargantuan effort; I can pull it off very quickly. For estimated difficulty, this level is a 73 (Expert) due to the note density, so it's a bit between levels 5-5 and 6-1. That's going to do it for this update. Hopefully people are happy that finally have some new levels to show off again :)
I decided to explore the mod.io integration that I mentioned last month, and have decided to move forward with it, but I was getting a little overwhelmed trying to figure out exactly how I wanted to hook up all of the export/import/login flows, so I decided to table that and work on more core level editor functionality instead.
Backdrop/Tileset Selection
Up until now all of the progress screenshots I've shown of the custom level editor have just used the level 6-1 backdrops as a placeholder, and there was no way to change that...until now! I've added a "Graphics" menu where you can select between any of the level tilesets and backdrop groups that I've made for the game:
You might be asking whether or not it's possible to import your own background images and graphics and the answer is a definite "I'm not going to implement that yet". The backdrops are authored in very specific ways, using multiple parallax scrolling layers, and are also encoded in the special 8-bit red-channel-only format, with specific color limitations to work with the palette shader...even if I did implement the ability to import your own images, I feel like it would just be a huge hassle for anyone to author for the system as is. It's just not worth it at this time.
Palette Selection
There's also a new tool that lets you change the color palette used in each checkpoint section:
Right now you can only select from the preset palettes that I've already authored for each set of level backdrops. Internally it's actually already possible to specify your own arbitrary custom color palettes, but there's no UI for it (yet?). As with the backdrops, it's probably more trouble than it's worth for people to dial in individual colors, but at least this one is more feasible (just need some sort of color picker interface), so I guess maybe it could happen someday. Panning around the level will also automatically shift the color palette to whatever section is on screen, giving you a quick way to preview the colors as they change throughout the level:
Particle Effects
Keeping with the theme of graphics-oriented editor features, I also implemented the ability to add particle effects to each checkpoint section with another new tool:
I originally designed these particle effects on a per-level basis, so I'm not actually sure they work well across different backdrops (the layering might get messed up...), but at least it's enough for me to give this a passing grade for now.
Steamworks Refactor
While I was experimenting with some of the mod.io integration -- specifically, trying to authenticate to mod.io using a Steam account -- I found it impossible to make some of the Steam functionality work using the Steamworks.NET integration that I've been leveraging until this point. I decided to spend some time ripping that out and replacing it with the Facepunch.Steamworks library instead, which had no such issues. Along the way I decided to revisit how I was handling Steam builds. Up until now I had been making a completely separate set of builds for Steam vs. non-Steam distribution, with the latter having all of the Steamworks functionality taken out via compile flags. I decided to change that and just have it be the exact same build for both cases. The Steam integration will now always attempt to connect with the running Steam client, but if you didn't launch the game through Steam, that will just gracefully fail and it won't do anything. The main benefit of this is that I don't have to make 3 extra builds every time I do a release (Windows/Mac/Linux Steam vs non-Steam), which is great. Functionally it's the same except if you run the Steam version of the game outside of the Steam launcher/client, it won't have any Steam integration. Thinking back, I don't actually know why I didn't do this in the first place; I feel like I've definitely seen this behavior in other games and it's generally better to not have divergent executables if you can help it.
Unity Upgrade a.k.a How I spent a week fixing a platform-specific bug
If you've worked in gamedev for long enough you already know how this story goes... So I noticed that on one of my Mac machines the game was totally unresponsive to input when starting up. That didn't happen on another machine I had so eventually I traced the issue down to being introduced with the latest version of MacOS Sonoma, but only in windowed mode due to some weird change with OS window size reporting code which causes Unity to choke. "Luckily" the issue has a fix in later versions of Unity, so I dug in and started downloading gigs upon gigs of Unity Editor + Module updates across two machines to see if it actually helped (it did!). It had been a long time since I had done a Unity update, not only because my project is pretty far along in its lifecycle, but also because I remember there was a specific bug with mouse input not working on the upper-right corner of the game window with certain Unity versions a while ago which caused me to downgrade (sigh). Of course, updating from Unity 2021.2.14f1 to 2022.3.19f1 came with it's own fair share of headaches. Not only did I have to install the new version of the editor, along with the support for WebGL, iOS, Android, etc builds, but I also had to update my Nintendo Switch SDK environment (queue up another big chunk of downloads). The Android build pipeline has its own set of dependencies and OF COURSE Unity 2022 requires a different version of those than Unity 2021, so go ahead and download the Android SDK/NDK/JDK =but= don't you dare download the latest version because Unity won't work with that! You need the specific version that Unity 2022 wants... And of course, a major version update means I had to debug functionality changes, yay! Not only did I have to debug an issue with the audio scheduling, this also caused some of my plugins to stop compiling, such as the one I was using to bring up an OS-native file picker dialog (why is this not built-in to Unity...?). And oh, it turns out the mod.io integration that I was using doesn't actually compile for WebGL, also for some reason the Discord plugins weren't working correctly on OSX anymore?? Wait, why does the Discord SDK plugin have no documentation and why does it provide two conflicting Mac library files with different extensions? Wait, these two files are identical copies of each other??? Anyhow, I ended up with this task checklist over the course of that week:
-
try updating unity to resolve input bug on sonoma
tried updating unity but now music sync is broken
Test to make sure build is still functional on all platforms
How to port Modio to webgl: https://github.com/modio/modio-unity/compare/main...ComputerKim:modio-unity-webgl:main
the fuuckkk? when starting osx build, main menu animations are fast, until you go into another menu - bug with cached beat lookups
Lots of fixing build stuff with unity / plugins / blahhhhh
Mac build discord lib WOW discord is dumb
Downloaded new Android sdk/ndk/jdk
Fix ios/android/switch builds (steamworks compile)
More switch/android updates
Finally got both android and switch builds compiling
Fix ridiculous unity scriptablebuildpipeline issue causing builds to take hours
The Rhythm Quest Demo has been updated to version 0.30.0! This patch includes some small but important improvements as well as a number of bugfixes for community-reported issues. The game internals also received a refresh with a new Unity version as well as some plugin updates (hopefully not breaking anything!).
I forgot to list it in the official changelog but this build also fixes a critical issue on later versions of Mac Sonoma where the game was unresponsive to inputs.
Full changelog:
Version 0.30.2
- Fixed scheduled sounds not playing correctly after respawns
Version 0.30.1
- Fixed wrong version of some east asian glyphs being used
- Fixed broken "Audio Mods" menu in main menu scene
Version 0.30.0
- Updated Unity version
- Fixed issue on later Mac Sonoma versions where the game was unresponsive
- Added pitched coin/metronome sfx to match the key of each level
- Added ability to fast-forward endlevel sequence by holding menu button (escape)
- Fixed "bumpy" camera movement across ramps/beatgrids
- Fixed missing default keybinds for 'B' and '/'
- Re-fixed certain accessibility audio cues sometimes not triggering
- Fixed blurry horizontal line when using smooth font in shop display
- Tweak line height for unicode pixel font
- Tweak font pixel snapping logic
- Camera pixel rendering fix for odd-pixel resolutions
- Fixed offbeat pulse timing on respawn
- Replaced and refactored Steam integration
- Running the Steam build outside of Steam will no longer restart the game via Steam
- Allow changing resolution in exclusive fullscreen mode
- Added several more windowed resolution options for large displays
- Fixed incorrect description for "Blur" screen filter
- Fixed demo end screen showing up incorrectly after data reset
- Fixed menu pulse animation speeds sometimes being incorrect
My most recent work on custom levels has been this screen where you can import new custom levels, either ones that you've already downloaded, or by typing/pasting public URLs where they're hosted:
Internally this importer needs to handle a bunch of different things -- fetching the level package, making sure that it doesn't already exist in your levels folder, and extracting the appropriate files from the archive. It's all more or less working now, though the "Browse Steam Workshop" button just takes you to an empty page (more on that later)... It currently also supports file drag-and-drop, though only on Windows (sorry, this is a really OS-specific thing and I couldn't find any other implementations!).
Level End Fastforward
I also took some time to implement a small quality of life feature -- being able to fast-forward through the level end sequence:
This is done by pressing the menu button/key (escape on keyboard, start on gamepad), since you can't bring up the menu during the endgame sequence anyways. For mobile and mouse players, the "pause" icon in the upper-right corner fades into a fastforward button that has the same effect. I'm just doing this by increasing the global timescale of the game to 3x whenever that button/key is pressed. In an ideal world I would fast-forward only the UI animation here, but that isn't really easy to do with my current implementation, so I think I'll just live with this.
Object Pooling
I don't have any visuals to show for this one, but I also went ahead and did a nice performance optimization for the level editor. If you'll remember, the editor works by regenerating the entire level from scratch every time you make a change. It isn't super slow, but you can definitely notice a hitch every time you make a change when your level gets longer. A lot of the time here was spent reinstantiating all of the level objects from scratch (including not only the obstacles but also every segment of the ground/terrain, timeline helpers, etc), immediately after the old ones were all destroyed and discarded. This is super wasteful as we could have just reused a lot of the same objects that we already had, as long as we reset their state properly. To address this I now have an object pooling implementation in place so that instead of destroying the objects, I disable them and mark them as available to be reused. Then when the level is re-generated I can just grab those same instances and reuse them once I reset their state. My implemention isn't super robust, but it also doesn't need to be since level regeneration is essentially the only place in my game when objects need to be destroyed and recreated like this. This took a bunch of (mostly not too difficult) refactoring to get working, but there's a noticable difference in responsiveness now when changing larger levels. Yay!
Custom Level Publishing
Looking forward I'm going to be taking a little bit of time to experiment with solutions for custom level publishing and browsing. I had assumed for a while that I was going to look at integrating with Steam Workshop to give players an easy way to share and browse each others' levels, but I'm now having second thoughts about that since it won't work well across other versions of the game (itch.io, mobile, switch...). It's probably worth doing some exploration here, so I'm hoping to see if any other solutions may fit my game well. For example, I may be able to integrate with mod.io and use their service as a way to host and manage user levels. I could either use their built-in whitelabel in-game content browser, OR go the extra mile and implement one of my own (say hello to another mountain of UI work, future me...). In theory this could work cross-platform, but I'll have to start by dropping in their integration and seeing how it actually function in practice.
It'll probably be good if I can try to write my devlogs more concisely so they don't feel like such a chore to post...let's just get right into it.
Dotted Yellow Ghosts
Let's start with the most interesting news first: while playing around with the level editor I realized that it felt really awkward to chart certain styles/sections of songs because of the lack of 16th note rhythms in Rhythm Quest. All of my obstacles (minus speed zones) are done at the granularity of half-beats (8th notes), which has been fine so far since I've been able to tailor all of my music to it, but a lot of the music out in the wild features other sorts of rhythmic patterns. I don't want to go wild with facilitating =all= possible rhythms; I feel like Rhythm Quest chart design is good when it's constrained, just like DDR charts are at their best when they capture the "flow" of a song rather than trying to stepchart e-ve-ry in-di-vidual syll-able in the vo-cal ly-rics. But I decided to try adding in a new variant of the multihit ghost enemies:
These go at dotted 8th notes, so exactly twice as fast as the regular purple ghost enemies. They're not only colored differently but have a little orb accent to distinguish them visually. Right now these are the only obstacles that are allowed to be offset at this granularity. You can, of course, mix these in with other obstacles if you'd like to be extra devilish, but I don't plan on doing so very often. I think if your chart makes frequent use of 16th-note granularity it might be a sign that you should chart it as double the tempo. I haven't yet decided if or how these new ghosts should be incorporated into the main Rhythm Quest levels, but if I do include them they'll show up in World 6. It might actually be nice to do so as it would give players something else to learn besides the triplet speed zones, which might get old after a while if all 5 of the World 6 levels are primarily about them. Hah! See, it's actually not a bad thing that I've been putting off the rest of World 6 for so long...;P You'll also notice from the above gif that I've added a debug toolbar with shortcuts that you can use while testing your levels to quickly jump between checkpoints and toggle autoplay. Basically the same tools that I've been using all along to develop my own levels, except as first-class UI elements instead of hidden developer-only shortcuts :)
Camera Scrolling Tweak
The beatgrid markers break up the otherwise-continuous slopes in levels, and therefore, the camera followed suit, panning smoothly across the slope but the pausing for a brief moment at each flat beatgrid marker:
I didn't want to change the design of the beatgrid markers, but I'm trying a change where I instead modify the camera plotting so that it ignores them and instead acts as if the slope is continuous all the way through. This makes the camera scrolling smoother, with the downside that the player character's y-position on the screen doesn't stay centered for that brief moment.
It's a subtle change, but I think (?) I like this new version better. The little camera "bumps" were honestly something that I just got used to a very very long time ago and didn't think very much of...I think I'll have to play some levels here and there and see if the new version bothers me at all.
Hierarchical Folder Display
Last time around I showed off the custom level browser, which featured the ability to drill into subfolders of the root custom level directory:
I've tweaked this so that folders (and nested subfolders) are all shown in the same singular display instead of jumping to a new set of buttons. I use indentations and arrows (like in a Windows Explorer view) for this, and animate the indented buttons as they appear and disappear:
Preview Clips
Back in the editor UI, I've added a new section to the music loading menu where you can specify a section of the song to use as a preview clip when browsing custom levels.
Thankfully I was able to reuse the same audio display component that I already made, and just add some new handles to it, but this wasn't really a trivial task; there's a lot of subtle things that need to happen to make this editor feel good, such as automatically scrolling when you drag past the end of the window, automatically clamping the preview clip length (right now it's a minimum of 1 second and a maximum of 20 seconds), etc. I've gone back and forth a few times on exactly what the format of levels should look like and what different file types to use, as well as how music, levels, banner images, and such should be encoded. Initially I imagined the preview audio as simply being played as a segment of the original song, but I've decided to change that and instead I export the preview as a separate .ogg vorbis-encoded audio file -- this makes it easier to stream directly from disk. In implementing this, I had to look for a way to export that segment of audio data and vorbis-encode it. Surely there must be a nice, cross-platform, performant C# library that does exactly that, right? Unfortunately, the answer this time was "not quite", so I had to do some wrangling to get what I wanted, but it's working now...
Just Keep Working
As always, there's still like infinity more things to work on. I have to add the new dotted eighth note ghost enemies to the level stats displays, I have to come up with a new UI for importing levels from zipped archives, and of course there's artist verification flows and Steam Workshop integration. I do want to give a shoutout to Rhythm Doctor as I've been referencing their level editor and custom level browser regularly to compare how they handle things. It's nice being able to look at an example of how a different team solved the same problems, just to have a jumping-off point. I'm not going to lie, I was feeling a bit burnt out on Rhythm Quest in past weeks. Something about the mountain of work that seems to be ahead of me as I tackle this whole custom level endeavor (this is why I didn't do it in the first place...), but also just the fact that another year went by and I didn't finish my game (understandably so, but still...). I know there are a lot of expectations and external desires about Rhythm Quest and I've felt them weigh heavily on me at times...but I think it's unhealthy for me to give them too much space in my head, it's best if I just focus on finding the "fun" in development and try to minimize the pressure that I feel. Fortunately, I seem to have been able to do that in more recent days!
The Rhythm Quest Demo has been updated to version 0.29.4! This patch features more bugfixes and adds a quality-of-life improvement for input bindings that should help players who are using high speed mods. Full changelog: Version 0.29.4 - Multiple key/gamepad bindings for the same action now function independently to allow for easier multipresses - Added "blur" background filter - Fixed keyboard button focus being lost after closing menu overlays - Fixed jump logic when overlapping multiple obstacles - Fixed jump miss sound playing on hit when sound scheduling disabled - Fixed certain accessibility audio cues sometimes not triggering - Fixed doublehit enemy inputs sometimes not respawning on input timeline - Added support for localizing "Congratulations" text on demo end - Slightly tweaked initial camera pan on load
Oops, I missed my monthly devlog post for November. On the plus side, I've been doing a lot of good work over the past few weeks! I've found that my time and motivation for Rhythm Quest tends to oscillate a little bit over time -- sometimes it's because other parts of my life are busy, other times it's because I'm working on something that's tougher to get motivated about. But I do my best to just keep plugging away slowly and steadily... It's a little crazy to think that ~3 months ago the Rhythm Quest level editor didn't exist at all! Now we've got an entire editor tool palette UI, notifications, undo/redo, saving and loading, input timelines, waveform displays, animated preview sprites, automatic beat detection, level validation, ... There are like, infinity different things that go into custom level support, which is why I initially wanted to put it off until post-release...but I'm actually having a lot of fun developing it all, so that's great!
Custom Level Browser
Most recently, I've been doing a bunch of work on a custom level browser that will let you navigate through the custom levels you've downloaded:
A lot of it is already working pretty well! It's able to parse all of the song files in the custom level folder, show them as options, and even allow you to drill down into subfolders in case you want to organize your custom levels into various different directories. Since the menu is dynamic (can have any arbitrary number of buttons), I decided to go with a scrolling menu layout. It works simply enough with keyboard/gamepad, but if you're using a mouse you can also scroll to a given song by clicking it (or even use the scroll wheel!). You'll notice on the left panel that selecting a level brings up a bunch of details about the level, including a stats display of how many obstacles of each type are in the level. These weren't too hard to derive and save as part of the level metadata, and they should hopefully provide a nice way of judging the flavor of a chart, or in case you want to just avoid any levels that have speed zones or green enemies or whatever. It's a little busy, in terms of the visual look, but I do think that it's useful information to display. (Maybe there could be a toggle for it?) There's still some additional work that needs to be done here...for one thing, I'm not a huge fan of the way that folders are handled...instead of drilling into separate submenus, it would probably make more sense to just have all songs be listed as one (indented?) list and then be able to open and collapse folders from there. I also don't have a way to access the shop or change characters/speed settings easily from here yet. =( I also need to think about handling music previews...the easy thing to do would be to just load the entire song on-demand and start playback, but that's really slow, so I'll have to instead stream the music from disk. Of course, ideally you'd be able to edit which region of the song gets played in the preview...
Difficulty Estimation Curve
There's a new "estimated difficulty" scale from 1-100, which is automatically calculated based on the density of actions required for the level. Of course, it's hard to be very precise in determining how much trouble any given player will have on different levels, but I figured it would be nice to at least have some rough estimation available. Having a difficulty scale go from 1-100 is actually an interesting conundrum because there's no theoretical limit on how difficult a song can be (you can just add obstacles on every half beat and increase the tempo higher and higher). Right now the primary heuristic I use for determining difficulty is the number of button presses required per second, which ranges from 0.86 for level 1-1 all the way to 3.79 for level 5-5 (and probably a little higher through the end of world 6). I could of course just pick an arbitrary maximum limit (5.0?) and then come up with a linear scale, where 0 presses/second = 1 and 5 presses/second = 100, but I don't think that would be a great scale, because the differentiation within the low and high ends of the scale would be pretty useless. Does it really make sense for level 1-1 to have a difficulty of 17/100? It feels like the lower numbers below 15 would just never get used. Also, perceived difficulty isn't really linear based on presses per second either -- the jump between 1 and 1.5 presses per second isn't nearly as big of a deal as the jump from 3 to 3.5 presses per second. So instead of a linear mapping, I tried to find some sort of non-linear function that I could use to estimate difficulty. Ideally I wanted something that would ramp up very slowly from 0, then increase more rapidly toward the middle end of the range, and then taper off so that the crazy charts with 5 or 6 presses/second can just all be lumped in the 90s or whatever. In other words I wanted something that kind of has horizontal asymptotes... If you've studied enough trigonometry (sorry, yes, math is back) you'd remember that the graph of y = tan(x) has vertical asymptotes. Which of course, means that the inverse, y = arctan(x), has horizontal asymptotes. The inverse tangent function graphs like this:
Which is pretty much what I was envisioning! All that was left was to apply some scaling constants to shift and scale everything around, and then I had my non-linear difficulty curve from 1-100 (currently caps out at around 4.8 presses per second):
Here are some values and what they map to, so you can get a sense of how this non-linear difficulty compares to what a linear mapping would provide:
Level | Actions/Second | Linear Difficulty | Arctan Difficulty
1-1 | 0.859375 | 17 (Easy) | 3 (Easy)
1-5 | 1.560284 | 31 (Medium) | 9 (Easy)
2-3 | 1.718750 | 34 (Medium) | 11 (Easy)
2-5 | 1.984972 | 40 (Hard) | 17 (Easy)
3-4 | 2.195684 | 44 (Hard) | 20 (Medium)
4-5 | 2.761905 | 55 (Hard) | 41 (Hard)
5-5 | 3.345877 | 67 (Expert) | 70 (Expert)
| 4.000000 | 80 (Impossible) | 93 (Impossible)
| 5.000000 | 100 (Impossible) | 100 (Impossible)
Of course, this is just a first attempt, so it's definitely possible that it'll need some tweaking or shifting around...
Level Metadata Editor
I also put together this screen for inputting all of the metadata that you see in the level browser:
Not too much to say here except that I had to program in the ability to load in a custom image that you provide, and then automatically crop it to the right dimensions when displaying it. This image will also be used for if/when you upload the level to the Rhythm Quest Steam Workshop (have to implement that at some point, too...).
Lots of Other Stuff
The new level browser was the main exciting piece of work, but I've also been doing a ton of other things as well...for instance, I implemented a blur filter for the background that you'll be able to enable in the screen filter settings:
In general this sort of quick and simple post-processing effect tends to break the clean look of pixel art, but I won't judge you if you just think it looks cool and want to just enable it for normal gameplay. It does tend to help with foreground/background readability, for sure. I also implemented the ability to drag obstacles around using a move tool. This is a little less straightforward than you might think...you're essentially doing a delete followed by a new action, but you need to validate that the new action will make sense after the delete and handle the preview accordingly.
The same tool also lets you resize flight paths, water zones, and speed zones, by dragging the left or right side of them. Of course, the other obstacles need to all adjust based on the change...
The level end object automatically moves when you insert new obstacles past the end of the level. You can also move it around now!
Water zones and speed zones will now automatically merge together if they overlap or are placed end-to-end:
The jump tool now features multiple subtools for specifying whether you want to prefer (when possible) placing winged air jumps, grounded jumps, or grounded jumps with a vertical height difference:
What else...? I'll preview this in the future, but I also added an initial set of editor sound effects, so you can hear a nice little [pop!] when you place something. There was also a ton of work done on minor things with level validation logic, as well as general UI polish (the way that grid snapping worked, etc.). I still need to release this in the next patch, but I also finally implemented the ability for separate keybindings to work independently instead of being summed together, which means it'll work if you press a second attack key without releasing the first one -- this one should help players out with faster charts or if you're playing with a high speed mod.
More Work to Come
As we approach the end of 2023 I want to thank anyone who cares about my game enough to keep reading these devlogs. I may or may not take a little break from updates over the holiday season, but either way I'll be continuing to chug along as always with slow progress toward the mountain of work to be done. Sometimes I start to get really self-conscious of all of the expectations people must have about the game -- like, ~18,000 Steam wishlists and ~700 Discord members, that's a little ridiculous for my little project, isn't it? Cultivating an active community presence is really not a strong point of mine, but I'm optimistic that when the time comes the level editor will be able to inspire a lot of new content and excitement. We're still a long ways from that, though, so I'd best not count my chickens before they hatch...
It feels like I've been really inconsistent with these devlog entries, but looking back it seems like I have been basically putting out one per month quite regularly, so maybe it's not as haphazard as I thought!
Music Adjustment UI
Last week I talked about the waveform display that I implemented using some fancy parallel burst compilation. My main use case for that functionality was to build out a UI for loading a music file and (more importantly) specifying and adjusting the tempo and start time for that file. After a lot of work, here's what I ended up with:
It's working pretty well! You can preview the song, zoom into the waveform, and drag around the beat markers to adjust the timing to your liking, or simply type in the tempo and start time manually if you already have it on hand. There's also a built-in metronome function so you can verify your work. This seemingly-simple UI widget really involved a lot of different moving pieces and little quality-of-life touches. There's smooth scrolling and zooming, and I needed to make sure that the beat markers appear and disappear as you scroll through the track. Dragging the beat markers past the end of the current view also makes it scroll automatically, and some of the beat measures fade out if you zoom out far enough (to avoid clutter). It's worth noting that I took some of the functionality that I developed along the way and added it in other places. For example, since I needed to implement a metronome to give audio feedback on whether your song timings are correct, I also added that as an option to use in-game. I also added the waveform display to the background of the input timeline while editing, to serve as an additional reference:
Automatic Tempo Detection
While the "respawn loop" button does nothing at all yet (that is supposed to be a separate dialog that allows you to provide an optional short audio loop that will play during respawns), the rest of this big devlog post is going to be talking about that other rather inconspicuous button, "Auto-detect". You might have already guessed it, but clicking this button performs a whole bunch of math and signal processing in order to procedurally analyze the music file and attempt to automatically determine both the tempo and start time of the music file. Here's a short video of that magic in action! [previewyoutube=HGlDK7AZYrs;full][/previewyoutube] It's definitely not perfect, and it takes a few seconds to churn through all of the calculations (video above has that processing time skipped), but it actually does a pretty good job in a lot of cases! It looks easy since it's just a single magic button, but I ended up diving quite deep into the rabbit hole of audio signal processing and beat/tempo detection algorithms in order to implement this... Before I start explaining the methodology here, I wanted to point out something that might surprise you a bit. You might think that my goal with this automatic tempo detection is to make it work so well that manually setting the timing data for a song is no longer necessary. That's a nice hope to have, but I'm not really confident I can do that. On the contrary, I actually think for me that it's the other way around: I want the manual beat-setting interface to work so well that the automatic tempo detection is unnecessary! In that sense, you could say that the automatic detection is really just a secondary nice-to-have feature that I could honestly have dropped altogether. But, I'm a perfectionist, and I found the problem interesting, so I dove in...
Resources and References
While Unity provides basic access to audio data (get the samples out of an audio clip as a looooong array of floating point numbers), doing any sort of more involved operations (normalization, convolution, filtering) is something you'll want to use a specialized C# library for (don't reinvent the wheel!). NWaves was by far the most user-friendly and sensible one that I personally found (though I did end up re-implementing particular parts using Unity's job/burst systems, for performance reasons). NWaves was a huge boon for me and let me do things like Short-time Fourier Transforms without having to learn a bunch of new complicated math and attempt to implement it from scratch. Also, I rarely find myself doing this, but for this particular problem I ended up consulting a whole bunch of research papers that have been written about the topic, some of which were extremely helpful. "Evaluating the Online Capabilities of Onset Detection Methods" by Bck et al in particular provides a pretty nice survey of various approaches to detecting note onsets -- this is not 100% equivalent to tempo detection but is closely related. Accurate Tempo Estimation based on Recurrent Neural Networks and Resonating Comb Filters, also by Bck et al, was one of the other more helpful reads.
Detecting a Tempo
The process of tempo detection basically consists of the following steps at a high level: Do some preliminary processing on the audio signal to prepare it for further steps Run the audio through some sort of detection function(s) that is designed to highlight beats/onsets Determine what tempo best lines up with the detected beats
Preliminary Processing
This step is pretty boring, we basically make sure that the audio is normalized, and converted from stereo into mono. I also add some silence to the beginning as a buffer and scale the audio levels a bit (apparently working in a logarithmic scale tends to perform better). In some approaches the audio is filtered and split into multiple parts -- for example one copy with only low frequencies, another with mid frequencies, and another with higher frequencies. I didn't find this to work super well for me and it also adds additional processing time since each filtered copy needs to be processed separately, so I just stuck with a single unified copy of the audio. But it's worth noting that filtering is a relatively common technique here, and your mileage may vary.
Spectral Analysis
Now we need to take the music track and come up with some way to detect onsets or "strong attacks" in the audio. The first thing you might think of is to look at the places in the audio where the volume is loudest. That might work decently well for a single section of music with an isolated instrument that looks like this:
But for a loud song that has many different elements going on at the same time, the waveform looks more like this:
Part of the problem here is that all of the different sound frequencies that make up the song are represented together in a single waveform (one big array of floating point numbers), so it's almost impossible to isolate different musical events. The Fourier Transform can help us here by converting a single audio signal into a breakdown of the different frequencies that comprise that signal. If you've ever seen any sort of spectrum visualizer like this one, the Fourier Transform is what's being used to evaluate how tall each "bar" in the spectrum is:
Here's the same complex waveform from earlier above, but this time displayed alongside its spectrogram (generated using a form of the Fourier Transform). Notice how you can not only see a vertical line pattern (corresponding to the big kick drum hits), but you can also see distinct horizontal bars that correspond to different notes being played on the lead melody synth.
Onset Detection
NWaves can perform "Short-Time Fourier Transforms" in order to generate the equivalent of the above spectrogram, which is great. However, we still need a programmatic way to get from the spectrogram to some sort of evaluation of where note/beat onsets are. There are various approaches to doing this. In fact, some of the best results are done using neural network techniques...which unfortunately are a little too far out of my wheelhouse for me to implement. Instead I went with a simpler (well, kind of) approach, detailed in this paper. I basically take each of the sinusoidal frequencies (that are given by the Fourier Transform) and at each point in time, evaluate the change in energy and phase of that frequency. So if the energy level in a certain frequency goes up suddenly, that's a good indicator of a note starting. Similarly, if the phase of that frequency changes significantly, that's also a indicator of a note starting or changing. I add up all of the "change amounts" for every frequency and end up with a single number for that moment that describes "how much did the frequencies change in total at this moment?" Here's a rough visualization of what that "total change amount" looks like, along with the other signal representations. The yellow spiky line is the raw "total change amount" data that I use for the rest of the computations, the green graph is just a smoothed out version of that to show that it does indeed map onto the beats of the song.
Here's a simpler example where you can see it a little more clearly:
In some approaches, you take this "change amount" and try to run some sort of thresholding to pick out discrete onset/beat events. I chose not to do this and instead leave the signal as a continuous one. As you'll see in the next section, we don't actually need to pick out discrete beats in order to find the tempo. (One advantage of this is that we can also make use of information that lies in between beats.)
Comb Filtering
The next step is to look for regularities in the onsets (the yellow graph) so we can determine the actual tempo. The way I do this is simply to try many possible tempos (all the way from 60 BPM to 180 BPM) and see which one(s) "matches" the graph best. How do we measure how well a given tempo "matches" the graph? The way I chose (referenced in some of the literature) is to use comb filters. A feedback comb filter is basically a fancy way of saying we are going to put an echo on the signal. I run the onset graph through many different comb filter delays (echo lengths), corresponding to each candidate tempo. The hope is that if the delay matches the actual tempo of the song, then we end up getting more feedback resonance than if not, so the resulting signal amplitudes will be higher. That ends up being true! In the below graph the blue/cyan line represents the comb filter output for an incorrect tempo, and the green line represents the filter output for a matching tempo.
Both of them start out relatively similar, but you can see that as the resonance kicks in, there's a feedback effect on every beat (since there tends to be note onsets there more often), which causes a higher signal amplitude. After calculating the comb filter output for all possible tempos, I simply go through and choose the tempo whose comb filter values are highest more than all of the other ones. Sometimes there is more than one different tempo that is higher than the rest -- often times this happens when the song has strong syncopated patterns, so for example instead of 120BPM the detector could also find 160BPM as a valid candidate. Right now I just have it pick the top one, but in the future I could build some sort of UI to suggest multiple tempos when it isn't quite sure.
Detecting Beat Offset
Now that we have our song tempo calculated, the next order of business is to try and figure out what the beat offset is. I'm still working on tweaking this part a little, but what I do right now is take the comb filter output and process it even more using averages and thresholding. I end up with a more discrete selection of peaks:
I use various rules for selecting these peaks -- for example, the signal at that point has to be much higher than its average, it needs to be the highest point in some proximity, and there can't be two peaks too close to each other. Note that this attempted "peak selection" isn't perfect, and usually tosses away some otherwise-relevant information (which is why I didn't do it in the previous step). But as long as I get "enough" of the correct beats, it's fine! The last step is simply to go through all of the possible beat offset values and see which one of them lines up most with the peaks from this step. I just do this by adding all the on-beat amplitudes that would result from a given beat offset.
It Works!
Amazingly, the entire system works fairly well most of the time! It still has some troubles with certain songs, and often the start time is wrong by half a beat (I'm guessing this is because off-beats tend to be featured prominently as well), but there are always going to be exceptions like that. Again, even when it's wrong, it usually has the correct option as its second or third choice. After I ironed down the main algorithmic implementation, I ended up doing a pass over most of the computation logic and rewriting it to make use of Unity's parallel job / burst compilation system, which helped speed things up drastically. Right now the algorithm looks at the first 30 seconds of a song, which is over a million floating point samples, so there is quite a lot of data to parse through! Before the optimizations this process was taking over ~10 seconds, but it's now down to just a couple of seconds at most. I could go on and on trying to fine-tune all of the different parameters in the system (there are a lot...), and I actually found a couple of silly bugs and noticable improvements even while writing this devlog (hooray!). However, it's about time that I call it a wrap on this particular system and get back to making sure that everything else in the editor is well-integrated...
The Rhythm Quest Demo has been updated to version 0.29.2! This patch features some more bugfixes, but also adds a few in-game mods including an input timeline and metronome. Full changelog: Version 0.29.3 - Tweak/fix grass in world 1 level select Version 0.29.2 - Fixed a softlock bug that allowed getting stuck in walls - Slightly lowered audio encoding quality to reduce build size - Fixed incorrectly-centered vertical backdrop scaling behavior - (This fixes an issue where the foreground in 3-1 could cover more than expected) - Fixed a bug where keyboard menu selection was not restored for certain menus - Fixed a bug where the player could run off the level if the game is paused after a level end - Fixed perfect medal animation offsets in world select for demo version - Added "Run in Background" setting - Added "Input Timeline" visual mod - Added "Metronome" audio mod - Fixed tutorial being skipped if 1-1 is restarted during it - Fixed scheduled sounds being very slightly offset in some cases - Fixed end level medal particles having different speeds depending on resolution - Allow pressing any key in calibration screen (not just space) - Fix subpixel render misalignment of player in game - Fix subpixel render misalignment of ground in menu - Disabled screen refresh rate label (currently not working) - Localization updates
I've been continuing to try my best to clock in some hours toward fleshing out the Rhythm Quest level editor! Here's some quick demos of what is now possible in the editor:
More Tools
The level editor has a LOT of functionality that needs to be built out, so a lot of the time was spent just toward implementing additional tools, which you can see in the demos above. A no-so-short list of what I ended up implementing tool-wise:
- "Insert Floor" tool to insert blank sections of track
- "Delete Floor" tool to delete sections of track + included obstacles
- Undo/Redo, (with Ctrl+Z / Ctrl+Shift+Z/Ctrl+Y keyboard shortcuts)
- "Jump" tool which inserts either ground jumps or air jumps
- When inserting or deleting jumps, the other obstacles adjust (e.g. ground jump becomes air jump)
- Visualization for jump arcs (also provides something to click to delete jumps)
- "Flight Path" tool lets you click and drag to insert flight paths
- "Ghost Enemy" tool for purple multi-hit enemies
- "Edit Ramp" tool for adjusting the slope of ground / flight paths
- "Checkpoint" tool for adding checkpoints
- "Spike Enemy" tool for adding spike enemies
- "Scroll Speed" tool for adjusting the relative scroll speed of each section
- "Water/Speed Zone" tools let you click and drag to insert those zones
Character Preview
Something cool you'll notice in the gifs is an animated character preview that goes through the level. It's not too fancy, as it doesn't actually interact with any of the obstacles, but it's a fun little visual preview that didn't require a ton of effort to make happen. During level generation I actually already calculate the entire player path through the level -- this is needed to understand where to place obstacles such as coins, flying enemies, and the like. (This is actually just one of several such calculated paths; there's another one for the camera, for instance) You can see that in this debug view in red here:
Since I already have this path on hand, it was simple to just create a preview sprite that traced along the same path at the correct speed. The only other work I needed to do was to trigger the appropriate animations based on the obstacle timings. There's no collision detection or anything like that, so it's very simple!
Tooltips and Notifications
Another thing you may have noticed is that I've added tooltips for the different buttons in the editor! Right now most of them are on a big palette on the left (with some additional ones on the bottom-right), and they're just icons, so I thought it would be nice to show a little tooltip when you over over each button:
Along with that, I also implemented a notification system at the top of the screen! This not only gives you a little more context for how to use each tool (some of them involve dragging, others just require clicking), but also displays information on actions such as undo / redo history:
I wanted this to look nice, so it supports the ability to either show multiple notifications at once (automatically scrolling them as they fade out), or replace an existing notification. The way this works is that there are different "slots" for notifications, so for example if there's already a tool selection notification showing, it'll just replace that existing one instead of showing a brand new one.
Menu Hookup
I also took a second to make the level editor accessible from the main menu:
Right now the "Custom Levels" menu is empty otherwise, but eventually there will need to be some way to import / browse existing levels (more work for later...). As a side note, the buttons in the main menu now adjust their height dynamically instead of always being the same. This is because the main menu actually has more or less buttons depending on a whole slew of factors:
- The "Quit" button isn't shown on web or mobile builds
- The "Wishlist" button is only shown on demo builds
- The new "Custom Levels" button may also have some restrictions (?) (TBD)
Waveform Rendering
I also ended up taking a little detour figuring out how to take a music file and calculate + render a waveform into a texture to display on the screen:
This task involves a surprising amount of technical finesse! A 1.5-minute song has some 4 million stereo audio samples, so obviously trying to plot and process all of that data is a little tricky to do in a performant and sensible way. Trying to draw a line between each of the 4 million points is pretty futile, so I didn't even bother doing that. Instead, a common approach is to separate the audio samples into chunks -- in this case, one chunk for each column of the final output texture. Then for each audio chunk we can simply take the min and max signal amplitude for all of those samples and draw a line representing the magnitude of that. (you could also use other metrics, such as the average signal magnitude) Because you're processing 4 million samples, this works OK, but is still a little slow. The other problem is how to actually draw all of the lines / pixels into the resulting texture in a way that's more efficient than simply calling Texture2D.SetPixel(...) thousands of thousands of times. This is a rare case where I actually dug into the technical details of how to optimize the performance here -- luckily, there's a Unity blog post from earlier this year that describes some details of how to write to textures efficiently, and there's a link provided to some sample code that leverages the parallel job system and burst compiler to speed that up. It seems a little bit black-magicky, but it did the trick and I'm able to generate that texture on the fly without a noticeable hitch in framerate (woohoo!). Right now since I'm just testing, the waveform appears as an ominous black-and-red texture behind the stage (haha), but eventually I hope to integrate this display into some sort of UI (?) that will help you tune the BPM and beat offset of the audio that you load into the editor. In case you're wondering, the texture is red-and-black because I'm using the lightweight one-byte-per-pixel R8 texture format (the same one I talked about in my backdrop optimizations post).
Next Steps
Despite all of the good work that I've been able to accomplish, there's still no shortage of work needed in order to bring the level editor into a fully-functioning state (not to mention a slew of extra quality-of-life features that I've thought of already). Chief among those is the menu interface for adjusting song timing properties (BPM/beat offset), which is why I started looking into the waveform rendering tech...but, there's also things like export/import functionality, backdrops, color palettes, particle effects, (the list goes on...). Hopefully I'll have even more to show off the next time I write an update!
I've been kinda radio-silent over the past month. The first part of that was due to simply not getting that much done, but in the latter half of the month I had a better (and more exciting) reason for foregoing updates: I've been hard at work building out the initial skeleton of the Rhythm Quest Level Editor!
Changing My Mind on the Level Editor
Those of you who have been following along for a while will probably know that I've always had plans to build out a Level Editor for Rhythm Quest, but that I had initially pegged it as a post-release feature -- something that I'd build after the release of the initial game. I wanted to make sure that I focused my efforts on bringing the main game to a finished state, even if it meant sacrificing some extra features that couldn't make the cut. Building a level editor is no small task and I wasn't even sure exactly how I would be able to do it. Would it be an entirely separate application? What would the interface look like? Would I have to refactor the entire level generation process again? What formats would levels be saved in? How would I load in external music files? How should I handle copyright disclaimers? Despite my strong belief that Rhythm Quest levels work best when you have someone (like me) carefully crafting both music and charting together, and ensuring that the levels follow canonical Rhythm Quest charting conventions, I understand that the level editor is a popularly-request feature and could really help to bring excitement to the game in a multitude of ways. But the reason that I decided to change my mind and start working on the level editor now (instead of post-release) is much simpler: I simply got interested in building it.
On-Demand Level Generation Like so many other challenges that I've come across in working on Rhythm Quest (like the new coin/medal rework, which I'll talk about later), the level editor conundrum was one of those things that sort of just sat in the back of my mind for a long time until I had finally devoted enough spare idle cycles to it and was beginning to have some ideas of how to actually get started working on it. There is of course something to be said for keeping feature creep down, but I've learned that "working on whatever I'm excited about" is usually a good approach for keeping me going. Rhythm Quest levels are authored as strings of events. Here's how the charting for level 1-3 is written out, for example:
"Obstacles": [
"#... .... *... *... *... *...",
"#^.^ .-11 .^.^ .+11",
"#+.- .*.* .+.- .*.*",
"#.11 ..^1 ..^^ 1111",
"#*.* .*.* .111 1.11",
"#.^^ ..** ..^^ ..**",
"#.^^ ..** ..^^ ..**",
"#1.+ .1.^ .1.- .11*",
"#1.+ .1.^ .1.- .1**"
],
The different symbols here are a representation of different types of events. '#' represents a checkpoint, for example, while '1' is a basic enemy, and '^' is a normal jump. ('*' is shorthand for a jump followed by a flying enemy.) When the engine parses this string, it converts it into its respective sequence of timed events, so something like:
_events = {
new EventData(0.0f, EventType.Checkpoint),
new EventData(8.0f, EventType.NormalJump),
new EventData(8.5f, EventType.SingleEnemy),
new EventData(12.0f, EventType.NormalJump),
new EventData(12.5f, EventType.SingleEnemy),
...
};
This (along with other metadata about the level) then gets passed off to the level generation procedure, which is responsible for processing all of the events in order and building the actual level out of them. Normally this is all done ahead-of-time when I author the levels (in a process I call "baking" the levels), so the end level objects are saved into the scenes directly to optimize load time.
Now, the way that the (work in progress) level editor works is simply by maintaining a similar list of events that compose the level being edited, and re-generating the level again every time there's any change. It might seem terribly inefficient to keep rebuilding the level compared to just editing the resulting level objects directly, but there's a lot of reasons why it makes sense to do things this way. For example, changing a list of events is simply more efficient than having to worry about editing the actual level objects (moving floors around, etc). and I already have the code to do all of this, so I just have to worry about providing an interface to visualize these changes well.
Testing the Prototype
Part of the reason I wanted to dive into working on the level editor right away was simply because I was curious whether this approach would even be feasible at all. I was worried that re-generating the level at each change might be too slow, for example. So I created a quick editor scene and made a script to hold a list of events, populated with some test data. I could then invoke the level generation process at runtime from there... ...and have everything be totally broken. All of the objects in the game are all built assuming that if the game is running, the level is supposed to be playing. They also assume that a song is playing, that they can query for the current music time, that a player instance exists, etc. So I had to do a bunch of refactoring to handle this unplanned-for case where we have all of these level objects, but they're not actively updating because we're in the level editor. One thing I wanted to shoot for was to be able to instantly jump from the level editor into playing the level, without having to go through any sort of scene transition or anything like that. So I needed to make sure the level editing scene also contained everything needed for the base game, including the player instance, the music controller, etc. I also wanted to see if I could successfully load in audio files specified by the user. Here's what all of that looks like in action:
After doing all of these refactors, I had a simple prototype and I could add in basic enemies or ground ramps by pressing a key on the keyboard. One of the first things I did after that was to see what the performance was like when I triggered level generation, especially after I added a ton of events and made the level longer. To my delight and surprise (especially because the full level baking process normally takes a bit), the performance was actually pretty acceptable! I was initially expecting to see like 1-2 second pauses once the level got longer, but it seemed like it was only a minor hiccup most of the time. This is also without any sort of optimization -- of which there could be many. Besides just raw "cache things, do work ahead of time, make the code faster", there's also the fact that most events shouldn't require the =entire= level to be rebuilt. Yes, a change in the ground ramp early on does mean that the height of the rest of the level will change, but at least you can skip re-generating everything that came before that. And adding or removing enemies shouldn't require the entire rest of the level to change. If it came down to it, I could force you to work on only one checkpoint section at a time. But it looks like I don't have to worry about any of those optimizations (yet).
Input Timeline
There's going to be a lot of work for me to do in the upcoming weeks for implementing various tools so that the editor can actually provide enough functionality to create a full level -- both in terms of all of the actual level obstacles (water zones, flight paths, etc.), as well as the level metadata (music BPM, scroll speed, background palettes). One thing I did in the meantime was to implement what I'm calling an "input timeline" feature, where the expected inputs are displayed as colored diamonds in a separate track below the level. I added this mostly for use in the level editor, but I also made it function in-game in case you want to use it there:
The exact look of this will probably need to be adjusted (not very colorblind-friendly right now either), but this is a really useful view for the editor already, and will probably become even more important once I look into more advanced editing features (editing via keyboard or even a "record" style live play). One thing about this input timeline is that you can see just how boring of a game Rhythm Quest is in terms of the raw inputs. A big part of the appeal of the game (to me, at least) is parsing the platformer-like obstacles into rhythmic elements; if the chart is just laid out in front of you like this it's really not too interesting.
Scoring Rework
I did this a while ago but never wrote about it. Despite the fact that I've already tweaked the coin / respawn / medal scoring system a few times (at various points in time it's alternatively been based on respawns and coins), I've iterated on the system once again. I was never happy with how the medal thresholds felt both arbitrary and also not very visible, so I worked out a "progress bar"-style animation in the level end screen to show that off visually:
The thresholds are now straightforward and easy to remember based on the visual (25% = bronze, 50% = silver, 75% = gold, 100% = perfect). Previously you were awarded a bronze medal simply for completing a level, but I've changed that, so you'll just have no medal if you finished with less than 25% coins. Along with this, I'm trying out a new system for coin loss amounts. Previously you always lost a fixed amount (5 coins) on every respawn, but this usually led to people either getting very close to perfect, or losing almost all of their coins on a particular section or two that they had trouble on, even if they performed very well through the rest of the song. I've always wanted something that scales more cleanly, like for example every time you respawn you lose 50% of your coins, but that by itself doesn't work well because it's extremely punishing for single mistakes that are made late in a level. The way it works now is more complicated, but should hopefully be more "balanced" in terms of coin losses. The new system internally maintains two different coin numbers -- the coins that you have recently collected, and the coins that you have "banked". At every checkpoint, half of the coins you have on hand are put into the "bank" and can never be lost from then on. And at every respawn, half of your non-banked coins are lost. The idea is that this system rewards you for performing well, and can't fully "take away" that reward even if you mess up a lot afterwards. It's a bit obtuse in that it's a pretty hidden mechanic, but I like the simplicity of implementation and the fact that I'm not using some really random number like 5. We'll have to see how it works in practice, though! That's going to do it for now. I'm trying my best to get the level editor off the ground...it's a lot of work, but also interesting and exciting since there's so many little systems that need to be written, for the first time! There's unfortunately a good chance that this will end up pushing back my launch date to 2024, but...I'm hoping you'll all agree that the custom levels that will come out of this will be worth the wait.
The Rhythm Quest Demo has been updated to version 0.29.0! This patch fixes a bug with the latency calibration measurements (you may want to re-calibrate) and reworks the coin/medal system, along with other fixes and improvements. Full changelog: Version 0.29.1 - Fixed localizations not falling back to English translation - Modify respawn/fadein timing for enemies according to music speed/respawn timing - Fixed steam build not running properly when started outside of steam - Localization updates Version 0.29.0 - Fixed auto-calibration output values (was previously ~75% too low) - Reverted level medals to be based on coin count again - Changed coin thresholds for medals (25%, 50%, 75%) - Changed coin loss amount/mechanic to be much less punishing: 50% of non-banked coins are now "banked" on each checkpoint and can no longer be lost 50% of non-banked coins are lost with each respawn - Added new level end coin/medal display - Updated ghost teleport to change speed based on music speed mod - Updated input handling interactions to be more timing-accurate - Fixed an input bug triggered by having multiple inputs on the same frame - Fixed a bug causing the color invert fx on doublehit enemies to persist - Fixed a timeslicing issue that sometimes caused interactables to be skipped at high speeds - Try (again) to fix rendering issues at odd resolutions - Added Respawn Timing setting - Fixed ghost enemy trail effect to render consistently regardless of framerate/speed - Fixed screen refresh rate label not actually updating - Localization and UI updates - Fixed respawn count not resetting properly when checkpoints are disabled - Fixed the ability to finish a level and respawn simultaneously
The Rhythm Quest Demo has been updated to version 0.28.0! This patch includes a multitude of extra settings and game mods, including the ability to play levels at a faster or slower speed! Full changelog: Version 0.28.0 - Fixed ramped floors being allowed with spike enemies - Renamed "Cheats" menu into "Game Mods" - Added assists/game mod settings to in-game menu - Made shop accessible from Extras menu - Added music speed game mod setting - Added timing window game mod setting - Added ghost helper game mod setting - Added frame queueing graphics setting - Added configurable bindings for gamepad controls - Keyboard bindings now default to entire left/right half of main keyboard area - Fixed rendering artifacts at odd resolutions - Added UI volume setting, separate from sfx volume, reduced UI sfx volume slightly - Change pixel font setting default based on language at runtime - Added low quality graphics toggle to help framerate for older devices - Added "Released Early" and "Released Late" text for hold presses - Added "screenreader" command line argument force start with screenreader prompt - Fixed too-fast scrolling before level 1-1 tutorial - Fixed fullscreen not working for devices that present resolutions in an unexpected order - Fixed vsync off behavior, especially for desktop platforms - Changed flashing effects to be time-based rather than frame-based, to support higher refresh rates - Settings are now saved when drilling into a submenu (not only when navigating back) - Progress is now saved (again) while auto play is enabled - Thickened some outlines on smooth text - Minor tweaks to jump logic (hopefully not breaking anything) Version 0.27.0 - Added tracking of respawn counts to save data and level display - Localization and UI updates
Despite what it might seem like, I've actually been working on quite a lot of different things for Rhythm Quest recently! Let's get right into it...
Game/Music Speed Mod
You can now change the speed of the music to make the game more or less difficult: [previewyoutube=V_Ymeh8yBck;full][/previewyoutube] Some of you might be wondering why this took so long to come to the game, considering how this has actually been a debug-only feature for a while. Changing the speed/pitch of the music isn't actually very hard, the problem is getting all of the music synchronization (and respawn, and pause) logic to work properly in tandem with it. I'm happy to report that this has been implemented properly now! It's definitely not as simple as it might seem...every time you change the setting in the pause menu, what I'd =like= to do is to immediately increase the speed of the currently-playing pause music loop, then adjust the audio timing variables accordingly. But there's no way to do that in a way that respect the audio synchronization perfectly, since the audio timings are all running separately. Instead I have another copy of the pause music which is set to play at the new pitch/speed. Instead of trying to play that one immediately, I schedule it to start playing 0.2 seconds from now (scheduling sounds in advance is the only way to ensure that they trigger at a precise time on the audio timeline). Unfortunately, I can't schedule a "stop" command in the same way for the first version, so instead I simply wait for roughly 0.2 seconds and then do a flip where I set the volume of the first loop to 0 and the second one to 1 at the same time. Doing all of this lets me keep the beat synchronized with all of my timers correctly. Of course, since the whole process does take a little bit of time (0.2 seconds), I also needed to implement a simple wait, in case you trigger the button twice in quick succession -- the second music speed change doesn't occur until the first one is finished. Anyways, this will be available in the upcoming 0.28.0 patch, so players can either use lower speeds to practice / get through difficult sections of the game, OR use higher speeds to give themselves an extra challenge.
Timing Windows
I also have a brand new setting for modifying the lenience of the timing windows for obstacles:
This affects the collider sizes of the various obstacles in the game. Here's how that looks for a basic flying enemy:
Originally I had a crazy half-baked idea in my head that to implement "extra leniency" I would actually buffer inputs for you and delay them automatically to "auto-correct" your early inputs...and for late inputs, I would just "freeze" the character in place for a split second to give you extra time to hit the correct button. I realized, though, that this would make empty jumps and attacks (the ones you do for no reason) feel really sluggish and awkward. I could try and do some tricks to correct for it, but in the end I figured that modifying the sizing of the colliders was simpler and just as effective, while maintaining the tie between your input and the game reacting immediately. One cool thing is that the setting works for jumps too, even though you wouldn't think they have a "collider" to modify. This is because every jump (even if it's just a normal one over a pit of spikes) has an invisible trigger object (this is what spawns the blue "spinning square" visual effect). As long as you jump while you're inside of that trigger zone, my jump logic will autocorrect the length of your jump to make you land in the appropriate spot. I also already implemented "coyote time" jumps that will allow you to jump even after running off a cliff (as long as you're still within the correct trigger area), so it all just works. Here's that in action with the extra wide timing windows:
Ghost Helpers
I got to see and/or hear about a variety of play experiences when I did my last internal playtest. I won't lie: the design of Rhythm Quest is almost entirely driven by my own sensibilities, but it's still useful to see other perspectives so I can make small course-corrections and admissions when I feel necessary. Interestingly (or maybe this shouldn't really be surprising), different people seemed to struggle more or less with different aspects of the game -- for some, the water zones really threw off their ability to read the charts, while other people mentioned the ghost enemies being difficult to read since they felt like they needed to react to each of the new positions. For people who struggle with the ghost enemies, I've added a new helper option that will display guidelines for the second and third positions of the ghosts:
This does, of course, take away from the entire conceit of the ghost enemies to begin with (with the helpers, it's no different than three red enemies), but I really don't mind adding these sorts of modifiers when the implementation is so simple (and I'm not busy working on other things). You can play Rhythm Quest however you want!
Performance Settings
I already did a bunch of work on performance optimizations in a previous stint of work, but I'm happy to report that I've also made the rendering of the water sections more performant: previously, the render textures used to implement the "wavy" water shader were way bigger than they needed to be...now they don't extend past the height of the screen, and dynamically shift based on the camera to be more efficient. However, I also wanted to give a nod to some lower-end devices, and ended up adding a graphics quality toggle that will disable the fancy water shader entirely, as well as cut out some of the translucent backdrop layers (that I pick by hand) in an attempt to make rendering more performant. I also added another engine-level setting for controlling frame queueing. Those all live on a new sub-settings page that comes complete with a quick-and-dirty FPS counter and some water zones in the background so you can see the effects of the settings live:
Other Stuff
Some miscellaneous other stuff was added too, like a "screenreader" command-line flag that will force the game to start in screenreader mode even if it doesn't auto-detect that one is enabled. In an attempt to add a little more discoverability to the shop menu, I also made that accessible from the "Extras" menu in addition to the normal place in the level select screen. I also renamed "Cheats" as "Game Mods" since I'm throwing in all of the timing and visual modifications that I've added there. Most of those are also accessible via the in-game menu, which can be helpful if you need a temporary assist for a tough checkpoint that you're struggling on.
I think that about covers it for what I've been working on lately!
I'm continuing my break from working on world 6 levels for now. I've sent out the current build to some internal beta testers to get some feedback on the difficulty scaling and reception to the newer mechanics, so I want to give myself a chance to let that feedback come in and stew on it for a bit before I continue on with the last 4 levels of the game. In the meantime, I've been trying to tackle some improvements and fixes that have been laying around in my backlog for a while...
Gamepad Rebinds
This one has been desired (and requested) a long while ago, but I kept on putting it off because I wasn't sure exactly how I wanted to handle it. I already had control rebindings working just fine for keyboard controls, which have a single key assigned to each action:
The problem with gamepad bindings is that by default the gamepad controls have many different bindings: to jump you can use the d-pad, the left analog stick, the south or east face buttons, or even the left shoulder button. I was sort of at a loss for how to deal with this, both in terms of the UI (how to show the combined default bindings?) and in terms of implementation (how to override the entire set of bindings at once?). Like many other tricky problems I've run across in Rhythm Quest, letting it sit in the back of my head for a while allowed me to come up with a different approach:
Gamepad and keyboard bindings now each have their own standalone submenu (not available on platforms where they don't apply). More importantly, there's an individual setting that toggles between a "default binds" set and a "custom binds" set. The default binding set features multiple binds, whereas the custom binding set only has two (that can be overriden by the user). This elegantly (?) solves the issue I mentioned above. This also lets me illustrate the controls in a hand-drawn diagram, something that's probably easier to parse than "Jump: DPad, Left Stick, Left Shoulder, A, B, ..." Using the same system, I'm even able to detect whether a (supported) gamepad is plugged in at all, and dynamically update the screen accordingly:
I adopted the same tech for the keyboard bindings screen as well (had a bit of fun trying to draw a keyboard layout):
You'll notice that I decided to also expand the default bindings to just encompass the entire left/right half of the main keyboard keys. Unity does a reasonably good job (?) of detecting keys based on physical location, so this should work even if you use a nonstandard key layout like I do. I'm not sure what will happen for non-ANSI physical layouts, but I'm assuming the custom binding system will suffice for any odd edge cases. For now I'm providing two custom binding slots for each action (an improvement over before where you could only use one key), in case you want to alternate keys for faster sections. As usual, there's a ton of silly little details that need to be handled with input rebindings, and as usual, Unity provides just enough functionality to be helpful, but also forces you to work with a ton of abstractions like "InputControlPaths", "InputActions", and "ControlSchemes" that end up making your head spin when you think about them too much. You need to, for example, make sure that a rebinding can be cancelled via either Gamepad OR Keyboard input (the input system by default only allows you to define a single cancellation binding...)...
Rendering Artifacts
This is a really silly one, the kind of thing that you'd never imagine would be an issue, but somehow it is. Rendering the game to a width or height that's an odd number (e.g. 1013x533) causes weird visual artifacts:
This is caused by camera scaling and such -- here, the resolution is 501x301 and the game has decided to render the pixels at 2x, which means the base resolution is 250.50x150.50, which doesn't work out too nicely. I tried to address this before by automatically resizing the game window and forcing it to be a multiple of two, but that didn't work too well. My new solution is to handle the rendering properly by shifting the camera by a similar fractional amount, so here we simply shift the camera over by a half pixel and fortunately that works to fix things.
Released Early/Late
Suggested by one of my playtesters -- the "Too Early / Too Late" text for holds is now more specific in calling out "Released Early / Released Late". A super easy fix that hopefully helps clarity a tiny bit:
I'm glad I got around to some of these improvements and fixes (which should be coming to the demo soon), but I feel like I've only just scratched the surface of the work that needs to be done. Even for the gamepad rebinding system, I still need to test how it works on Switch / for other types of gamepads, and could even stand to draw different graphics (especially for the Switch joycons). There's also some tweaks that I'm going to be trying to look at after seeing how playtesters fared with the current build... The year is about halfway over and unfortunately my progress hasn't been super great -- I've only managed to finish off 6 levels in that time, plus some optimization work/etc. Of course, I had some real life stuff happen that drew my attention away, but that's also sort of true in the upcoming months as I help mentor for a video game tournament. That "end of 2023" date is starting to feel really scary when I think about it...
The Rhythm Quest Demo has been updated to version 0.26.5! This patch includes some hefty optimizations, which should help improve performance, particularly on lower-end machines. Full changelog: Version 0.26.5 - Reworked texture encoding for memory and performance benefits - Various other performance optimizations - Fixed an issue causing minor vertical blurring on WebGL builds - Add better transition for Furball attack -> jump - Fixed attack animation being cancelled on fly start - Fixed menu bug where foreground level backdrops sometimes failed to fade in - Minor UI fixes
Somewhat unexpectedly, I took a break from working on levels this month to focus instead of **performance and memory optimizations**. This was brought on by the fact that I made some release builds for the first time in a while and found that my iOS build crashed on startup because it was running out of memory loading the main menu!
The main culprit? These huge backdrop texture atlases...(this one is 64 MB!)...
The Problem
Your first thought upon seeing these atlases is that they're really wasteful. Why is there so much empty space in the upper-right? Well, that one is because the texture atlases need to be even powers of 2 in dimensions (1024, 2046, 4096). I could, of course, have each layer be separate, without packing them into a single atlas, but then I'd lose all the performance benefits of being able to batch the draw calls for all of the background layers together. The better question is why does each backdrop layer have so much vertical padding? Well, I didn't want to make any assumptions about the player's aspect ratio, resolution, or zoom settings, so the easiest way for me to solve that was to just author all of my backdrop layers with loads of vertical leeway, so that they'll always be fully in view.
Each separate layer is exported at 500x1200 pixels (very tall!), and then tiled horizontally by the game. Some of the levels have upwards of 10 or 15 separate backdrop layers, so that's quite a lot of pixels...
Texture Encoding
The first thing I wanted to do was see if I could just store the textures more efficiently without changing anything about my authoring workflow. You may have noticed that the texture atlases are all grayscale (no color). This is a change I made a long time ago, back when I decided to use a palette shader for the backdrops. Essentially, I only really need to represent indices into my color palette (currently, one of 11 colors), so during my export I just use grayscale colors that the pixel/fragment shader can read and then interpret as color 0, color 1, etc. I also sometimes have partial transparency, so the alpha value is also important. However, the textures are still encoded as 32-bit RGBA, which means 8 bits are assigned to each of the red, green, blue, and alpha channels! That's pretty wasteful, so I wanted to look into whether Unity supports other lossless texture formats (across multiple platforms). It does, in fact you can actually use the "R 8" texture format, which exclusively encodes a red channel (nothing else!), and only uses 8 bits per pixel (25% of what I was currently using!). That seemed perfect, as really all I needed was grayscale values anyways. The one problem was that I still needed to store alpha values to handle partial transparency. Could I somehow pack both the color index, and the alpha information, into 8 bits? Since I only have 11 different colors in my color index, 4 bits is enough to encode that (2^4 = 16). That would leave the other 4 bits to store alpha information, which would mean I could have 16 different possible alpha values. That's more than enough for my purposes, so I went ahead with this strategy of using 4 bits for color encoding and the other 4 bits for alpha values:
To get this all working, I needed to first write a python script to take all of my original backdrop exports and encode them into an 8-bit red channel like you see above. Then I needed to modify my palette shader to do the reverse: take the 8-bit encoding and parse it into a color index and an alpha value. After a bunch of shader math debugging and fussing around with bit arithmetic, it was all working (everything looked the same as before) and the iOS build was no longer crashing. Hooray!
Texture Cropping
We can still do better, of course. The next step was to see if I could get rid of all of the extra padding on the top and bottom of many of these images. Take this cloud layer for instance:
Ideally we could only store the actual texture data that really matters (the middle section). The top half is all transparent, so we can just discard that, and then for the bottom half we can just "clamp" the texture lookup so that the bottom-most opaque row is essentially repeated indefinitely. Doing the crop itself is simple enough -- I just modify my python image-processing script to analyze the rows of the image and trim it accordingly. We end up with this nice cropped version of the image:
The trickier part is that we now need to render this in the same way as the original texture. There are a couple of problems with this... First, the new origin/center point of the sprite is different than before, since we trimmed an unequal amount of rows from the top and bottom, so it's going to be offset from where it was supposed to be drawn. To fix this, I added processing to my script to keep track of how much the new cropped sprite is offset by. I also track some other important metadata, such as whether the top or bottom sections (or both) should be repeated transparency, or a repeated opaque row. Then I output that all to a C# file that I can read in:
{ "level2-5_background_4", new Entry {
Offset = -62.5f,
TopTransparency = true,
BottomTransparency = false,
OpaqueBelow = 1,
OpaqueAbove = 55
} },
My backdrop tiling script is responsible for taking the stored offset metadata and shifting the center position of the rendered sprite accordingly.
The second issue is that while Unity supports texture coordinate clamping, there's no way to do that when the sprite in question is one of many sprites packed into a texture atlas! Unity's sprite renderer only handles tiling in a very specific way, which no longer applied to what I wanted to do, so I had to modify my fragment shader to handle the texture clamping part.
In order to do this texture clamping correctly, I also needed my fragment shader to understand what UV texture coordinates it was supposed to be working with inside the texture atlas. Normally the fragment shader is completely oblivious of this -- the Sprite renderer is responsible for handing it a set of UVs to render and then the shader just does the texture lookups blindly.
It also turns out that you don't actually have access to the sprite UV metadata from within your fragment shader =/. So I needed to pass those into the shader, =and= I couldn't use uniform variables since that would break batching. Luckily, Unity happens to expose a SpriteDataAccessExtensions class which allows you to write to the UV texture coordinates of the sprite mesh used by a sprite renderer internally.
In addition to allowing you to modify the main UVs, it also lets you set additional texture coordinates on the mesh (TexCoord1, TexCoord2, TexCoord3, etc.). I used those to pass extra data to the vertex shader -- and then through to the fragment shader -- including the sprite UVs from the texture atlas.
This took a lot more debugging to get right, but at the end of all that, it was working! Here's the new version of the texture atlas from before (in all its red-channel glory), which is 1024x1024 instead of 4096x4096, and 1 MB instead of 64 MB!
Alleviating Overdraw
Rhythm Quest isn't really a performance-intensive game, so it runs fine on most systems. That said, there are a couple of areas where it can get into performance issues on lower-end devices (surprisingly, the Nintendo Switch is the main culprit of this so far). One major performance bottleneck involves overdraw, which is a term used to describe when pixels need to be rendered multiple times -- typically an issue when there are many different transparent / not-fully-opaque objects rendered in the same scene (*cough* backdrop layers *cough*). Unlike in a generic 3d scene (where we would try to render things from front-to-back, to minimize overdraw), for our backdrop layers we need to render things from back-to-front in order to handle transparency correctly:
Unfortunately, this results parts of the screen being rendered to many times over and over again, particularly the lower areas (all of those overlapping cloud layers...). The good news is that the cropping we did above already does some work to alleviate this a bit. Before, the large transparent portions of backdrops would still need to go through texture lookups and be rendered via the fragment shader, even though they were completely transparent (i.e. didn't affect the output). But now, we've cropped those areas out of the sprite rendering entirely, so they aren't a concern. We can still do a little more optimization, though, for opaque backdrop sections! Take this layering of opaque cloud layers from level 2-5 as an example:
There's a lot of overdraw happening on the bottom sections of the screen. What if we were smart about this and kept track of which portions of the screen are being completely covered by each layer, front-to-back? That would let us render smaller screen sections for all of the back layers:
We can handle this by having our image processing script store some additional metadata (the "OpaqueBelow" and "OpaqueAbove" fields) so we know at which point a background layer obscures everything above or below it. We then need to modify the backdrop script to adjust the drawing rect and UVs accordingly (easier said than done...)... The end result of all of this is...that everything looks exactly the same as before...
But! It's significantly more efficient both in terms of memory usage and rendering time. I'll have to patch the existing demo builds with this optimization at some point, but the Switch build is already showing some improvements, which is nice. We're not completely done with performance though, as right now the rendering of the water sections are also quite inefficient! I may try to tackle that next...
I'm continuing to just roll ahead with levels! It's funny, I feel like there was a long period of time when working on new levels and thinking about the mechanics felt intimidating, so I would just procrastinate on it and work on other miscellaneous things. But now I think it's the opposite (probably partly because all of my mechanics are known now), where I've gotten into the habit of just working on only levels. It's good though, the levels are something that need to be done 100%. Anyways, I went straight ahead and finished up the first level in world 6, level 6-1! [previewyoutube=BWk1T_4gv1w;full][/previewyoutube]
Speed Zones
World 6 introduces one new mechanic, the red "speed zones" that increase scroll speed and change up the rhythmic meter into triplet patterns (quarter note triplets) temporarily:
As with some of my other mechanics, this might get mixed initial reactions from players (or at least, that's the expectation I'm setting up for myself...). For people who don't "get" triplet meter, it might seem sort of like an arbitrary changeup/speedup that's hard to react to. I experimented with having a sort of 2-beat "lead-in" to prep you for the new meter, but I was pretty unhappy with how that sounded (messy...) so I took it out. (Maybe that'll be an optional toggle someday?) For now I'm just trying to give the player some easy speed zones at first so that they can listen to and get used to the rhythm, before I throw actual quarter-note triplets at them:
You might not have noticed it until I pointed it out (now it'll stick out like a sore thumb...), but none of the speed zones have any height ramps -- they're all completely flat. I couldn't really get the "conveyor belt" graphic to look reasonably good at an angle, so I just decided to add that as a restriction (the level generator will probably be really confused if you try to add ramps in the middle of it). I'm totally ok with that though, it makes them simple to read...and I actually have the same restriction for spike enemies (they can travel across ramps, but the actual jump needs to be on flat ground), so it's not really a new thing. I guess technically I can support height changes in the form of air jump combos and flight paths, but those haven't come up yet.
Visual Identity
This one was easy since I had already been thinking for a long time to do an outer space theme for world 6 (maybe sort of a trope to have the final area be space-themed?). One of the worries here is that all of the level backdrops are just going to look similar since they'll all just be dark skies with stars, but hopefully I can make them a little bit distinct by experimenting with different foreground elements and such.
For this level I went with sort of a "spiral galaxy"-type drawing with a bright orb in the middle. In hindsight, I probably could have drawn it bigger...but I guess this way it's more of a single element rather than filling most of the screen, which works too. It looks like there's all sorts of colors in there, but it's really just the 8-color palette, but with a bunch of translucent layers. It was actually quite fun to draw, as it felt like more of a painterly (impressionistic?) approach throwing blobs and dots of colors everywhere rather than the geometric shapes from world 5. You can also see that I'm making heavy use of the spraypaint tool for the first time here, particularly in the soft "nebula"-like patterns in the background. As usual, I tried to add in some amount of variation in the color palette depending on the different sections of music. Here I switch to a completely black background color for the first "main" section of the song to up the contrast level a little bit:
I'm hopeful about this art style for world 6! Hopefully I'll be able to draw some nice backdrops by experimenting with this general direction. I was a bit worried at first since I feel like "space" art tends to not do well with such limited color palettes, but it's turning out fine with clever use of dithering-like effects and translucency.
Musical Identity
Unlike with world 5, I didn't do a whole ton of musical exploration before starting off on this level...I sort of just "winged it" and went with some rough ideas, seeing what came out of them. I knew I wanted to try playing around with whole-tone scale melodies, but I was also interested in exploring more varied bass sounds (maybe even dubstep-esque), as well as featuring prominent use of arpeggios and low-pass/high-pass filter automation. Here's a snippet showcasing the "wub" bass featured in this track, as well as a triangle wave synth that plays a whole tone scale pattern. I dunno, somehow wobbly basses and triplet rhythms almost seems like a bit of a musical trope... https://rhythmquestgame.com/devlog/53-wholetoneandbass.mp3 Here's another snippet, showing off some low-pass filter automation on gated chords, as well as an arpeggio that has some long reverb on it (spacey!). https://rhythmquestgame.com/devlog/53-arpfilter.mp3 And here's a longer snippet of the main buildup in the song. I use a different (but still-prominent) bass here, and slowly open up the filter as it builds. As with world 5, I'm making heavy use of triangle-wave tom fills to accentuate the rhythmic changeups. https://rhythmquestgame.com/devlog/53-buildup.mp3
Level Select
A new world also means a new level select theme! Here's a short video where you can hear that in action: [previewyoutube=mUxC7zIBLDg;full][/previewyoutube] I had a few false starts on this one before I landed on the idea, but it sounds great! I love how the major IV -> minor iv progression works here. You can hopefully hear the low-pass filter automation on the chorded synth, as well as the reverbed short arpeggio pattern -- same ideas as in the level. There's still a bunch more to explore with speed zones and how they combine with the other mechanics, which should be interesting to figure out over the course of these next 4 levels! I might have to tread a tricky balance since fast rhythms (e.g. double-hit enemies) are =really= fast in speed zones, so those will only be feasible if I take the overall tempo down a notch...
Despite not having a lot of time/energy to throw around this month, I somehow managed to finish working on level 5-5, tentatively titled "Brilliant Boulevard":
[previewyoutube=PvyRg6PRPMw;full][/previewyoutube]
As is usual with the last level in each world, there are no new mechanics here, so it's just combining everything from the previous levels and amping up the difficulty. The tempo is significantly faster than the slower-paced level 5-4, which makes the chart a lot more dense in terms of inputs.
For visuals, I experimented somewhat aimlessly with different shapes until I settled on this sort of "spiked" design, mirroring the top and bottom so it kind of feels like stalactites and stalagmites. It's reminiscent of the design from level 5-1, just with different shapes. Again my lack of visual "complexity" is showing here with the simplistic shapes -- the visual detail really lies in the layering (translucency!) and parallax scrolling.
I also experimented with having syncopated / offbeat spike enemies in the tail end of this level (so far they've only been on downbeats). Normally this is a little hard to read, but adding the green enemies actually makes it fine since it gives you a static marker to read the rhythm (even if the spike enemies were invisible you could still play this section fine):
This is one of those curious instances where adding additional obstacles (having green enemies, instead of just rolling spike enemies) actually makes the chart easier, not harder. There are many types of possible difficulty in Rhythm Quest charting ("random" notes that lack patterns would be incredibly awkward and difficult to read), but I'm following rather specific charting philosophies in order to feature difficulty in the "right" ways -- at least, for the main campaign.
That wraps up the entirety of world 5, which means next up I'll have to find some sort of musical and visual identity for world 6, where I'm supposed to introduce triplet-based speed zones!
The bad news is that it's been 2 months since my last devlog post! The good news is that I've finished 3 entire levels in that time: [previewyoutube=Yy4lgDCAz-o;full][/previewyoutube] [previewyoutube=D4HXrDVYopE;full][/previewyoutube] [previewyoutube=IGpfwxg46F8;full][/previewyoutube]
Level 5-2
This level introduces green enemies that are placed at the beginning of a flight path, so you need to pretty both buttons and hold down the jump button as you do:
As with level 5-1, I wanted to try using large partially-translucent shapes for the backdrop. One of my experiments while drawing level 5-1 was these large circular bubble shapes that I didn't end up using, so I used them here instead, along with an "undersea" theme.
Level 5-3
In this level we have green enemies combined with spike enemies:
We also get green enemies plus water columns:
Visually, I decided to stick to the generally "geometric" theme for backdrops (matching level 5-1 pretty closely) and just used a bunch of thick lines at 45-degree angles that form a sort of maze-line pattern. Again, there's large translucent shapes at play here.
Level 5-4
Level 5-4 introduces the tricky notion of placing a green enemy at the =end= of a flight path, so you need to release the jump button while pressing the attack button. This can be pretty tricky to get the hang of, so I made sure to dial the tempo back for this level to compensate.
For the visuals here I kept things pretty simple and just used translucent rectangle shapes with colored borders. It may seem like I'm being lazy here, but at least I've managed to get away from the big cloud layers that I was seemingly using in every level for worlds 2 and 4!
The patterns in world 5 actually introduce a new physical tapping motion, so I can see the potential for mixed reception of the charts and mechanics here -- something that I'll have to prove out and test in future user tests. For now, though, there's only one more level to complete in world 5, which means I'm about 80% done with all of the 30 main levels in the game! Level 5-5 won't have any new mechanics, just putting together everything on display that we've encountered so far. Unfortunately there are some extenuating circumstances going on in my life that are pulling my attention a little bit away from Rhythm Quest. This isn't anything new (I had similar "real life" stuff to deal with in January, and late last year as well), just thought I'd note it in case anyone wonders why things might feel a little more quiet around here for a bit. My current focus is still on finishing out the 30 main levels before I turn back toward polish and other game features.
The Rhythm Quest Demo has been updated to version 0.26.3! This patch fixes accessibility cue volume and makes some other minor tweaks. Full changelog: Version 0.26.3 - Fixed audio cue volume balance not affecting music/sfx volume - Slightly increased saturation for wing sprites - Fixed enemies sometimes fading in suddenly on respawn - Fixed minor texture rendering issue during respawns - Fixed level 1-1 tutorial not respecting screen filter - Changed menu player control behavior - Minor menu tweaks
I've completed work on level 5-1, tentatively titled "Gleaming Glassway"! This is the first level in world 5 (Crystal Caverns). I've got a =ton= to talk about for this level, but first, here's a video of a full playthrough of it: [previewyoutube=bodJ8_mIvuc;full][/previewyoutube]
Mechanical Identity
World 5 is going to focus on the green "combo" enemies where you need to press attack and jump simultaneously. There are a bunch of different ways in which these can be used, such as: By themselves In the middle of an air jump pattern Combined with the beginning of a flight path (press attack + hold jump) Combined with the end of a flight path (press attack + release jump) In combination with a spike enemy Mixed in with water zones / ghost enemies
I'm hoping to introduce these slowly over the course of the world, especially the flight path-related ones since those might be tricky to get a handle on.
Musical References
I've been trying to mull over musical style ideas for world 5 for a while now, and only recently had much success settling on something. I don't know exactly how to describe it succinctly, but it draws a little bit on "kawaii bass" influences, featuring big synth chord stacks that are sidechained to the kick drum. World 4 was pretty "lush" and "atmospheric" in its overall sound so I wanted to contrast that a little by going for something a little more punchy and crisp. Not "dry" like the 8-bit style of world 3, but something a little more "upfront" if that makes sense. I referenced things like the vibe of the chorus in nachi - (Nhato Remix) and especially the big chords in the drops of Snail's House - Pixel Galaxy and did a sketch called "Flight Experiments" during One Hour Compo. That sketch worked out really well, so I took most of the same ideas and put them into the track for level 5-1.
Block Chords
The main chorus features these characteristic chord stacks that I was mentioning earlier, along with some added arps to spice things up a bit. After freeing ourselves from the pentatonic scale limitation that I used in world 4, it's nice to go back to using 7ths and 9ths everywhere in these chords: https://rhythmquestgame.com/devlog/50-chords.mp3 One interesting issue that came up with writing these sections was the rhythmic structure of the second chorus, which initially sounded like this with a "four on the floor" beat: https://rhythmquestgame.com/devlog/50-fouronthefloor.mp3 It sounds okay, but in the context of the game, those first three quarter note chords (red enemies) are quite boring. In fact, the attack/jump patterns here go along with the main rhythm of the block chords, so there's really only one thing going on, which makes it very...monotonous, rhythmically. I ended up changing the drum beat around, adding another bassline layer, and ended up with this instead: https://rhythmquestgame.com/devlog/50-interestingbeat.mp3 The tweak is subtle, but I think having two different rhythmic things going on at once really helps make it less boring and one-note.
Musical Cues
Writing Rhythm Quest music is fascinating because there are rough rules that I find and discover for myself, but they usually aren't very strict. There are common patterns that I'll use a lot -- for example, flight paths are usually represented by sustained notes -- but those aren't always true: flight paths are sometimes represented by arpeggio patterns instead. It's the same thing with these green enemies. Sometimes I represent them in the music with chords (jump + attack at the same time = 2 notes at the same time), but I've also developed 16th note rhythms that I've started to use for them instead: https://rhythmquestgame.com/devlog/50-16ths.mp3
Other Details
I actually really love everything going on in this song, there are so many little details here and there that I want to talk about that I enjoyed putting in. One is this washed out glassy synth layer in the background that fills everything out and provides another element of rhythmic contrast. Here's what the first chorus sounds like =without= that synth: https://rhythmquestgame.com/devlog/50-withoutlayer.mp3 And here's the version with it added in (listen for it!): https://rhythmquestgame.com/devlog/50-withlayer.mp3 Listening to the first example you might not think that anything is "missing", but I really feel like this element helps fill in the space effectively even though it's in the background. Just one more audio snippet for you all: https://rhythmquestgame.com/devlog/50-outro.mp3 You'll notice that some of the leads have different timbres here than in earlier worlds -- I'm going to be trying to use alternate waveforms for my leads in world 5 to break away from the pulse/square waves that I use all the time. Really love the high filtered noise fill that happens midway through this snippet as well, that's just a simple synthesizer using white noise that goes through a high-pass filter. Also, there's that glassy synth line in the background again!
Visual Identity
World 4 used a lot of these translucent cloud layers (easy to draw!) to provide a lot of depth and adding additional colors to the scene without actually increasing the number of colors in my main palette (still always limited to 4 or 8 at a time!):
I knew I wanted to do something different in world 5, but I still liked the translucency effects, so I decided to take a different spin on it and instead of clouds, thought about other large transparent shapes, like bubbles and gemstones:
So here instead of having large bright cloud layers, I'm using geometric shapes (triangles, circles, diamonds) and using decorative outlines with translucent fill areas. You'll notice a lot of parallax scrolling with the light beams, which I think really makes the scene click visually. They vary in width, sorting layer, and color/intensity, so they create nice subtle visual appeal when they scroll past / through each other. Something else you'll notice is that the ground is also partially transparent here! I happened to stumble upon this idea and I think it really works for this world to set it apart from the others. It also makes it much easier to create visual interest on the bottom half of the screen since you get to see the (mirrored) bottom half of all the backdrops now. I actually ran into some issues when I first implemented this, since my level generation code previously generated overlapping ground areas in some cases. This wasn't an issue when the ground was just a single opaque color, but when it's translucent, it's a problem:
Luckily, those cases weren't too rough to track down and fix. During the chorus sections, I spawn simple diamond-shaped particles that fade in and out as they scroll by. Simple effect, but it fits the aesthetic of the level nicely and provides another element of parallax scrolling to add depth.
Menu Work
Since this is a new world, I also needed a new menu theme for it. Here's a video of the transition between world 4 and world 5, so you can see what that sound like when you unlock world 5: [previewyoutube=pIUg2mctXmU;full][/previewyoutube] Again, you can hear the use of block chords and sidechaining here, same ideas as within the level itself. I really like how different all of the menu themes are; you can actually hear my styles evolving and shifting over the course of the years as I work on the game and experiment with new sounds. That's it for now! Next up is going to be level 5-2, which is probably going to start touching on green enemies + flight paths!
The Rhythm Quest Demo has been updated to version 0.26.1! This patch implements some minor bugfixes and quality of life tweaks, as well as updated Sayuri sprites and a new Vietnamese localization. Full changelog: Version 0.26.1 - Updated Sayuri sprites to properly feature her ears - Added "Perfect Clear Mode" / disable checkpoint setting in cheats - Auto play mode no longer saves progress - Buffer pause inputs during respawn - Added Vietnamese translation - Fixed bug that prevented click input on initial calibration screen - Select TTS voice based on language for OSX - Allow/implement pausing during level 1-1 tutorial sequence - Fixed position warping bug when game loses focus or fails to update
The Rhythm Quest Demo has been updated to version 0.26.0! As thanks for reaching 10,000+ wishlists on Steam, this update includes a brand new level in the demo, level 3-4, which features the purple "ghost" enemy mechanic!
Full changelog:
Version 0.26.0
- Made level 3-4 avilable in the demo!
- Added missing pixel font diacritics for Polish
- Fixed Chinese, Ukranian localizations not forcing smooth font for low resolutions
- Save data validation tweaks
- Text wrap/padding tweaks
- Fixed doubled web browser link opens
- Fixed calibration squares sometimes not flashing
- Tweak graphics settings submenu layout
- Prevent mixing of different pixel fonts for unicode/non-unicode glyphs
- Keyboard bind displays now account for keyboard layout
Happy holidays, everyone! I've got another new level to show off today, but before we get into that, I'd like to celebrate the awesome milestone of hitting 10,000 wishlists on Steam!
I'm really astounded by how much traction the game and demo have gotten so far (despite being admittedly mediocre at effective promotion). Of course, Steam is not the only platform that matters to me (iOS, Android, Switch, itch.io), but it's the one with the most defined marketing metrics, so it's a nice way of getting a pulse on interest.
As a celebration and thank you for hitting 10,000 wishlists, I'm planning to add one more level in the Rhythm Quest Demo soon -- probably something like level 3-4 that involves the multi-hit ghost enemies. That would bring the demo content to 9 levels in total. There are 30 levels in the game, so that is actually quite a fair bit, but I don't think I mind too much. I will have to figure out how to make the demo work with including levels 3-1 and 3-4 but not the levels in-between (ick...) so that might take a little bit, but I'll try to work on it at some point.
Level 4-5
Aaand back to your regularly-scheduled update. here's the last level in world 5, tentatively titled "Moonlit Mesa": [previewyoutube=P36d9mWz8hc;full]https://www.youtube.com/watch?v=P36d9mWz8hc[/previewyoutube] As usual for the final level in each world, there are no new patterns here -- just a mix of everything else that's appeared earlier. In this case, that means vanilla water columns, water columns with spike enemies, water columns with triple flying enemies, and syncopated air jump patterns in water. I do have a little bit of new experimentation with ghost enemies + spike enemies + water columns at the same time in this level, though:
I don't go too crazy with that here since I haven't done a lot of offbeat ghost enemies in previous levels, but it's a minor curveball that makes these sections a little bit more challenging. Backdrop-wise, I was having a bit of trouble at first deciding what to draw for this level, but eventually settled on a night-time water background with the reflection of a moon/planet. As usual for this world, the translucent clouds really do a lot of the heavy lifting in making things look nice. These have been a staple, so I'm not sure how I'm going to change things up for world 5. There isn't quite as much freedom with dark night sky palettes since the base color needs to be pretty dark and not too warm-colored to avoid looking strange and cloudy. As usual, the palettes alternate between 4 colors and 8 colors depending on the section of music, although it often looks like more due to the transparent layers. I decided to use lighter colors for the night sky for tame sections and then transition to black for more contrast in the higher-intensity sections:
Localization Updates
Last month I transitioned the community localization efforts for Rhythm Quest over to the Crowdin platform. Thanks to the hard work of the community, the Rhythm Quest demo is now available in 14 different languages! There's still a lot of bugfixes and polish to tackle for localization: word wrap/padding issues, missing glyphs, and the fact that the screenreader accessibility plugin really doesn't like unicode characters...but I'm pleased at the state of things right now moving forward. That's going to do it for this update! I hope everyone is enjoying the winter / holiday season and taking it easy. Hopefully by this time next year Rhythm Quest will be released on at least one platform (ha ha ha ha...). As always, things are going to progress steadily but slowly here. I've got another Ludum Dare event coming up in a week, plus some hopefully-well-deserved holiday rest coming up. I'll see you all next year!
The Rhythm Quest Demo has been updated to version 0.25.1! This release includes several new languages, including Italian, Polish, Simplified Chinese, Swedish, and Ukranian -- big shout out to the community translators who made these localizations possible! Full changelog: Version 0.25.1 - Fixed mouse input not working in calibration screen - Text padding and localization tweaks/fixes Version 0.25.0 - Added several new languages and localization improvements - Accessibility fixes, especially for demo end screen - Expanded credits screen - Attempt to prevent performance hitch when enabling particles - Added diacritics to large pixel font - Fixed respawn count text spacing - Performance improvements
Hello Steam folks!
For those who are unaware, I've been regularly posting devlog articles for a while now, both to Medium as well as the official Rhythm Quest site. Since Rhythm Quest is starting to get more traction on Steam (thank you all!), I'm going to try to start cross-posting those here as well.
Before we start, I've got a quick announcement: I'm now accepting applications for translation heads! If you'd like to volunteer to be in charge of the localizations for a particular language, please go to this form to learn more details and apply!
It's been too long (5 months, somehow...) since I last completed a level, but I'm finally back at it, this time with the second-to-last level in world 4. Here's a video of level 4-4 in its entirety:
[previewyoutube=vlEI6y2bfIs;full][/previewyoutube]
Level 4-4 takes a slightly slower tempo (110 BPM, vs 128 BPM for level 4-3) and cranks it up a notch with the interplay between water zones and air jumps/flight paths. It also specifically leans into a few syncopated rhythms, such as this flight path riff:
One of the "rules" I'm forming for myself as I chart these levels is that syncopation is that off-beat rhythms are mostly off-limits when it comes to attacks or ground jumps because of readability concerns, but they're much easier to interpret when it comes to air jumps and flight paths because the height differentials give you an indication of the relative beat positioning of the notes.
Of course, water zones change up the visual representation of those beat positionings, which is what this level is all about:
Visually, I wanted to do a skyline theme, as I hadn't done that before. World 4 has a little bit of a visual identity crisis at the moment, flipping back and forth between "alien space world" and "cherry blossoms", so I don't know if this truly fits in, but as with a lot of things, I'm just going to roll with it now and question it later.
I was inspired to do this pink/purple color scheme, which feels vaguely scifi-esque to me. There are a bunch of examples I found by other artists of this sort of look (please let me know if you find the original artist for either of these works so I can credit appropriately):
My process was essentially to draw a bunch of layers of semi-random tall rectangles, then afterwards go back and add some sporadic details along the tops of several of them. There isn't actually a ton of detail here -- I decided to just stop after adding a few as it seemed to work well enough. The only other real thing going on is the sun in the very back, and then a bunch of layers of semi-transparent clouds (which are all over the place throughout world 4 -- that part at least is visually distinctive).
The color palette here is pretty straightforward by now: notice how the hue goes from purple to reddish-pink as we fade into the background layers. The colors get brighter and less saturated for the back layers.
Overall I'm happy with how this one turned out (I feel like I say that every time...), but I'm mostly just happy that I still managed to complete another full level. There's only one level left to do for world 4, so I think that'll probably be next on my list...
Hi all! I'll be doing a developer livestream of Rhythm Quest on Tuesday Oct 4, at 6PM PDT/9PM EDT.
You can catch the stream here on Steam, or on Twitch at https://www.twitch.tv/ddrkirbyisq
I'll be going through all of the currently-finished levels, including 10 new stages that aren't included in the public demo!
Rhythm Quest already has a live demo, please check it out on Steam here!
For more information about Rhythm Quest, please visit the website here or watch the gameplay trailer below:
[previewyoutube=vwtPYznqZHg;full][/previewyoutube]
(This event takes place on Tuesday Oct 4. Please disregard the steam event start time) This event is now scheduled for Tuesday Oct 4. Please click here for the updated event page!
Rhythm Quest
DDRKirby(ISQ)
DDRKirby(ISQ)
1970-01-01
Indie Singleplayer
Game News Posts 42
🎹🖱️Keyboard + Mouse
🎮 Full Controller Support
No user reviews
(0 reviews)
https://ddrkirby.com/rhythm-quest
https://store.steampowered.com/app/1635640 
Jump, attack, and fly to the beat to get past challenging obstacles in this rhythm-based twist on a traditional "runner" game.
Each type of enemy requires different button sequences to defeat:
Wings offer extra mid-air jumps, while flight paths require you to hold down the jump button:
Speed zones change up the rhythm of the song:
- Over 30 levels across 6 different worlds, each with their own musical themes and gameplay mechanics
- "9-bit" chiptune soundtrack hand-crafted to match gameplay rhythms
- Adjustable latency calibration and audio scheduling for tight and responsive gameplay
- Use coins to unlock bonus stages, alternate character skins, and more extras
- Storage: 1 GB available space
- OS: Ubuntu 21.10. Debian 11
- Processor: 2.5 GHz Dual Core (Intel / AMD)Memory: 4 GB RAM
- Memory: 4 GB RAM
- Graphics: Nvidia GT740M | AMD Radeon R8 | Intel HD 630
- Storage: 352 MB available space
[ 5950 ]
[ 1903 ]