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:
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
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.
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)
I don’t know why I didn’t do this earlier, but it was simple enough to set up with Unity’s layout groups. I still wanted the “Start Game” button to be a little larger than the others, but I was able to set that up using custom LayoutElement components, so now that one is 125% the size of all the others, and it all happens automatically. Yay!
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).
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!
Wishlist Rhythm Quest on Steam today at https://store.steampowered.com/app/1635640/Rhythm_Quest/