Project Type — Commercial Game (1.5 years in-development, team of 5)
Software Used — Unity
Language Used — C#
Role — Solo Programmer, Creative Director
BubbleBeast DigiDungeon is a colorful, Y2K tile-matching rhythm game where the player pops colored bubbles to the beat of the music. In addition to being creative director and composer, I was the project's only programmer, and my key contributions included implementing a rhythm game beat-tracking system with FMOD audio middleware, as well as dynamic menu systems that instanced UI elements at runtime.
BubbleBeast DigiDungeon is published on Steam for free, with 180+ reviews at 99% positive. As producer, I was also responsible for all marketing and publishing for the game!
My biggest aesthetic goal as creative director was to have the game be as reactive to the music as possible. The tile-matching gameplay happens in rhythm, of course, but lots of visual and sound cues do as well:
BubbleBeast's main menu. Note how the logo and the menu text bounce in time to the music.
To support this cohesive musicality, I had three big goals as I worked on the beat-tracking system:
Tight Timing
No matter what, all rhythmic elements must always be in sync with the music and with each other.
Musical Flexibility
Beat-tracking needed to restrict the musical composition process as little as possible. Tracking should easily support:
Different time signatures and tempos, including tempo changes within a song
Arbitrary events during music playback, denoting things like section transitions
Modularity
Scripts that capture music callbacks should be separate from scripts that use those callbacks.
Simultaneously, beat events should be easily accessible to any script that needs them, from bubble spawners to bouncing UI buttons.
An early design document for music-linked scripts.
Unity's native audio engine doesn't support things like tempo tracking, so to support my goals I turned to the audio middleware FMOD. FMOD packages audio files into assets called events, which can be played and referenced at runtime in complex ways.
A 2D timeline event in FMOD, holding audio, tempo data, and timeline tags for the song in Level 3.
In order to get callback events out of FMOD and into an Action format usable by other C# scripts, I adapted scripts designed by FMOD and ColinVAudio. I further extended the system to track subdivisions (beats between beats), the position of the music playhead, and song length.
In BubbleBeast, a central script called the TimelineHandler reads an FMOD event and emit C# actions, which are listened to by gameplay elements via the observer pattern.
A small portion of TimelineHandler.cs responsible for marshalling between C pointers used by FMOD and native C# values.
FMOD allows "markers" to be placed on the timeline, small structs with a timestamp and a string label. Though these are usually used for internal music state transitions, because my TimelineHandler system could read markers, I used FMOD markers to encode gameplay events, including switching beatmaps or making game objects appear.
A delegate function subscribed to the TimelineHandler, called when a marker is found.
A split-screen of Unity and FMOD. Note how UI appears in-sync with the timeline markers.
Gameplay menus like the Pause Menu or Main Menu needed to be deeply nested. A naïve implementation would involve a static, manually linked hierarchy, but if I needed to add new menu options, I'd have to manually add dozens of new buttons and links! A good implementation needed to be designer-friendly and to automatically handle connections.
Menus in BubbleBeast are implemented as MenuTree data structures, where each node in the tree holds info like child nodes and display tags.
This menu tree is encoded as a simple, designer-friendly text file, deserialized at runtime.
Menu UI is automatically assembled at runtime. Some parts of the menus had to be statically pre-built, like volume sliders, while other parts were reused in many places, like navigation buttons.
For static UI pieces, the "content" of a node was prebuilt in Unity as a GameObject and tied to a menu node via the node's title!
For dynamic UI pieces, a script called MenuUIBuilder read from the MenuTree; automatically placed and linked buttons; and parsed tags to disable, hide, or retitle UI elements.
The encoded MenuTree for the pause menu.
A portion of MenuTree.cs that parses tags.
The pause menu in action!
As an example of how dynamic menus sped up development, at one point playtesters began requesting a "Reset" button in the pause menu to simplify restarting a level.
Menu encoding made adding this option as simple as adding a line in a text file.
The content system let me define node content that reloaded the scene, knowing it would automatically run when selected.
The tag system let me define a "gameplayOnly" tag that hid the reset option while the player was in a cutscene.
This process and others like it would have been slower and harder without the clarifying MenuTree framework!