Comedy is all about... timing.

In the case of my game concept, so is user input.

"So what is this crazy game concept you keep talking about?"

Here it is:

Do you remember Super Mario RPG and timed hits? It was a fun addition to the usual turn-based RPG style game that added an extra layer of player interaction.

It was a nice touch that made the game very unique, in my opinion. I was surprised that it didn't seem to catch on. But it got me thinking...

I'm a music composer and sound designer; why not combine those skills with the concept of timed hits?

For this demo, I (quickly) made some sound effects using musical sounds. There is a background layer to keep time -- a simple 4/4 beat. When you send a character to attack, he first must arrive close enough to attack, and then he begins "priming".

During this "priming" phase, the character will pulse in time with the music. Then you click on the target and if you are on the beat, you get an extra power boost. Yay!

Depending on other factors, there will be other ways to maximize attacks. However be aware, in this demo, the enemies can fight back if you get too close!

Music, Audio and Timing


Before we can time our hits, we need to time our audio! For this we use the Web Audio API. I learned from this great information here.

For those of you into instant gratification: here is the code used to schedule the music.

var lastBeatTime = null;
var lastBeat = null;
var nextBeatTime = null; // when will the next beat occur
var nextBeat = null; // 32 total (8 bars of 4)
var nextFrameTime = null; // when will the next frame draw?
// tempo (in beats per minute)
var tempo = 125.0;
// these must be recalculated if the tempo changes 
var secondsPerBeat = 60.0 / tempo;
var framesPerBeat = 3600 / tempo;

var calibration = -0.001; 
// use this to fudge the click time back for accuracy (clicking late "sounds" correct)

window.onload = function (){
 
    //make sure the webkit Audio API is available
    try {   // Fix up for prefixing
        window.AudioContext = window.AudioContext||window.webkitAudioContext;
        audioctx = new AudioContext();
    } catch(e) {
        alert('Web Audio API is not supported in this browser');
    }

    //load the audio (code for this class is below)
    gMusic.parseAudioFile();
}

function startMusic(){
    secondsPerBeat = 60.0 / tempo;
    framesPerBeat = 3600 / tempo;
    nextBeat = 32;
    lastBeat = 31;
    nextBeatTime = audioctx.currentTime;
    lastBeatTime = nextBeatTime - secondsPerBeat;

}

function scheduler(time){
    //schedule the next beat
    lastBeat = nextBeat;
    lastBeatTime = nextBeatTime;
    if (nextBeat === 16) nextBeat = 1;
        else nextBeat++;
    nextBeatTime += 60.0 / tempo; // schedule the next quarter note
    scheduleSound(nextBeat,nextBeatTime);
}

function scheduleSound(beatNumber,time){
    // create an oscillator -- it makes the beep
    var osc = audioctx.createOscillator();
    osc.connect( audioctx.destination );
    if (beatNumber % 32 === 1) {
        gMusic.playSound("Pad1.wav",time);
  }
    if (beatNumber % 16 === 1) {
        gMusic.playSound("Beat1.wav",time);
        gMusic.playSound("Drums3.5.wav",time + (secondsPerBeat*2.5));
    }
}

function animate(){
    var NOW = audioctx.currentTime; //gets the time of the last draw...when is now?
    nextFrameTime = NOW + (1 / 60); // calculate when this frame draw will occur
    if (nextBeatTime <= nextFrameTime) scheduler(nextFrameTime);
    //calculate the part of the beat the frame will draw at (as a percentage)
    var bp = Math.round((nextFrameTime - lastBeatTime) / (nextBeatTime - lastBeatTime) * 100);
      
    // ALL OTHER ANIMATION OCCURS
}

gMusic is an instance of an AudioFontClass I created. More on that later, but this is the playSound function it uses when scheduling audio.

playSound: function (name,time) {
    //first find the buffer
    var b = null;
    for(var i = this.sounds.length - 1; i >=0; i--) {
        // find the buffer for the name
        if(this.sounds[i].name === name) b = this.sounds[i].buffer;
    }
    // if we don't find the sound, do nothing
    if (!b) return;

    var soundSource = audioctx.createBufferSource();
    //schedule the sound
    soundSource.buffer = b;
    soundSource.connect(audioctx.destination);
    soundSource.start(time);
} 

How does it work?

Simple: each animation frame checks to see if the next frame will happen after the current beat. If it will, it calls the scheduler to schedule the audio for the next beat. The scheduled keeps track of which beat it is on, and loops the music every 4 bars (or 1 or 2).

The nice thing about the Web Audio API is that you can schedule audio way ahead of time, so nothing plays late.

However, one of the future challenges in making this web game will be keeping the load time down. Too much audio means longer load times.

For now, try the demo here! Enjoy the terrible artwork!

Controls are as follows:

Click and drag from a character to aim your "wave attack".

A single click will select him. If you release on the character, you can click elsewhere to move, or an enemy to attack. (Don't forget to click the enemy when you arrive for a "timed" attack! On the beat gives you a boost!)

Once selected, Z and X cycle through the colors of the "wave attack". Different waves will have a different effect on the enemies.

A second click on a selected character will bring up a menu.

You can choose a formation: some formations require a certain arrangement to already exist. Try moving characters around first. Be aware that the enemies react to formation changes.

Taunting an enemy or protecting an ally can help keep the enemies away from wounded allies.

And escape... well, you don't need that.

0 comments: