Getting into the groove
Drawing shapes, bad audio formats, and my upcoming game gets a new title
What I’ve been working on
We have been cranking away on our Playdate rhythm game, Otto’s Galactic Groove (formerly known by its working title Crank Crank Revolution). Rupert has written some incredible songs! Edie has been designing adorable characters! I love getting messages with awesome assets attached — I find myself thinking, I get to put this in our game?!
Meanwhile, I’ve been working on the level editor for OGG’s songs. It’s a web app for quickly generating note plans that we can import into the game. Here’s a little video of the editor in action.
Though still rough around the edges, it’s already proven usable enough to build note plans for several songs.1 My plan is to release the editor alongside (or soon after) the game so that players can build their own levels and sideload them.
One technical challenge I’m still facing: the thing about those sideloaded songs is that they ideally need to be WAV files converted to PDA (Playdate Audio) format for optimal playback performance — more on that later in this newsletter. But the only way to convert to PDA is by compiling a game on the command line, which is a bit much to ask most players.
Some savvy developers have been able to reverse-engineer the PDA format, but so far I haven’t been able to write a JavaScript function that successfully produces the desired file type. It’s a lot of stuff to do with headers and binary data and, phew, I’m well out of my depth. If you happen to have already cracked this particular nut, please do let me know!
Handy snippet: a draw function
Maybe it’s my background in SVG and HTML Canvas, but I like being able to draw graphics in code, especially for fast prototyping where I don’t want to have to jump back to a sprite editor.
The Playdate SDK comes with a bunch of useful drawing functions, but using them to quickly create an image with geometric shapes, and then apply it to a sprite, is surprisingly unintuitive. Luckily I have a snippet to take the pain out of that.
function draw(w, h, drawFn, debug)
local image = playdate.graphics.image.new(w, h)
playdate.graphics.pushContext(image)
playdate.graphics.setColor(gfx.kColorBlack)
playdate.graphics.clearClipRect()
drawFn(w, h)
if debug then
-- draw a box around canvas
playdate.graphics.setLineWidth(1)
playdate.graphics.drawRect(0,0,w,h)
end
playdate.graphics.popContext()
return image
end
You can then use this to draw an image and pass it into a sprite, just like a file loaded from disk:
local image = draw(40, 80, function()
-- Now draw anything we like in this 40x80 canvas
playdate.graphics.fillCircleAtPoint(20, 20, 8)
playdate.graphics.fillCircleAtPoint(20, 60, 8)
end)
local playerSprite = playdate.graphics.sprite.new( playerImage )
Cute, right? This makes use of a Lua language feature called closures, where we pass a function which gets called inside of the draw function (you can see it being called on line 6). And we use the Playdate graphics library’s contexts feature to draw only in the current image. To help with debugging, there’s a fourth `debug` parameter that draws a border around the image.
I’ve use this snippet a tonne in Skwish and Otto’s Galactic Groove — although much of my geometric shapes in the latter are gradually being replaced by Edie’s far superior hand-drawn pixel art.
What I’ve been playing
Of all the polished Playdate games releasing on Catalog, I didn’t expect a basketball game to be the one that really grabbed my attention! The art style alone in Quantum Phantom Basketball is unique and eye-catching, mostly pre-rendered CG with odd-looking characters and wild typography. But it’s the 1-on-1 gameplay that keeps me coming back, quick-fire matches that remind me of Street Fighter as much as any sports game I’ve tried before (although, to be fair, that list is exceedingly small). The game has an interesting backstory, too. Highly recommended!
Performance tip: don’t use MP3s
The other week, I suddenly noticed that the frame rage in one song in Otto’s Galactic Groove was incredibly choppy, rendering it unplayable. I assumed it was something to do with the number of sprites in the scene — but there weren’t even that many notes in the song!
Even more mysteriously, the performance suddenly returned to normal after I swapped out the song file for an updated version from Rupert.
So what was the difference? Well, the old file was an MP3, whereas the new one was an ADPCM WAV.
Seems weird, but the SDK docs include a warning:
Fileplayer can play MP3 files, but MP3 decoding is CPU-intensive. For a balance of good performance and small file size, we recommend encoding audio into ADPCM .wav files.
So there you have it — the performance hit from MP3s can drag down the entire game, even if they go through some sort of conversion process at compile time. Stick to those specialised WAVs instead.
Other Stuff to Read
That’s the end of this edition! If there’s anything you’d like to see in future issues please do comment or send me a message on Mastodon.
In the meantime — looking for something else to read or watch on gamedev? Here’s a couple of suggestions:
Crank - Gimmick or Useful Input Method? — from the devlog of upcoming Playdate game Solar Descent.
How to Make a Good 2D Camera (video) — Game Maker’s Toolkit continues to earn my Patreon dollars.
You’ll have to wait a little longer to see/hear them in action, as the game’s UI is still a work in progress.