Manatee Waltz: Elm Game Jam #4 Postmortem

In July, my wife Julia and I built a simple game for The Elm Game Jam #4. You can play Manatee Waltz on itch.io.

The release buuild of Manatee Waltz

Perhaps, The Elm Game Jam #4 couldn’t have been held at a worse time. Or, maybe, the timing couldn’t possibly be better. After all, the global pandemic had us stuck at home and the lack of commute did put some extra time on my schedule. Yet, when the two-month long event was announced in May, I wasn’t sure if I had any energy left to participate, but once the theme (“Animals/Nature”) was revealed, an idea randomly popped into my head and just wouldn’t let go. I was thinking back to a vacation in Florida where Julia and I spent an afternoon snorkling with wild manatees. Suddenly, Shostakovich’s Waltz No. 2 from the Jazz Suite started to play in my head. I smiled to the absurdity of watching bulky manatees “waltzing” to this graceful, determined music. I spent the evening humming the melody and imagining a lonely manatee that, for some reason, is diving deeper and deeper down into the dark ocean (insert 2020 joke here). Later that night, I spent ten minutes putting together a prototype with HTML and CSS, showed it to Julia, and when it made her laugh, we agreed to give this game a shot.

The original vision was heavily inspired by The Deep Sea project by Neal Agarwal. As the manatee would descend deeper, the player would encounter new creatures, new backdrops, and perhaps new game mechanics. However, after some consideration, we decided against this idea. To keep things interesting, we’d need at least three distinct underwater “sections” and that’d require a lot of assets. Also, making a convincing 2D animation of a manatee swimming straight down turned out to be surprisingly challenging. Finally, there’s the realism argument (right, in a game about a manatee chasing a mysterious light flare): manatees rarely venture further than a couple of meters deep, which is where most of their food grows.

The ten-minute prototype I pitched to Julia

Thus, it was decided that the manatee will swim left to right, which resolved all of the issues above. However, before we said goodbye to manatee the diver, I’d already started work on smooth animation of changes in direction, which led me to implement a primitive skeletal animation framework. This system still made it to the final build, but most of its features are never used.

A very early skeletal animation demo

One other choice I had to make early was choosing the rendering framework. The real-time nature of the game coupled with lots of raster assets pretty much excluded DOM and SVG so I was looking at either 2D canvas or WebGL. Looking at Julia’s artwork, I wanted to have access to WebGL shaders (for performant image manipulation) in case we need some post-processing, but I didn’t want to have to write a 2D engine on top of Elm WebGL, even a primitive one. Luckily, through my presence in the #gamedev channel on Elm Slack, I had become aware of the webgl-playground package by Romāns Potašovs, which is based on Evan’s original elm-playground. One caveat is that the Playground is designed as an opinionated app type: if you use it as intended, you lose direct access to DOM, flags, ports, and other common primitives. In my case, the deal-breaker was not being able to communicate with Web Audio over ports. So, instead of adding playgrounds as a dependency, I downloaded the source and started to build our little game within that codebase. Having direct access to the engine internals turned out to be beneficial for other reasons: it allowed me to add some inspection tools, custom shapes, and new shaders.

Something we really wanted to have in our game was seeing the manatee disturb the passing fish shoals, like in this delightful animation by Lizzy Goedhart. After reading some papers (most importantly, “Flocks, Herds, and Schools”) and watching this insightful video on the topic, I felt like I had a good grasp on the math of simulating such movement, but a bug in my code turned this into a multi-day adventure. The nature of the bug might be of some interest to the Elm community: it spawned from the atan2 function accepting arguments as y,x instead of x,y, as I assummed. I didn’t catch this immediately and went on a wild goose chase reworking a completely different part of the computation. Still, I think the end result is pretty nice to look at and totally worth the effort.

The first working demo of shoaling

