As is tradition, I did Ludum Dare 39, and so it's time to tell the story. The theme was "Running out of Power", and I created a point 'n click puzzle-solving game with a 90's aesthetic, played in a Windows 95-esque desktop environment.
Preparations and workflow
During the event, I took some care to document my ideas and progress in the beginning – I had a Github repository set up, I committed somewhat regularly, I wrote down my goals in text documents. Naturally, I got more and more sloppy with the commits as the jam progressed, but it was nice to sort out the ideas in my head. Before I get into the actual game development, let me talk briefly about my workflow.
My toolset consisted of:
- Haxe – my favourite language for anything, can't go without it
- plustd – the aforementioned libraries
- TextMate – to write it all
- Photoshop – STILL working in it, despite only ever doing pixel art graphics
- bfxr – decided to give this beefier version of sfxr a go, it is pretty neat, although not a whole lot more useful than the original
- "psdwatch" – a NodeJS script using the psd module which watches my .psd files and converts them to .png files of the same name any time it detects a change (saves about three seconds and ~5 keystrokes of doing this manually in Photoshop for every change I want to see)
- "autobuild" – a bash / C script which binds two keystrokes globally (fn-A and fn-R) to build / build and run my project, play a sound depending on the success of the build, and autofocuses its own terminal tab to show errors if any were encountered (saves a lot of keystrokes!)
And that's basically it. I didn't get around to using the live asset reloading which is supported in plustd, but I don't think I needed it. I didn't do many re-builds just to see my images change.
I believe in general a good workflow should be one that the user knows very well and is comfortable with. Apart from the "unfortunate" Photoshop, I try very hard to have a toolset with the features I need, and not much more. In my opinion this would only be distracting (for gamejam purposes especially), and would make my computer work just a bit harder than it needs to. This is also one reason why I personally don't like many complex IDEs. But, it's only an opinion!
Nonetheless, this LD went as smooth as I could expect, without any problems of a technical / logistic nature that are somewhat common among newcomers. Despite having recently commented on a video about making gamejam games that experimenting during events like LD is very important, I would argue this doesn't have to extend to experimenting with your toolset. In other words, if I ever find a replacement for Photoshop, or a less ascetic way to make music (more on that further down), I will most certainly not testdrive them during an LD.
Once again, I had no strong favourites in the final voting round. In fact, I voted down Running out of Power:
But I didn't have any concepts in mind for any of the themes, so I didn't mind. These were my scrap notes I wrote in the morning for the theme:
### Theme: Running out of Power ### ### Ideas ### - Power as a limited resource - **Time** - Timed levels, collect batteries to replenish your bar, ... - Ore - Strategy / Simcity-like, build powerplants, consume power, ... - Power as a place (running **out of** Power) - **A power** - A government in power - **A place called Power** - Austin Powers! - Power as a setting - Post-apocalyptic - **Powerbox** / electricity - Genre - **Point 'n click** - **Puzzle** - Strategy - Platformer - Meh - **Vapourwave aesthetic** - Windows 95-esque interface - Applications to represent the player, battery tower, notepad, ...
Happy with what could be done, I thought of a little story:
### Story abstract ### You work in Battery City (B.C.), in a battery tower. It is a tower which fills up with floors when it has power, but there is a leak, so the power is running out. You cannot access the higher floors when the power is not high enough. To charge the tower, minigame(s) around the city. Power can be running out with the time (hard mode?) or with user interactions. The goal is to get to the top floor to ... (plot twist!)
So the idea was the player would have to treat power as a limited resource and take care to not waste their turns. I didn't get around to implement this in the end, but the resulting game seems more accessible anyway.
As you can see, I also wanted to make a Vapourvawe-themed game, with the aesthetics, music, and connotations that go with it. Unfortunately, my friend didn't have time to make any music for me this time, and I couldn't hope to create anything sounding nearly convincing enough, so I boiled this bit down to Windows 95-like look. The art style for the story pictures (which indeed only serve to establish an atmosphere) is very simplistic and not quite what I would consider good graphics, but I think they fit the 90's very well. An additional benefit was that I could create them pretty fast, unlike background art e.g. in Primary Objective or hARBOuR.
My one and only piece of concept art for this game:
There were some other ideas I had to cut out of the game at some point.
Sign puzzle, resizing and scroll bars. I spent quite a long time (three+ hours, significant in a gamejam) implementing proper scroll bars for windows for any type of content. Based on this, I wanted a sign puzzle, which would be a puzzle with a lot more content than the window could show, so the player would have to use the scroll bars to navigate around it. There would be signs pointing in various directions scattered around the window, and the player would have to follow them to get to the true goal (instead of the fake ones). Some signs could have been riddle-type signs, e.g. "take the same direction as the first sign".
Basic block puzzle (a.k.a. fifteen puzzle) whose solution would look like a button that the player can click to receive their points.
More complex lockpick puzzles. The idea of the series of lockpick puzzles was to present the player with less and less direct ways to interact with the sliders. As one of the comments on my LD submission said, in the style of /r/programmerhumo(u)r. Unfortunately, I only got three variants in (direct, indirect, indirect with falling sliders). I was worried people would find these puzzles too boring, as they were technically just filler puzzles, but I guess they provided a nice break from the other puzzles. The final lockpick puzzle was the favourite of multiple people, and probably the bane of others.
At some point on the third day, I found myself with 10+ hours left, and a game that could be played from beginning to end. It didn't have all the puzzles of the final version yet, but I realised I had a lot of time for general polishing, and even attempting to make music. So I did …
FL Studio? Ableton? LMMS? No, my tool of choice was: code-generated music. Silly as it sounds, I didn't need anything complex, and I actually felt the most comfortable with this method. Here's how it works:
On game startup, I create the "instruments", or, more accurately, samples for the instruments. These are simply vectors (arrays) of floats of the particular instrument playing at various frequencies (2.5 octaves of a just-intonated chromatic scale). I created a bass-like sound with a quickly fading sine wave combined with a sawtooth wave, a lead sound with some PWM / square waves, and a jazzy organ sound with 4 octaves of sinewaves stacked on top of each other. The snare drums are simply white noise, the kick drums are a low frequency sinewave (with a downward frequency sweep). And that's it, samples done! The code for creating the samples really is that simple, it just adds some padding at the end, and makes a very quick fade-in and fade-out for each sample to avoid pops in the playback.
Then I needed some way to put these samples into patterns – or "riffs" as I called them, because I was already using the name pattern for something else – easily in code. The track data structure is simple enough – an array of objects which say what instrument they are, which beat of the track they start at, how fast they go relative to the track tempo (i.e. how many beats of the track should one beat in the riff take), and how many times they should repeat. This is converted into one big vector of ints called pattern (there it is), which stores the "notes" for all the riffs and instruments, at the beat frequency of the track. Repeats are unrolled and made explicit, note data is added to the instrument type of each riff, giving a lookup index valid in the big fat vector of samples. The pattern data is simply 0 for any silent beat. If one instrument plays, it is 1, followed by the index in the sample array. If two instruments play on the same beat, it is 2, followed by two indices, etc.
This pattern array is then handled in the actual sound sampling callback function, which is called asynchronously by native code a couple of times per second. The sound card (or rather the Flash / JS intermediary) expects the callback function to fill a buffer with samples, usually 8192 at a time (that is, 8192 floats for each stereo channel). To make sure this is not the source of a lot of lag, the buffer-filling code itself should be rather fast. This is the entire reasoning behind the seemingly heavy preprocessing done up till this point, with caching instrument samples and having pattern data in a linear format. In the buffer-filling loop, I only require two things for any sound that is currently playing – which vector to take data from, and what position to take it at. This information is determined and update before the loop (probably important), so no need to do 8192 condition checks for every channel. The padding added in the sample creation comes into play here – my code never "stops" a sound from playing in the buffer-filling loop. Instead, 8192 samples of silence are added at the end of the sample. After the loop, if any of the sounds ended up in their padding zone, they are removed.
I am happy with how this entire machinery works. Also very happy I was able to make it work with the limited time I had. I might make it into its own, very simplistic music tool at some point.
Let's plays and conclusion
That's mostly it for what I have to say about Batterycorp. I'm satisfied with the final result and I expect some nice ratings too! This week we (my girlfriend and I) have been playing a number of LD games on Twitch.
Here's the full playlist.
That was quite enjoyable. The timelapse is coming soon, I need some music for it, as always. If you have, thank you for reading this post mortem, and I hope you enjoyed it.