Return to site

Playdate Dev Blog: "Downbrella" (a la Downwell)

Learning PulpScript and Pulp editor for a crankin' good time

· Game Development,Dev Blog,Downbrella

"Hey, Amanda, why are you spending time learning an esoteric scripting language and editor for a niche console when you could be working on Floaty Hippo in Unreal 5?"

 

Shhhh, shhh. Shh. Hush. I know. It's okay. Floaty Hippo will still be there. I've been making steady progress on it, actually. We've got scoring, we've got procedurally generated level blockouts, we've got a chill lo-fi soundtrack made by yours truly, and we've got ~plans~ for a character customization screen, so you can spend more time lookin' at your hippo's cute li'l face while you personalize color schemes and hair styles. (And by "we've", I mean the royal "we", because this remains a solo dev adventure. Unless you count the cat that is frequently battling my laptop for lap time.)

I took a break from Floaty Hippo in October to focus on Sculptober and level-up my 3D modeling skills, which, in turn, will make it faster to model environments and assets for Floaty Hippo — so there's a method to my madness. A breakdown of my work on Sculptober is coming, by the by. "Something something, subscribe here or follow me on LinkedIn so you don't miss the post, something something."

And here's a little preview — I decided to fill an isometric room with items inspired by Sculptober prompts to tell a unified story, rather than make 31 disparate items. (Can you spot the Floaty Hippo cameo?)

broken image

I thought about turning this isometric room into a point-and-click adventure scene, or at least some sort of interactable hidden-ish object scene... and I'm still thinking about it. Especially because The Chinese Room just released Inkpot for Unreal, and I quite love Ink in Unity, so I'm thinking I'll experiment with some sort of point-and-click narrative something-or-other so I can play around with Inpkpot in Unreal.

Eventually, anyway. I can only dabble in so many projects at once in between job-hunting and playing Baldur's Gate 3 practically nonstop. Astarion isn't going to romance himself, after all. (Actually, he is, and it's hilarious — and no, that's not a link to a fanfic about vampire spawn clones, it's Neil Newbon, Astarion's fantastic award-winning actor, streaming his playthrough of BG3.)

Buuuuut let's get back on topic before I digress into how much I love Baldur's Gate 3, and how I've spent 150-and-counting hours being absolutely delighted by the writing and storytelling, and how much Larian Studios should hire me for the Associate Lead Writer role. But, like, honestly, really, truly, they should. I'd slay the house down boots, Vlaakith.

 

Crank Dat, Yo

My Playdate finally arrived, two years after ordering it, and I've been super eager to get crankin' on a Downwell-inspired game. (This will absolutely not be the last of the crank puns. You have been warned.)

 

This is Downwell, for the uninitiated:

 

And this is how far I've gotten with my Playdate interpretation, which I'm calling "Downbrella" for now:

This isn't much, but it's something, considering I knew zero PulpScript going into this project, and PulpScript is fairly limited!

My intention — and the inspiration for the whole game, really — is to control the player's x-axis movement with the crank. And that 100% works in the Pulp editor simulator, I'm happy to say! But on device, it behaves... uh, rather oddly. You could say I got *cranky* trying to make it work, ahahahahaha.

 

Developing for the Playdate

Would-be Playdate developers have a number of options for developing games and apps for the Playdate. And that number is 3:

  1. Lua
  2. C
  3. PulpScript

I went with PulpScript, namely because it's the language of the in-browser Pulp editor. The Pulp editor is an all-in-one solution for making simple Playdate games, allowing you to create simple pixel tile art, make simple songs and sound effects, develop simple scenes, and write simple scripts in the propreitary PulpScript language.

Note my emphasis on "simple", because while you can make a lot of really cool, complex, and complicated things for the Playdate — like this awesome not-3D 3D spaceship shooter, Tau — you cannot do very many complex and complicated things in Pulp or with PulpScript, as I quickly realized.

Now, I'm not a programmer by trade. My game dev wheelhouse contains staggering stacks of storytelling, writing, design, and direction, and a healthy heap of visual scripting a la Blueprints and propreitary plugins. What traditional scripting I'm capable of has come about through experimentation and necessity — setting up game data in Javascript, building little tools in Python to improve my workflow, dabbling in C and C++ for personal projects, building websites in HTML/CSS, etc.

So, with that caveat, let us say: Amanda is maybe not the greatest programmer, and some of her approaches and choices will be laughable to people who are great programmers.

But, let us also say, PulpScript documentation is sparse, so considering a lack of information and a teeny-tiny Discord community for outside help interpreting that documentation, I'd say it's fairly impressive that I managed to pull this off.

 

So, let's laugh at my code!

...but first, let's take a tour of Pulp.

