Trees in the Desert

Hope everyone had a good Fourth of July weekend! You know what I learned this week? Sleep is awesome. I slept until like 11 AM or noon every day this weekend. I needed it. This new schedule I’m following is crunchy and has been requiring a lot of late nights. We’ll see whether it’s sustainable. This next month in particular might be pretty rough, since my normal development schedule is going to be upended by preparing for and attending regional events.

As I mentioned in last week’s video log, I’ll be at SGC up in Frisco the weekend of July 17-19, and then the weekend after that (July 25-26), I’ll be down in Austin for Classic Game Fest. I’ve been preparing materials for these events, including buttons and flyers to give away and a demo build of Super Win the Game that will hopefully be a little less aimless than last year’s “here’s the entire game, have fun” build.

I probably won’t be bringing a playable build of Gunmetal Arcadia to these events, as there just isn’t enough content yet to warrant it. (The weekly in-dev builds I’ve been posting are the full extent of the game at the moment.) But I would like to have some sort of Gunmetal presence, so I’ve been working on an intro cutscene that can double as a teaser trailer. I threw together a quick test of what an intro might look like a few days ago with placeholder art and text.

At the moment, I’m using an XML sheet to define slides for this cutscene as pairings of images and text. In the future, I want to add support for simple animation and parallaxing effects. I’m shooting for something in the style of Ninja Gaiden (and countless other NES games), and I’m pretty happy with how it’s looking so far. I’ll almost certainly use this system for an ending as well as the intro, and depending on how long it takes me to make these slides, I may interject cutscenes throughout the rest of the game as well.

vireo_comp
Completely true made-up story: the design for Vireo began as “vaguely anime Michael Stipe.”

I’ve been having fun stretching my art legs a bit on this project. I have zero art training, and I know that it still shows a lot of the time, but I feel like I’m getting better at getting things to a “good enough” point where it doesn’t read as “embarrassing programmer art / replace before shipping.” I’ve been thinking more consciously about how to develop a consistent aesthetic for this game that feels authentic to NES games. I feel like I might’ve talked about this a bit in the past, but examining Faxanadu has been hugely revelatory in understanding how to play to the strengths and weaknesses of the NES color palette. Take a look at this scene from early in the game:

faxanadu_ref

Notice how each element is monochromatic: the walls are green, the background is orange, the enemies are blue, and the player character is a sort of pinkish hue. The NES color palette is notoriously lacking in dark or desaturated colors, but the bright, bold colors it offers don’t necessarily have to appear cartoony. Delineating each element by hue allows the scene to remain legible even when elements overlap in brightness. The background ranges from bright pumpkin orange to dark brown to almost total blackness, but it all reads as a background element because it’s united by a common hue, and although its luma is roughly equivalent to that of the walls, there’s no confusion as to which elements are solid and which can be walked past.

It’s also worth noting that the walls have a brighter, more distinct highlight than the background elements, so even in grayscale, even with very similar luma, there’s enough of a difference to distinguish between the two. This highlight is also a slightly different hue than the rest of the tile (pale yellow versus the forest green that comprises the majority), but it still reads as monochrome.

Besides conveying functionality at a glance, this design also lends itself well to palette swapping, as I talked about some months ago. And speaking of palettes — awkward segue — I finally implemented a feature this week that I’ve been wanting to do since a very long time ago. I’ll be discussing it in more detail in Wednesday’s video log, but here’s a quick look at palettized screen fades.

GunArc 2015-07-02 23-34-45-903

It took two separate attempts and fairly disparate implementations to get this to a state I was happy with, but it works, it’s fast, and I think it’s going to be one of those sort of subtle, intuitively authentic look-and-feel things that helps sell Gunmetal Arcadia as something that could have believably existed on the NES.

Synthesis

As of this last weekend, I’ve largely finished bringing over changes from my standalone synth tool into the game. It’s not totally done yet; there’s still a lot of optimization left to be done, but it’s stable enough that I can move on to other tasks for a time.

Porting diffs between these two environments is always a little bit more involved than I expect due to some considerations necessary for supporting a latency-free gameplay experience. The standalone tool only plays one “tune” at a time. A tune may be a looping piece of background music or a sound effect; in either case, it’s a single thing that may use all four available channels (two pulse waves, a triangle wave, and a random noise channel). In this environment, I never have to worry about how to handle multiple overlapping tunes, but more importantly, I don’t have to worry about inserting data into the stream just ahead of the write cursor, which greatly simplifies the implementation. This is nice for prototyping behavior in the standalone tool, but these differences do need to be reconciled when bringing these changes into the game.

