I had told myself for the longest time that sounds were a lower priority than getting in core gameplay features. I guess that might be true to an extent, but now that I'm playing the game with sounds, I'm really regretting not adding audio months ago, because the game feels much better with sound effects. (And also some accompanying screen-shake effects, which really help to give the sounds some added punch.)
Sound Design
Sounds in StarWright can be grouped broadly into two categories: User interface sounds and gameplay sounds. User interface sounds are easy because they never vary in volume or pitch and always get played whenever the user interacts with the interface. Gameplay sounds on the other hand are much trickier to design and implement because whether they're played, their volume, and sometimes even their pitch depend on what's happening in the gameplay.
Designing gameplay sounds for any game is hard, but StarWright has proven to be by far the hardest sound design challenge I've yet tackled. There are two primary reasons for this challenge:
First, unlike most games where the area that the player can see is relatively constant, in StarWright the scale of the player's view can change dramatically as the player zooms in and out; the player's view can scale from a dozen meters across to a kilometer across or more in a second. What the player hears when zoomed in at 10 meters (the loading of ammo and beeps of controls and charging of lasers) is not what the player should hear at 100 meters (the muted, distant reverb of cannons and thrusters) nor what the player should hear at 1000 meters (the almost-empty yet strangely-beautiful resonance of space).
Many of the more-detailed gameplay sounds (such as beeping controls, loading ammo, and charging weapons) are only audible when zoomed-in near the 10-meter scale; they quickly fade out as the player zooms away. But the louder sounds (such as firing cannons, projectile impacts, and explosions) can be hard at the 100-meter scale as well. However, I wanted these louder sounds to sound more muted and distant when the player is zoomed out, and so each of these sounds is actually authored as two separate sounds; one containing higher-frequency elements that fade out quickly as the player zooms out, and one containing lower-frequency elements that fade out much more slowly. Eventually though, near the 1000-meter scale, no gameplay sounds are audible and the only sound left is the eerie sound of space itself. (Yes, I know space IRL has no sound, but in StarWright it does!)
Sounds don't just fade out as the player zooms out; they also fade out as their point-of-origin moves farther away from the center of the player's view. This works like most games in that more distant sounds are quieter (an enemy shooting you from ten meters away is a lot louder than one shooting at you from a hundred), with the added wrinkle that the distance that a sound can travel increases as the player zooms out the camera. This may seem unnatural, but if the distance didn't scale then the player would either (when zoomed in) be able to hear loudly a lot of sounds from objects they can't see or (when zoomed out) not hear sounds from objects that they can see.
The effect of fading sounds as the player zooms out while simultaneously increasing their audible distance leads to a nice "level of detail" -- that is, when zoomed in, the player hears a relatively small number of higher-pitched, high-details sounds; whereas when zoomed out, the player hears a large number of more muted, less-detailed sounds.
The other big challenge to designing sounds for StarWright is the sheer number of objects that can make sounds. While a typical side-scroller or action game might have a few dozen sound-emitting objects (on screen or near the player) at once, StarWright can have hundreds or even thousands since every weapon, thruster, projectile, explosion, and crew member could be making a sound. And the problem with so many sounds at once (aside from being computationally expensive) is that when they all combine together, too many sounds will start to be like gray noise and the individual details will get lost in the chaos; that's neither pleasant to listen to nor useful information for the player.
My solution is conceptually simple enough: limit the number of gameplay sounds of any single type that can be played at once; the theory being that there should be enough simultaneous sounds that the player won't notice when some don't play while also not having so many sounds that they just create unrecognizable noise.
Gameplay sounds in StarWright can be subdivided into two general groups: One-shot sounds and continuous sounds. One-shot sounds are those that are tied to a particular event happening in the game (such as a cannon firing or a battery being delivered) and are over within a few seconds. Continuous sounds loop indefinitely for as long as some state is true (such as while the control room is powered). The ways in which each of these two groups of sounds is limited differs significantly:
For one-shot sounds, there is a simple limit to the number of sounds of any particular type (such as the sound of the regular cannon firing) that can be played simultaneously. (Usually around 3-5 simultaneous sounds, which seems to be about the point at which players will stop noticing when some don't play.) When a one-shot sound is about to be played, the game checks how many other sounds of the same type are playing, and if the maximum number is already reached, it simply doesn't play the sound.
Continuous sounds are a bit more complicated. Any particular type of continuous sound is typically limited to only 1 playing at once. But unlike one-shot sounds, once the limit is reached, any additional continuous sounds will increase the volume of the currently-playing sound. For example, two thruster rumble sounds playing at 0.5 volume will get combined into one thruster rumble sound playing at 1.0 volume.
Sound Implementation
One of the biggest reasons that it took me so long to release 0.5.0 is that I almost completely rewrote my audio-playing code.
StarWright is based on the same codebase that I originally created for my game Tanky-Tank almost a decade ago. At the time, the best API for playing sounds on Windows was DirectSound. However, in the many years since, DirectSound has been obsoleted by the much-more-modern and well-maintained XAudio2 (and on newer systems, DirectSound is actually emulated on top of XAudio2).
When I started working on sounds for StarWright, I started with my old DirectSound-based code, but I quickly ran into some nasty race conditions that would cause crashes in the above-discussed sound-limiting code. These race conditions were going to be very difficult if not impossible to work around, and so I chose to spend a few days rewriting all of my audio code to use XAudio2 instead of DirectSound.
This rewrite went relatively smoothly, except that I found a few bugs in SharpDX, the C#-language DirectX wrapper I use. I spent at least a day tracking down and then fixing those bugs in SharpDX. (But on the bright side, I had my first-ever GitHub pull request accepted to fix those bugs!)
Screen Shake!
The greatest lesson I've learned in my years as a game developer is that every game is made better by adding screen shake!
While this may be a bit of an exaggeration, it's true enough for StarWright.
You may notice while playing that a lot of explosions and firing sounds are accompanied by a subtle (or not-so-subtle) screen shake effect. They started as kind of a silly experiment but they really added a great sense of weight and power to the louder, more explosion-y sounds. When you get hit by a Large Cannon, it feels quite impactful, and a lot of that has to do with the accompanying screen shake.
Screen shakes work a lot like sounds in the sense that they get weaker as the player zooms out or as the origin gets farther from the center of the screen. (And yes, that distance-to-center is scaled by the zoom, just like sounds.) But unlike sounds, there is only one type of screen shake, and so the magnitudes of all the currently-in-effect screen shakes get added together to determine the final amount by which the screen is actually shaking.