I was glad my choice of WebGL didn’t let me down, as I did end up writing some custom shaders for the game, although not as many as I’d anticipated. For example, every layer of the parallax background is implemented as a fragment shader that accepts the current manatee’s position and updates the texture accordingly. I also extended WebGL playground with a gradient fill fragment shader, which is used on the farthest layer of the background. Originally, I’d expected to program the flares using some noise generating shaders, but I couldn’t figure out how to do it in Elm WebGL due to lack of framebuffers. Luckily, transparent PNGs ended up looking better than I’d expected so Julia made a bunch of animated sprites and we shipped those instead. I also ran into a limitation of WebGL on iOS where the maximum texture width is 4096 pixels. The seamless background texture that Julia had given me was about 6000 pixels long so I ended up trading some of that background variety (😕) for simplicity of implementation.

The other major aspect of the game is sound. I originally envisioned a bigger role for the “waltzing” bit, but the WebAudio API turned out to be a major disappointment. For example, I thought we could sync gameplay events to changes in amplitude, or maybe even make it into more of a rhythm game, but the vanilla browser support turned out to be underwhelming: even adding simple effects like low-pass filters or speedup/slowdown revealed significant discrepancies between different browsers. At a later point, I even ran into a bug in iOS Safari that only got fixed in late July with 13.6. While it was likely possible to resolve these issues by using different WebAudio primitives, it quickly became apparent that it’d consume most of my time and none of that work would have anything to do with Elm or bring me much joy.

Consequently, the sound in the final game is primarily a decoration instead of a first-class gameplay element. It’s managed in vanilla JavaScript via a dedicated Elm port. If the player misses one flare, the game sends a message to activate a low-pass filter (this is what creates the “underwater” effect), and when two flares are missed in a row, a slowdown effect is turned on for the Game Over screen (regrettably, only seems to work in Chrome). One other annoying thing about web audio is that you can only play sounds as a response to a user action (e.g., a button click). (Note: yes, I know there is a good reason for that, but it’s frustrating that noone can “have nice things” because of a handful of abusers) In my case, I had to instantiate all the Web Audio primitives in the event handler for when the player clicks “Play”. I was relieved that this worked despite all the indirection introduced by Elm’s loop. Not sure if the credit is due to Elm or browser developers (although, I kind of expect things like these to break without notice in the future).

For the music track, I originally used an orchestral recording of Waltz No. 2 from the Internet Archive. However, an unknown copyright status prevented me from keeping it in the release version. Instead, I found a MIDI score of the piece and livened it up with some virtual instruments in Logic Pro X. It came out admittedly goofy and the mix is pretty terrible (I’m a complete amateur when it comes to audio software), but perhaps not the worst sound work for a game jam submission ever.

Of course, some things could be better if we’d invested more time:

  • controls are somewhat janky, especially on touch screens,
  • when you start a new game, you’re likely to see a state where some or all of the assets are missing (unfortunately, asset preloading is not easy with webgl-playgrounds),
  • the gameplay in general could use some tuning,
  • …but isn’t this always the case?

Reflecting on this project, I think back to this lightning talk I gave at Philly ETE last year where I pitched the idea of building video games for fun using new and exciting programming languages. I think the advice mostly holds: I certainly didn’t regret not rolling my own 2D renderer or trying to sneak an ECS into the game code, I focused on things that seemed like the most fun (shoaling, shaders, etc.) and I cut a lot of corners along the way (hence, I’m not sharing the atrocious code that powers this monstrosity any time soon).

Perhaps, one thing that stood out for me this time was the importance of building integrated debugging tools. When tracking down certain issues, like the aforementioned bug in shoaling math, I tend to rely too much on tools that I use in my web development job: the likes of debug logging and step-by-step value inspection. Instead, a more effective approach almost invariably was to render more debug output and advance the game via a fixed time delta.

The built-in debugger. Press ~ to activate.

All in all, I’m happy with the result: Manatee Waltz feels like a reasonably complete game, albeit not one that’s too much fun to play. It does seems to earn a smile from the players though. More than a single person who tried it described the experience as relaxing, which is honestly what Julia and I were going for. I mean, it’s 2020. Take a break y’all, you deserve it.