Let’s take a step back and review how audio buffers work. An audio buffer is a block of memory that contains raw waveform data. For one-shot sound effects or very short loops, the data can often be loaded in its entirety, thrown over to the audio device, and never touched again. For longer pieces that would be impractical to keep in memory all at once, we have to stream the data. In this case, we allocate an audio buffer of a fixed size and dynamically write and rewrite its data as the audio device is playing it. If our source is a large WAV file, we may be able to copy the data directly; if it’s in MP3 or Ogg Vorbis format, we’ll have to decode the data first before copying it into the buffer. In the case of Super Win and Gunmetal, the source is in a minimal MIDI-like event timeline format, and the waveform data must be synthesized in real time before being copied into the buffer.

That’s not too bad. Each frame, we can query the audio device to find the cursor positions in the buffer. We get two cursor positions back: a play cursor and a write cursor. The play cursor indicates the point from which the audio device is currently reading and playing data. The write cursor will be a short distance ahead of the play cursor. This indicates the point at which we can safely write new data into the buffer. The region between the two cursors, which the audio device will be playing imminently, is considered unsafe for writing. Knowing these, we can write some portion of data starting from the write cursor and then sleep for a while, waking in time to continue writing before the device catches up to us.

audio_buffer

For a single piece of background music, that’s pretty much the end of the story. Where this gets a little tricky is in dealing with sound effects that must play quickly in reaction to gameplay events. In order to minimize the perception of latency, we need to insert these into the stream as close to the play cursor as possible. As we know, the closest we can get to the play cursor is the point designated by write cursor, so we want to insert the new data there. We will almost certainly have already written data from another tune to this region, though, so we need to make sure to mix these together correctly and not obliterate existing data.

Where this gets really tricky is in applying realtime effects like reverb and filters to the entire mix. Because we may wish to insert new data into the stream in response to gameplay events, we can’t know for sure that a region of the buffer is completely finalized and won’t be changing again until the write cursor has passed it and it has entered the unsafe region between the play and write cursors. At this point, it will be too late to apply effects, as we can no longer alter that portion of the buffer. Instead, what I do is save off the state of each effect starting from wherever the write cursor was on the previous frame. From there, I can step forward through the data in the buffer, in the unsafe region, up to the current write cursor, and save off the state of the effect at that point. These saved states give me a “known-good” position from which I can advance the effect forward to the end of whatever data I’m writing. If I end up inserting new data into the stream before the write cursor has advanced, I can apply the effect again starting from the same point and trust that the output will be correct. Only once the write cursor has advanced and I know that the data is the unsafe region is final do I advance the saved state of the effect up to the new write cursor and begin the process again.

The exact nature of these saved states depends on the effect, but they generally involve saving off waveform data or something calculated from waveform data. For temporal effects like reverb, I maintain a series of ring buffers of decreasing sizes that can be used to produce a dense array of echoes. For first-order low-pass and high-pass filters, I only need to save off the input and output values for the previous waveform sample. (For Nth-order filters, the previous N values could be recorded instead.) For dynamic DC bias, I maintain a running average of the wave values for a window and shift the output by this mean to keep it roughly centered on the line.

The downside to this method is that I often end up synthesizing data or applying effects more than once, and those costs begin to add up. It’s possible in the future I may move away from realtime synthesis entirely in favor of converting these synth tunes into WAV, MP3, or Ogg Vorbis formats upfront. There are advantages and disadvantages to each option. Improving runtime performance is a very compelling argument for this conversion, as perf is becoming increasingly important as the synthesis grows more complex and CPU-intensive. On the other hand, the current implementation offers a drastically reduced file size of shipping binaries, which is nice not only for decreasing download times but also for minimizing the size of my source control repositories, as binaries generally can’t be diffed and must be stored in their entirety for even small changes.

With any luck, this should be the last devlog for a while that’s devoid of screenshots. I know I’ve been leaning hard on the walls of text recently, and now that this work is wrapping up, I should hopefully be able to get back to producing new content again. Stay tuned!

In-Dev Build #2