Pulp runs entirely in your browser (Chrome and Safari are recommended, but it worked fine for me in Firefox, my go-to), which makes jumping into Playdate development easy and appealing if you're not keen on setting up SDKs and working in Lua or C. And I was not.

broken image

Here's the hero view. On the left, you have your various tabs of development. We're looking at the "Game" tab, where you define the launcher card thumbnail that appears in your game list on-device and in Playdate's web catalogue. You can also set various metadata, export assets, and assign the song that loops on-device while the game is selected but not yet launched.

Let's pop over to the "Song" tab, as I want an excuse to show off the theme song I built. This was another reason why I was keen to use the Pulp editor, because you can build, right in your browser, everything you need to get a game going, provided you keep it simple.

broken image

Hobby musician as I am, and eschewing my destiny to play a mandolin (hahagetitmynameisAmandaLynn), I had so much fun setting up a little chiptune theme song here in the editor. With five channels of waveforms to play with, and various tweaks you can make to each sound channel, you can create some pretty interesting and groovy tunes.

(Incidentally, there's a bug with the editor: beyond 32 bars, you can't scroll the song editor, but you're able to paste data beyond those 32 viewable bars, which results in portions of the song you can't see/edit beyond bar 32. Which means if you want your song to loop seamlessly, and you've accidentally pasted data beyond bar 32, you have to rebuild your song from scratch, because there's no way to scroll over and delete that extra data. A bit annoying. It's been flagged in the dev forums, so hopefully someone's working on a bug fix.)

Here's the theme song I made for Downbrella (screen-recorded from my laptop, so scooch your volume down a tad):

Groovy, no? I was going for something moody, dramatic, a little bit dangerous — and I think I got there.

Now let's take a look at the "Room" tab, which is where you build out your pixel tile art, and then arrange that art into scenes.

broken image

Here in the center, you have your view of the room you're currently building. On the right is the pixel tile editor for creating new tiles, which you can then place in your room. Playdate is a 1-bit console, so your choices of color are black and white. These red squares you see are for the dev view, indicating "world tiles".

On the bottom, the "Layers" section shows the different sorts of tiles you can build and place. World tiles are your environment art. Items are interactable and can be collected. Sprites are also interactable, but can't be collected. The player is... self-explanatory. And exit tiles are connected to other rooms, which sets up where the player goes after stepping on an exit tile.

broken image

Here's a closer look at my Player tile. As it were, the name "Downbrella" came about because a) I'm not good at pixel art, and an umbrella was the best I could do for my player character, and b) it evokes "Downwell", and that's free r̶e̶a̶l̶ ̶e̶s̶t̶a̶t̶e̶ marketing.

As you can see, you can set up an animation for the player (which you can do for other tile types, too), whose frames you can call in PulpScript to trigger various animations. I haven't gotten that fancy yet, but eventually I'll flip the tile when you move the player along the X-axis to indicate directionality.

You can also assign a script to the player — more on that later, but briefly, "on bump do" controls what happens when you bump into the wall. In this case, because bumping into the wall = death in my game, the screen shakes, a sound plays, we wait half a second, and then return the player to the start. (Remember in the "Game" tab how I described this as a "punishing arcade game"? }:D)

 

broken image

Here's a look at the first room after the starting room. Note the music note collectible (haha, pun not intended, but certainly welcome) — it's a red tile rather than an item tile! Why would I do that?

Well, Greek chorus singing exposition to the audience, here's the short answer why: PulpScript is *funky*. So let's get into it.

 

Descending into PulpScript

So. In PulpScript, we can call a handful of events in a handful of specific circumstances:

broken image

And because my game is about the player character constantly falling downward, "on interact do" is largely not available to me. Because the player is not, technically, actually moving the character. The game is moving the character.

Which means I have to do almost everything in this game via "on loop do".

Like so:

broken image

Here in these first 45 lines called on the game, we do the following:

  • Check if the room is the “start” room. We do this so we can retain the manual D-pad controls in this room, and give the player the opportunity to manually progress the player character down to the first room, where the player character begins falling automatically. This gives the player the chance to rest between death and the next run.
  • And we also set the score counter to "0".

    • In any other room that isn’t the “start” room, we do the following:
  • Lines 9 through 21: Control the player character’s movement on the X-axis based on the rotation angle of the crank. This works perfectly in the simulator in-browser! And works terribly on-device for reasons I’ve yet to figure out. The player character goes absolutely flying across the screen at the slightest breath of air on the crank, so there are some kinks to work out.

    • Lines 23 onward: Move the player continually downward along the Y-axis, and check what type of tile they’re falling through. We have to check what type of tile they’re falling through in order to invoke certain mechanics — because, technically, at least according to PulpScript, the player isn’t interacting when the player character is falling. So “on interact do” isn’t available as an event to call on the player character in these circumstances.

    • Based on what type of tile the player character is falling through, before reaching the bottom of the screen (targetY<=13), the following happens:
    • If the type of tile is one of the barrier tiles, the screen shakes, a sound effect plays, and the player character returns to the start — because, remember, wall = death. }:D
      • If the type of tile is a collectible tile (tileatlocation==24), as seen below starting at line 63, a sound effect plays, a global variable is stored, and the score counter increments by 1.

