What We're Making

js13kGames

js13kGames is a yearly game development competition created by Andrzej Mazur. It runs for a month and has one main constraint: don't go over 13k. It's a similar concept to other code golf competitions, but this one is strictly for games.

Including images and audio eats up a huge portion of your available size. However, if you generate them with code, you can get a lot more bang for your buck. This post will cover one possible method of using audio in your game, while still leaving a lot of room for actual game code. I used something similar to this in my entry for last year's competition.

Dependencies: as3fxr + jsfxr

as3fxr* is a sound effect generator created by Tom Vian. It's a great way to quickly create retro style sound effects. This is where we will be creating all of our sound settings.

jsfxr is an as3fxr JavaScript port by Markus Neubrand. With this library, we can turn our as3fxr sound settings into audio elements. The library itself clocks in at 2,517 bytes (Closure with Advanced Mode), which is a lot compared to our total of 13k, but well worth it.

*as3fxr is a Flash port of the original C++ sfxr by Tomas Pettersson. Yep, we're utilizing a port of a port of a port.

Overview

In a nutshell, we will:

  1. Create a wrapper for our audio functionality called ArcadeAudio
  2. Add named sounds with an amount to be pooled and an array of as3fxr settings
  3. Create multiple (pool count) audio elements with jsfxr for each of those sounds
  4. Cycle through each audio element as they are played which will allow for multichannel audio
  5. Select random audio elements from sounds that are defined from multiple as3fxr settings which is great for subtle sound variations
  6. Attach the play functionality to some buttons

Implementation


Constructor

  function ArcadeAudio() {
  this.sounds = {};
}

Kick things off and create a object to hold our sounds effects.


Adding Sounds

  ArcadeAudio.prototype.add = function( key, count, settings ) {
  this.sounds[ key ] = [];
  settings.forEach( function( elem, index ) {
    this.sounds[ key ].push( {
      tick: 0,
      count: count,
      pool: []
    } );
    for( var i = 0; i < count; i++ ) {
      var audio = new Audio();
      audio.src = jsfxr( elem );
      this.sounds[ key ][ index ].pool.push( audio );
    }
  }, this );
};

For each of our sound effects, we need:

  • key: The name of the sound effect
  • count: The amount of audio elements to created for the pool
  • settings: An array or arrays of settings exported from as3fxr

For each sound, we create a pool of audio elements to pull from, based on the count variable passed in. If multiple as3fxr settings are passed in for one sound, they become an assortment that gets randomly pulled from when they are played. This is nice for creating organic variations in similar sounds. For example, the thud of your character hitting the ground could have four slightly different variations.


Playing Sounds

  ArcadeAudio.prototype.play = function( key ) {
  var sound = this.sounds[ key ];
  var soundData = sound.length > 1 ? sound[ Math.floor( Math.random() * sound.length ) ] : sound[ 0 ];
  soundData.pool[ soundData.tick ].play();
  soundData.tick < soundData.count - 1 ? soundData.tick++ : soundData.tick = 0;
};

This method grabs the sound by name, and then determines whether it is a sound with single or multiple settings. If it's single, it uses it, if it's multiple, it gets a random settings array. Then, it plays the sound at the current pool index and increments that number for the next time we play the sound. This way, we can cycle through each of the audio elements in order to decrease the chance of trying to play an audio element while it is already playing.


Integrating into a Game

  var aa = new ArcadeAudio();

aa.add( 'powerup', 10,
  [
    [0,,0.01,,0.4384,0.2,,0.12,0.28,1,0.65,,,0.0419,,,,,1,,,,,0.3]
  ]
);

aa.add( 'laser', 5,
  [
    [2,,0.2,,0.1753,0.64,,-0.5261,,,,,,0.5522,-0.564,,,,1,,,,,0.25],
    [0,,0.16,0.18,0.18,0.47,0.0084,-0.26,,,,,,0.74,-1,,-0.76,,1,,,,,0.15]
  ]
);

aa.add( 'damage', 3,
  [
    [3,,0.0138,,0.2701,0.4935,,-0.6881,,,,,,,,,,,1,,,,,0.25],
    [0,,0.0639,,0.2425,0.7582,,-0.6217,,,,,,0.4039,,,,,1,,,,,0.25],
    [3,,0.0948,,0.2116,0.7188,,-0.6372,,,,,,,,,,,1,,,0.2236,,0.25],
    [3,,0.1606,0.5988,0.2957,0.1157,,-0.3921,,,,,,,,,0.3225,-0.2522,1,,,,,0.25],
    [3,,0.1726,0.2496,0.2116,0.0623,,-0.2096,,,,,,,,,0.2665,-0.1459,1,,,,,0.25],
    [3,,0.1645,0.7236,0.3402,0.0317,,,,,,,,,,,,,1,,,,,0.25]
  ]
);

document.getElementById( 'powerup' ).addEventListener( 'mousedown', function(){
  aa.play( 'powerup' );
});

document.getElementById( 'laser' ).addEventListener( 'mousedown', function(){
  aa.play( 'laser' );
});

document.getElementById( 'damage' ).addEventListener( 'mousedown', function(){
  aa.play( 'damage' );
});

To integrate it into our game, first we have to instantiate a new ArcadeAudio. Then we can add our sound effects. I sampled a few sounds from my 2013 entry, Radius Raid. To create your own sounds, go to as3fxr, configure your sound, then press ctrl + c to copy the settings. Paste them into your settings and you're good to go.

It's important to note that the value you choose for count will depend on how that particular sound gets used in your game. A sound that's long or that happens in frequent succession should have a higher value, as there is a greater chance of multiple overlapping instances of the sound. If you do hit the maximum pool count and the first audio element is still playing, then a new sound will simply not play. This can create some gaps in your audio. Single sounds, such as something that occurs once at the end of each level, should have a very low count, as there is a low chance of any overlap. You'll have to feel out what pool count works for each sound. Don't crank the count too high though because it takes time when your game loads to create those sounds.

And finally, the last section attaches event listeners to each button and plays the appropriate sound on mousedown.

Putting it all Together

If you have any questions or suggestions on how this code can be improved, please leave a comment below!


5973 4 19