Changes since previous version:

  • Brought over recent changes from synth tool
    • Better approximation of 2A03 sounds
      • Implemented looped noise mode
      • Restricted noise to the correct 16 frequencies
      • Quantized changes to 60Hz updates
      • Low- and high-pass filters at 14KHz and 90Hz
      • Non-linear mixing
    • Better support for dynamic instrument voices
      • ADSR envelopes
      • Pitch envelopes
      • Vibrato
      • Tremolo
      • Pulse width modulation
    • Added oversampling option to reduce high frequency aliasing
    • Dynamic DC bias
  • Added “audiothread” console command to report on thead health

Windows: GunArc.zip
Mac OS X: GunArc.dmg
Linux: GunArc.tar.gz

Twisty Little Passages

Hey, wow, it’s been a whole week already since that thing I did! Following last week’s introduction to my new development documentation plan, I put up another video on Wednesday and another in-dev build on Saturday. I feel like this is a schedule I should be able to maintain. It’ll be crunchy, and I can’t promise I won’t miss a week here or there, but I feel like I’ve streamlined the process well enough that I won’t be spending all my time preparing this content and leaving no room for development.

Still pretty much the state of the game.

This week was the first time since early April that I was really able to dig into some Gunmetal Arcadia development tasks. Whatever momentum I had had on content production has been cut short, and in the interim, I’ve decided to focus on some core systems that don’t exist yet. I’m not talking about moment-to-moment gameplay systems; by and large, those have been finished since late March, barring whatever one-off work is required for weapons, items, abilities, enemies, bosses, NPCs, or environments that I haven’t designed or built yet. No, I’m talking about systems that define how a single session of Gunmetal Arcadia is structured, how persistent data is carried over across multiple sessions, how daily challenges will be implemented, and so on.

I’ve started by working towards making random number generation higher quality and easier to wrangle. I’ve usually just assumed that the built-in C++ rand() function will be good enough for my purposes, but in light of my efforts to serialize the complete game state, I need a better solution, and that means rolling my own RNGs. Well, not my own own; I’m using a bog standard Mersenne Twister implementation (MT19937). This guarantees consistent behavior on all platforms, facilitates the use of multiple RNG streams, and allows me to save and restore the state of RNGs across multiple runs. That last one is what I’ve been pursuing this week. The state of a Mersenne Twister can be stored as 625 integers (624 for the internal state and one index into this state array), which is a relatively small amount of data but too heavy to fit nicely into my existing serialization system. My saved game data consists of nested key-value pairs, all represented as strings. This is convenient for a number of reasons (easy conversion to and from XML, etc.), but for anything more than a small handful of variables, it’s too unwieldy. I ran into this limitation previously when I was implementing speedrun ghosts for Super Win the Game. For these, I ended up doing a one-off implementation in which a speedrun component managed its own file I/O. That was sufficient for a quick post-launch feature, but for Gunmetal Arcadia, I need to build with the future in mind, and that means spinning this into a real system.

I’ve already written extensively about my entity serialization in past entries, but the high-level overview is:

  • When entities’ components are destroyed, they write out any data necessary to restore them to their current state. This data is saved in the serializer, keyed by the entity’s GUID. When the entity is rebuilt, this data is retrieved and applied to the newly constructed components.
  • Upon request, the serializer may write out all its stored data to disk. This data represents the entirety of the player’s saved game. Once loaded back into memory, this data can be retrieved and used to rebuild entities as normal.

In general, I think of this pattern as going: [live data] ↔ [serial data in memory] ↔ [serial data on disk]. In extending this system to account for arbitrary binary data, I had to decide whether or not to follow this same pattern exactly. What I’ve done for now is, rather than storing potentially large amounts of binary data for inactive entities in memory all the time, I’m writing the binary data to disk immediately but in a temporary location. Only once the serializer is prompted to save the player’s game is this file copied to the normal saved game folder. In this way, the behavior mirrors that of the ordinary nested key-value data path, but without the need to keep this data in memory. This is still a very young system, and it feels like it’ll take a few more revisions before I’m totally happy with it, but at least in this first test case of saving and restoring the states of random number generators, it’s proved successful.

In-Dev Build #1

Changes since previous version:

  • Added a placeholder character selection screen
  • Added a component for managing random number generators
  • Added a framework for associating arbitrary binary data on disk with serialized data for saved games
  • Fixed a reference counting bug in file streams
  • Updated in-dev build deployment scripts
    • Windows binaries are now built with SDL and OpenGL to eliminate the need for an installer
    • Fixed some naming conventions in Linux builds

Windows: GunArc.zip
Mac OS X: GunArc.dmg
Linux: GunArc.tar.gz