broken image
broken image

  • If the player reaches the bottom of the screen (targetY==14), then the player is teleported to the top of the next room.
  • "Ah, but why not use 'on exit do'?" ask the astute of you. Yes, why not, indeed! Again, because the player character is falling, and not interacting, I wasn’t able to reliably trigger "on exit do" any other way than through this roundabout method here.

As you can see, this gets long, because I have to set up the next-room teleportation at the end of every single room. I’m sure there’s a smarter way to do this; I have to experiment to see if I can increment string names or do some other better workaround. You can’t use arrays in PulpScript, or complex arithmetic (literally -=, +=, *=, and /= are all you can do; you can't do "thing.x + thing.y = thing.z"), so… yeah. That’s a head-scratcher. But probably calling a custom event that increments the room name? And naming the rooms in integers only? Maybe? We’ll see.

 

broken image

And that's the end of the script we call on the game during "on loop do"! And there's not much else going on in terms of scripting, really. It was surprisingly simple to get the core mechanics functional.

Let's take a peek at the rest:

broken image

So this is what we call on the player character, and almost entirely in the "start" room while the player still has control of the Y-axis on the D-pad:

  • "on bump do" — what happens if the player character bumps into the wall (i.e., death }:D)
  • "on update do" — this is called on movement (initiated by the player, not falling automatically); so, if the player character reaches the bottom of the screen, teleport to the first room
  • "on draw do" — this is called every frame before the player character is drawn, so this handles the scoring label in the top left of the screen, which dynamically updates when the player character passes over a note tile, as recalled from the global variable "collected" being incremented in the game script

 

broken image

And this is the dialogue that appears at the very start of the game — "on load do" — rather than in the room "start", otherwise it would be called every time player dies and restarts the run.

Here's a closer look at how that all plays out in the simulator, which is a bit easier to see than the video on-device (although no crashes and restarts are showcased here, and you don't get the sweet retro soundtrack):

broken image

And that's been my descent into development on the Playdate so far!

What's next?

Well, I could keep beating my head against the limitations of PulpScript... or I could rebuild this in Lua, and then be able to do things like:

  • Procedural level generation
  • More nuanced player movement (as you can see, the player falls at exactly one speed, and I don't yet know how I'd be able to alter that in PulpScript)
  • Build more complex music and art in external editors to use in-game
  • Create enemies, projecticles, moving obstacles, and more complex interactions with those things
  • ...and, you know, generally draw the rest of the f*#&ing owl

 

But as far as making a prototype and proof of concept goes, I'm really glad I played around in Pulp editor and with PulpScript!

Rapid iteration and prototyping was... well, rapid. I was able to tweak a value in my script, or make an adjustment to my room, or edit a song, or fix some tile art, then save, and play the updates in about three seconds. I'd imagine it's similarly swift in the SDK with the Playdate simulator, but then again, I didn't have to set up the SDK to get going with this prototype. Just signed into my Playdate Dev account and got going, no setup required.

It felt super approachable to be able to just start making something, no downloads, no setting up, no getting overwhelmed by the endless possibility space of Lua or C. PulpScript, for all its limitations, is great for someone who doesn't consider themselves a programmer, and whose prior programming experience is all very circumstantial and scope-contained.

Will I, in fact, jump over to the SDK and rebuild this thing in Lua, and then some? Well, frankly, that entirely depends on whether I snag a job offer in the coming weeks, or whether I find myself needing to fill time as the holidays slow down my job hunt. If I'm, say, moving across the country in the middle of winter to start a new job, I'm probably not going to have much time to work on this. But if I'm spending my holidays alone and unemployed in Vancouver, I'm going to need a distraction. So maybe I'll crank out some more progress. (I promised you puns, didn't I?)

If you've made it this far, I applaud your interest in Downwell-likes and Playdate development! I'd offer to help answer questions about PulpScript and Pulp editor, but really, all of this was trial-and-error, so I'm not sure how much use I'd be. But feel free to leave a comment here or on LinkedIn, and I'll see if I can help! Likely way more helpful are the Playdate Dev Forums, Playdate Dev subreddit, and Playdate Discord server.

Naturally, we've arrived at the only appropriate conclusion for this dev blog:

 

Soulja Boy, I tell ya.