Take to the skies! Take the skies... recursively.

If the game has been too much fun for you...

I kid. I'll finish it someday. Probably.

In the meantime, I've been taking an Introduction to Aeronautical Engineering course on edx.org. It's a great course (and free) from Delft Technical University. If you are interested in Aerospace, start there. It'll help you determine if you like math enough...

It turns out: I do like math.

So much so, that I programmed this International Standard Atmosphere Calculator. Using the 1976 international standard (which apparently hasn't changed since) you can plug in your geopotential altitude and get the air density and pressure. Very handy if you want check your flight altitude to make sure you won't collide with any other airplanes. Or if you need to do your Aerospace homework.

I added a nice graph, so you can just click the altitude rather than typing it. I hope to add unit conversions and also altitude prediction based on pressure or air density. One thing at a time.

Programming this little job also gave me a good excuse to use a recursive function.

Re...CURSES!

If you have ever taken a programming course, you've been taught about recursion. And then promptly forgotten it thinking, "that's too confusing! I'll just do it a different way..."

And you can solve most problems without ever using recursion. So why bother right?

Because it is so much TIDIER!

In the case of ISA, we have a piece-wise function that stacks on more function pieces as we progress to higher altitudes. Lapse rates change and we glide through isothermal layers... nevermind. Back to recursion!

Without getting too into depth on ISA calculations, let's just say this:
If we want to calculate the air density for an altitude of 40 kilometers, we need to first calculate it for 11 using one equation, then up to 20 with another, then up to 32; all based on the calculations made for lower layers.
If we want to calculate at higher altitudes, we need even MORE equations dependent on all those below.

So how does recursion help?

Recursion allows us to call one function for any altitude, that calls itself for the lower calculations. Then it spits out just the answer we need.

Without recursion, we'd have to write separate if.. then... statements for each case (retyping a lot of the same code). OR (as some ISA calculators do) we could cheat and store the key values for each layer in an array. But what's the fun in that?

Here's the commented code:

//this function takes two parameters:
//alt is altitude in meters
//To is temperature offset in Kelvin
function getAtmosphere(alt,To) {
    // constants!
    var R = 287.058; //gas constant
    var TEMP = 288.15; //in Kelvin
    var ATM1 = 101325; //in Pascals
    var DENSITY = 1.225; //in kg/cubic meter

    // range check (altitude in meters)
    if (alt > 84852) alt = 84852;
    if (alt < 0) alt = 0;

    //this is our OUT. no infinite recursion!
    //it returns 1 standard atmosphere at sea level in an object
    if (alt === 0) { //sea level
        return {"alt":alt,"T":TEMP+To,"To":To,"p":ATM1,"rho":DENSITY};
    } 

    // THIS IS WHERE THE MAGIC HAPPENS!
    // the function calls ITSELF...
    var atm0 = getAtmosphere(getAltRange(alt),To);
    // getAltRange() is a simple function that returns the altitude for
    // the "layer" beneath. This is used to find the correct lapse rate
    // and equation below...

    // lapseRate() returns the rate used to calculate temp for a given layer
    // here is where the calculation is done.
    // notice it uses output from itself (atm0)
    if (lapseRate(alt) != 0) {
        var T1 = atm0.T + lapseRate(alt)*(alt-getAltRange(alt));
        var rho1 = atm0.rho*Math.pow(T1/atm0.T,(-9.80665/(lapseRate(alt)*R)-1));
        var p1 = atm0.p*Math.pow(T1/atm0.T,-9.80665/(lapseRate(alt)*R)); 
    } else { // lapseRate = 0
        var con = Math.pow(Math.E,-9.80665/(R*atm0.T)*(alt-getAltRange(alt)));
        var T1 = atm0.T
        var p1 = atm0.p * con;
        var rho1 = atm0.rho * con;
    }

    // calculations "complete" it returns the data as an object
    return {"alt":alt,"T":T1,"To":To,"p":p1,"rho":rho1};
}
So wait? How can a function call itself and then use its own output to calculate. If it doesn't have the input... how can it give output... wouldn't it fail or return 'undefined'??

Well, no. Let's look at an example:

Let's say we call getAtmosphere(25000).

The function needs input from getAtmosphere(20000), so it calls that and waits...

THAT call needs input from getAtmosphere(11000), so it calls that and waits...

THAT call needs input from getAtmosphere(0), so it calls it and... WAIT! That's the out!

Once it calls that, it gets data returned! This is the KEY to recursion. It hits an end, now it can finish!

Then, getAtmosphere(11000) takes the input from (0) and runs the calculation. It passes that to (20000) which calculates and passes to (25000). And like magic, we get our data.

And no matter what altitude we call it on, it knows just how many times to call itself to get the right answer! Brilliant!

I think I've waited 15 years to have a need for recursion. And I'm proud to say I only froze my browser once...

Don't forget the OUT!

EDIT:

I have updated the calculator to properly return the density when there is a temperature offset. You can now change units and get the temperature up to 1000 km. A few other minor adjustments for accuracy too. Coming soon: density and pressure over 86 km!

Also, feel free to fork on GitHub!

0 comments:

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:

Enough Fun and Games

3:50 PM , , 0 Comments

Ok, So I am taking a short break from the game to do some real work. Sort of.

In learning Javascript, I decided it would be worth my while to learn some jQuery as well. Turns out it is super-easy and useful for making simple applications that have "real work" uses.

I developed two small tools that could be used in a number of ways. One is just a big-old table of contents style drop down, complete with link descriptions.

The other is what I call a "workflow" that could be used for giving instructions or even trouble-shooting common issues. Check it out here.

Both are easily adaptable and open source. So feel free to pilfer the code. Feel free to ask me questions if you need help using it.

Table of Contents

For this little app, I just needed a list that I could easily add links to and would fold itself up, so there wouldn't be too many on a page to look at at once. You can just look at the relevant section and ignore the rest.

First, each "category" is listed inside  list tag with class "slide". Then underneath, we create a list of links with class "articles". You can make unlimited categories.
 
<ul class='slide'>
<li><h3>Category 1</H3></li>
    <ul class="articles">
 +Each line is split at the plus
 +type "#plus" if you want to actually see a plus sign as text
 +It automatically parses addresses as links that are listed with http:
 +the script parses out the part after "Description:" for the mouseover
 +download the code here! http://games.codeandcompose.com/WorkApps/sourcecode.zip
                Description:The best code ever written! Or not.
    </ul>
... 

The app splits each line in the "articles" list by the +. Then it creates a link with an address (if there is one starting with http:// or https://). If there is a description (split at "Description:"), that will show up when you hover.

There is a tiny cosmetic glitch with the hover descriptions. I haven't yet come up with a great fix, but it's not too noticeable.

The string parsing was pretty simple. It uses some regular expressions and Javascript string.replace() function. After breaking the strings, it puts them into list elements and links (when used).

 
$.fn.extend({
    stringSplitToList: function() //this function parses the html body into a list for the jquery
    {
        var htmlList = '', aclass = 'none';
        $.each($(this).html().split('+'), function(i, v) {
            v = v.replace(/#plus/g,"+");//put plusses back in
            var tempSplit = v.split("Description:");
            v = tempSplit[0];

            //makes sure the description not undefined.
            var description = (tempSplit[1]) ? tempSplit[1] : "No description."; 

            if (v.match(/(https:\/\/|http:\/\/)\S+/igm)) {
                var alink = v.match(/(https:\/\/|http:\/\/)\S+/igm)[0];
                var title = v.replace(alink,"");
                if (title.match(/\*/gm)){
                    title = title.replace("*","");
                    aclass = 'internal';
                } else if (title.match(/\^/gm)){
                    title = title.replace("^","");
                    aclass = 'archive';
                } else {
                    aclass = 'none';
                }
                htmlList += '<li><a href="' + alink + '" class="' + aclass + '">' + title + '</a><div class="des">' + description + '</div></li>';

            //handle categories header
            } else if (v.match(/(href=)\S+/igm)) {
                var alink = v.match(/(href=)\S+/igm)[0];
                var title = v.replace(alink,"");
                alink = alink.replace("href=","");
    
                if (title.match(/\*/gm)){
                    title = title.replace("*","");
                    aclass = 'internal';
                } else {
                    aclass = 'none';
                }
                htmlList += '<li><a href="' + alink + '" class="' + aclass + '">' + title + '</a><div class="des">' + description + '</div></li>';

                //handle categories header
                } else {
                    htmlList += '<li><div class="category">' + v + '</div></li>';
                }
        });
        $(this).html(htmlList);
    }
});

Each link can also be marked as "internal" with a * or "archived" with a ^. You can even use internal links marked by href=

Just call this line once the page loads and it parses the text for you.

$('.articles').each(function(index){$(this).stringSplitToList();});

Workflow App

The workflow app can take very complex paths. By following the formula in each js file, you can add unlimited questions and answers. As the user chooses their answers, links, pictures and tips are given to help them out. The logic can be created to direct to a certain path based on previous answers form one or multiple questions.

I used it to make a wi-fi router trouble-shooting guide. The example given on this website is just for fun though.

Here is a graffle showing how the questions are programmed.



Each question is loaded in the following format:

addQuestion(1, //the question number
         //the question
    "This is workflow 1. Use this workflow?",
        //answer options (as many as you want)
    ["Maybe","No","Yes"],
        //answer direction, either a question number or LOGIC or RESET
    [1,"LOGIC",1],1, // the extra number is default
        //tips text
    "This area will show helpful tips. Or not...",
        //image urls (as many as you want) or null
    ["image1.png","image2.png"],
        // url and text for helpful links
    [{"url":"http://tagsounds.com","title":"A Link to TAGSOUNDS.COM"}],
       // callback function for when the direction of an answer is LOGIC
    function(){
        if (AA[0] === "Red"){
            nextQuestion(201);
            return;
        } else loadWorkflow("workflow2.js");
    }
);

If you like it, or would like to know more about how to adapt it for your needs, please contact me using the contact form at tagsounds.com.


0 comments:

INPUT (Click me! Click me!)

One of the most important elements of game-play is user interaction.

So many RPG games are great tactical games, but lose the sense of real-time enjoyment while bouncing through menus. An early prototype of my game was menu centric. But as I make some mini demos, I'm trying to limit the amount of time spent in the menus in favor of faster input choices.

For this demo, there is no need for a menu. One click on a character selects him and then click the enemy and he attacks. OR, click and drag to aim the projectile, release to fire. Click twice on the character (not double-click for reasons I will explain) and then click an empty space to move them.

I'd like to keep it setup in a way that may be ported to touch screen devices. I imagine that will introduce new challenges. Perhaps I'll learn Objective-C first and make an app... ?

Anyway. Step one is capturing the click. Since everything is done inside the canvas, we use the following code:

window.onload = function (){
    var canvas = document.getElementById("gamewindow");
    document.getElementById('gamewindow').onmousemove = moveHandler;
    document.getElementById('gamewindow').onmousedown = clickHandler; //onclick is too slow for timing
    document.getElementById('gamewindow').onmouseup = clickReleaseHandler;
    document.onkeypress = onkeypressHandler;
}
function moveHandler(event){}
function clickHandler(event){}
function clickReleaseHandler(event){}
function onkeypressHandler(event){}
Notice that I used mousedown instead of onclick. This is important for calling functions quickly so that when the player tries to synchronize with the music, it is more accurate. Plus we can catch the mouseup event separately. The downside, is that differentiating click from double click is harder, so we just don't do it!

Each event listener is directed to a handler function. Now we have to do, you know, logic.

Unlike normal interactive elements, there are a few challenges to overcame when dealing with a live game environment. Starting with the fact that everything moves. Thus, step one is finding out what was clicked.

Or as I quickly learned, the FIRST step is to offset your click location to account for the placement of the canvas and scrolling...

function clickHandler(event){
    // cache the time/location of the click
    clickTime = audioctx.currentTime;

    event = event || window.event;
    var scrollX = window.pageXOffset || document.body.scrollLeft;
    var scrollY = window.pageYOffset || document.body.scrollTop;
    clickX = event.clientX + scrollX - xoffset;
    clickY = event.clientY + scrollY - yoffset;

    var gclick = guyClicked(clickX,clickY,"Allies",true);
}

function guyClicked(X,Y,dic,mustBeReady){
    var dict = activeGuyDict[dic];
    for (var guy in dict){
        if (!(X < dict[guy].pos.x + dict[guy].bounding.x || X > dict[guy].pos.x + dict[guy].bounding.x + dict[guy].bounding.w || Y < dict[guy].pos.y + dict[guy].bounding.y || Y > dict[guy].pos.y + dict[guy].bounding.y + dict[guy].bounding.h)){
            if ((mustBeReady && dict[guy].ready) || !mustBeReady) return guy;
            //character must be ready if "mustBeReady" is true
            else return null;
        }
    }
    //no guy was found
    return null;
}
If you don't feel like ripping through that IF statement in guyClicked, here's the rough explanation. It's a beautiful use of NOT OR logic. We need to see if the click occurred INSIDE the bounding box for the character. If the box is 50x100 at point (200,130), we see if the x was between 200 and 250 and if y was between 130 and 230, right?

We could setup the logic to see if x > 200 AND x < 250 AND y > 130 AND y < 230...

OR

... we could check if the click falls OUTSIDE the parameters, and use NOT OR. Only one condition must be true for the whole thing to fail. Not sure if it matters here, but this is useful if you are comparing BOXES to look for overlap.

For my game, we also check to see if the character is "ready" before selecting. Now onto the real tricky logic.

First click down gets the aiming out. Then release to choose an enemy. Then click an enemy or click the good guy again. Or click another guy to change the selection. Then click a spot to move. Or if an enemy was selected, click again to time the attack. Or, um... what else can you do with just a click? It gets complicated fast.

I found that (although counter-intuitive) it works best to write the logic backwards. Start with the most specific scenario first and work backwards through input order. That way, the handler can check for the specific scenarios and bail, or move on.

In plain text it looks like this:
  • Check to see if a selected character is waiting to move.
    • If a character was clicked, change focus
    • If an empty space was clicked, move
  • Check to see if an attacking character is "PRIMING".
    • Was the appropriate enemy clicked?
  • Check to see if an enemy must be selected to attack.
    • Did you click an enemy?
    • If a character was clicked change focus
  • If none of those criteria are met, select the clicked character.
   
for (var pg in activeGuyDict.Allies){
    // check to see if a character needs to move
    if (activeGuyDict.Allies[pg].moveForm){
        activeGuyDict.Allies[pg].moveForm = false;
        activeGuyDict.Allies[pg].selected = false;
        colorWheel.reset();
        //need to check if the space is occupied by the other character
        if (guyClicked(clickX,clickY,"Allies")) continue;
        activeGuyDict.Allies[pg].formation = {"name":"Formation","pos":{cx:clickX,cy:clickY},"halo":{cr:0,r:5}};
        activeGuyDict.Allies[pg].action = "FORMATION";
        activeGuyDict.Allies[pg].target = activeGuyDict.Allies[pg].formation;
        return;
    }
       
    if (activeGuyDict.Allies[pg].animation.name != 'PRIME') continue;
    if (!activeGuyDict.Allies[pg].attackTarget) continue;
    else var primeClick = guyClicked(clickX,clickY,"Enemies");
    if (!primeClick) continue;
    if (primeClick === activeGuyDict.Allies[pg].attackTarget){
        var cT = clickTime; //un-cache the click time
        activeGuyDict.Allies[pg].setAnim('ATTACK',cT);
     
        //this code determines the power, based on the timing
        var sync = 0;
        var hbl = secondsPerBeat / 2; //half a beat length
        if (cT - lastBeatTime < nextBeatTime - cT) sync = Math.round((cT - lastBeatTime)/hbl * 50);
        else sync = Math.round((nextBeatTime - cT)/hbl * -50);   
        //calculate the power/damage here
        var power = activeGuyDict.Allies[pg].power(sync);
        activeGuyDict.Enemies[activeGuyDict.Allies[pg].attackTarget].takeHit(power,pg);//determine the damage taken from the hit
        clickTime = null;
        return; //don't do anything else with the click
    }
}//end FOR loop
   
if (selectThis === "ENEMY"){//picking an enemy to attack
    var gc = guyClicked(clickX,clickY,"Allies",true);
    var g = findSelectedChar();
    if (gc) { // a good guy was clicked... handle it
        if (gc != g){ //a different ready character was selected..switches focus
            selectThis = "";
            activeGuyDict.Allies[g].selected = false;
            activeGuyDict.Allies[g].action = '';
            var cT = clickTime;
            activeGuyDict.Allies[gc].selected = true;
            colorWheel.newSC(gc);
            selectThis = "AIM";
            activeGuyDict.Allies[gc].setAnim('AIM',cT);
            activeGuyDict.Allies[gc].action = 'ATTACK';
            mouseDown = true;//keep track of the mouse state
            return;
        } else if (gc === g) { //same character was clicked... moveForm
            activeGuyDict.Allies[g].moveForm = true;
            //cancel ATTACK action
            selectThis = "";
            activeGuyDict.Allies[g].action = "";
            return;
        }
    }
    var e = guyClicked(clickX,clickY,"Enemies");
    if (!e) return; //make sure an enemy was clicked
    if (activeGuyDict.Allies[g].action === 'ATTACK'){
        activeGuyDict.Allies[g].ready = false;
        activeGuyDict.Allies[g].selected = false;
        colorWheel.reset();
        activeGuyDict.Allies[g].target = {"name":e,"pos":activeGuyDict.Enemies[e].pos,"halo":activeGuyDict.Enemies[e].halo};
        selectThis = "";
        colorWheel.reset();//close the color wheel
    } else if (activeGuyDict.Allies[g].action === 'TAUNT'){
        activeGuyDict.Enemies[e].taunted(activeGuyDict.Allies[g].pos,activeGuyDict.Allies[g].halo);
        activeGuyDict.Allies[g].animation.name = 'TAUNT';
        activeGuyDict.Allies[g].selected = false;
        colorWheel.reset();
        selectThis = "";
    }
    return;
}
//see if a ready guy was clicked
var gclick = guyClicked(clickX,clickY,"Allies",true);
if (gclick){
    var cT = clickTime;
    activeGuyDict.Allies[gclick].selected = true;
    colorWheel.newSC(gclick);
    selectThis = "AIM";
    activeGuyDict.Allies[gclick].setAnim('AIM',cT);
    activeGuyDict.Allies[gclick].action = 'ATTACK';
    mouseDown = true;//keep track of the mouse state
}
This is the mouseup handler. It only pays attention if the player is holding the mouse down to "AIM". Thus avoiding unnecessary event functions.
function clickReleaseHandler(event){
    // cache the time/location of the click
    if (selectThis != "AIM") return;
    clickTime = audioctx.currentTime;  
    event = event || window.event;
    var scrollX = window.pageXOffset || document.body.scrollLeft;
    var scrollY = window.pageYOffset || document.body.scrollTop;
    clickX = event.clientX + scrollX - xoffset;
    clickY = event.clientY + scrollY - yoffset;
            
    var g = findSelectedChar();
    var gc = guyClicked(clickX,clickY,"Allies");
    if (gc === g) {
            selectThis = "ENEMY";
            activeGuyDict.Allies[g].clearAnim();
            mouseDown = false;
            return; //bail
    } else {
        //they released on the aimed area
        var cT = clickTime;   
        var sync = 0;
        var hbl = secondsPerBeat / 2; //half a beat length
        //click is closer to last beat
        if (cT - lastBeatTime < nextBeatTime - cT) sync = Math.round((cT - lastBeatTime)/hbl * 50);
        else sync = Math.round((nextBeatTime - cT)/hbl * -50);//click is closer to next beat
        //click was before last beat (in case the beat changes between click and frame)
        //if (cT < lastBeatTime) sync = 50 - Math.round((lastBeatTime - cT)/hbl * 50);
        var power = activeGuyDict.Allies[g].power(sync);
        clickTime = null;

        //create the projectile
        projectiles.add("wave",activeGuyDict.Allies[g].pos,clickX,clickY,power,8,500,activeGuyDict.Allies[g].color);

        selectThis = "";
        activeGuyDict.Allies[g].ready = false;
        activeGuyDict.Allies[g].selected = false;
        activeGuyDict.Allies[g].setAnim('ATTACK',cT);
        activeGuyDict.Allies[g].target = null;
        mouseDown = false;
        return;
    }
}
As always, feel free to steal my code. Just let me know how you are using it! I'm curious to know how and if anyone finds this code useful!

And as promised, here's a link to the demo!

Instructions:
  • CLICK and DRAG from a character to shoot a projectile. They will not do damage, but different colors will have different effects,
  • PRESS Z or X to change the color of a character after he is selected.
  • Just CLICK a character once to select him, and then click an enemy to attack. The character will chase the enemy. You MUST CLICK the enemy once he has arrived in order to initiate the attack. Try to time it with the pulsing to do extra damage.
  • CLICK the selected character again, and then click an empty space to move him.
  • No damage indication is given, just keep attacking, and the enemies will eventually disappear.

0 comments:

Color and Dissonance

So what exactly is this game concept I keep mumbling about?

Well, here's part of the picture: You are immersed in a world with a mysterious energy force. Not much is known about it except two things. First, it's great for making powerful machines and weapons. And when it is harvested, horrifying and dangerous spirits are released.

Naturally, along your quest, you must face these spirits to survive. But you can't just beat them to oblivion like normal RPG monsters or zombies. They have to be "in phase".

As a composer, I decided that I should make a game that fit my specific knowledge and skill set. Thus, music and sound had to be solidly integrated into the game play. I'll get more into the music side later, but there is an underlying function of music in the battles as well.

Imagine each spirit is assigned a "note" or frequency. In addition to fighting them, you can also blast them with another "note" using the wave projectiles we created here. Depending on the "interval" between the notes, a different effect is achieved.

For those of you not familiar with music theory, this gets a little intense. And since I want the game play to be inclusive, I had to come up with a visual representation. So why not color each note?

This presented me with more of a challenge than I had expected. HOW do you color all 12 notes?

Many people have tried to color the scale for educational purposes, but usually focus only on a 7 note scale. With Roy G Biv, that works out nicely. But I had to get a little more creative.

Since the effect of the interval will be based on its "dissonance" or "consonance", I decided to match a  color wheel using the Circle of Fifths. If you know what I'm talking about, see the diagram. If not... um, well... take a music class.
The adjacent notes are all an interval of a 5th. Which is consonant. As you move around the circle, away from one note, the dissonance increases. For example, C is a semi-tone from B and a tri-tone from F#... very dissonant. This, similar colors blend, opposites, not so much.

For game play purposes I grouped them like this:
Unison (same note) will heal the enemy.
5th is consonant, so no effect.
2nd (whole-tone) will have a disruptive effect, like stopping the enemy for a moment.
Minor 3rd will have a positive effect, like speeding up the enemy.
Major 3rd will have a negative effect, like slowing.
Semi-tone and tri-tones are so disruptive they will actually change the note of the enemy.

The math for this works out really well too. The HSL color mode has 360 hues. That means the intervals can be determined by subtracting the color hues. For example, if C=150 and A=240, the difference, 90, represents a minor 3rd.

Now, it would be no fun to let each character have access to all 12 notes. So they get a "key palette". Basically, a set of notes that are available during battle.

I had to add some properties to my good guy object:
this.color = 150;//may be unnecessary = this.palette[0];
this.palette = [150,210,270,120,180,240,300];//an array of the colors available
this.changeStep = function(step){
    this.palette.rotate(step)
    this.color = this.palette[0];
    //return this.color;
};
The changeStep() function is called with an interval so the character can move through his palette (currently by pressing Z or X). What is palette.rotate()? A handy little method added to the Array.prototype that pushes items around the array in order. There are other methods of doing this here... but I liked this the best.
Array.prototype.rotate = (function() {
    return function(inc) {
        for (var l = this.length, inc = (Math.abs(inc) >= l && (inc %= l), inc < 0 && (inc += l), inc), i, x; inc; inc = (Math.ceil(l / inc) - 1) * inc - l + (l = inc))
        for (i = l; i > inc; x = this[--i], this[i] = this[i - inc], this[i - inc] = x);
        return this;
    };
})();
Then of course, we need to know when the projectile hits anything. So while we check for collisions between the enemy and other obstacles, we also call the projectile.HIT() method below. Now these waves don't dissipate upon hitting something, in fact they pass right through. So what's the best method for determining collisions?

Unlike collisions that ricochet, we don't need any fancy vector math to get the angles or energy transfer. All we really need to know is how close the hit was.

I decided on a simple formula for this. Once the collision is detected, we keep track of the exact distance between the center of the enemy and the center of the projectile. Then, when the distance starts to increase, we record the closest point and call the appropriate function for the enemy's reaction. That distance is passed to help determine the power (effectiveness) of the wave. This code is part of the "projectile" object constructor.
this.HIT = function (Epos,Ehalo,Ename){
    var dis = getDistanceSq(this.pos,Epos);
    if (dis.ds > (this.halo.r+Ehalo.cr)*(this.halo.r+Ehalo.cr)) return false;//not close enough
    //have it check to see if it has reached the closest location
    if (!this.hitList[Ename]) {
        this.hitList[Ename] = dis.ds;
        return true;
    }
    if (this.hitList[Ename] === "HIT") return false;
    // they have already hit...is it getting closer or farther?
    if (this.hitList[Ename] >= dis.ds) { //closer
        this.hitList[Ename] = dis.ds;
        return true;
    } else {
    // use the closest distance and calculate damage
        //takewave is the call that tells the enemy what to do with the "damage"
        activeGuyDict.Enemies[Ename].takeWave(this.mass,this.hitList[Ename],this.color);
        //remove the enemy from the list so he isn't hit again!
        this.hitList[Ename] = "HIT";
        delete this.hitList[Ename];
        return true;
    }
};
this.hitList = {};

// This is the helper function to get the squared distance 

function getDistanceSq(a,b){ //for when the square will do, to save energy rooting it...
    var x = b.cx - a.cx;
    var y = b.cy - a.cy;
    var ds = (x*x)+(y*y);
    return {ds:ds,x:x,y:y};
}
getDistanceSq() is a handy little function that uses ol' Pythagoras, but returns the distance squared (and the x and y vector) as an object. By using the square for comparison, we save the trouble of using a costly Math.SQRT().

One more blog post on the click cycle for battle mechanics, and then a DEMO! Yay! It will be playable, however without sound. And it won't be hard since you can't die yet... (one thing at a time).

0 comments:

Projectiles as Javascript Objects

7:33 AM , 0 Comments

In my game, like many games, there is a chance to throw, shoot, fling or otherwise propel something hazardous at your enemy.

I'll get more into the specific game mechanics in the next post. For now, we'll just focus on aiming and firing "something".

I chose to treat each projectile as a separate entity, just like the characters and enemies. However since there are an unknown number of projectiles coming and going at any time, they don't need names. Thus we use an array to collect them.

The nice thing about Javascript objects, is that we can make them do whatever we want. I decided to make a list object that not only keeps the projectiles, but adds and removes them thus:

var projectiles = {
    list:[],
    add : function (type,pos,x,y,mass,speed,range,color){
        var newP = new projectile(type,pos,x,y,mass,speed,range,color);
        newP.parent = this;
        this.list.push(newP);
    },
    remove : function (p){
        for (var i = this.list.length - 1; i >= 0; i--) {
            if (this.list[i] == p) {
                this.list.splice(i, 1);
                return;
            }
        }
    }
}

Then I created an object constructor for the projectiles thus:
function projectile(type,pos,x,y,mass,speed,range,color){
    this.parent = null;
    this.type = type;//wide, dart, explosive...etc
    this.pos = {"cx":pos.cx,"cy":pos.cy};
    this.halo = {cr:4,r:8};
    this.heading = normVec(pos,{"cx":x,"cy":y});//normalized vector vx and vy
    this.mass = mass;
    this.speed = 8;
    this.range = 500;
    this.irange = 500;
    this.color = color;
    this.delay = 25;//frame delay to allow for attack animation
    this.HIT = function (Epos,Ehalo,Ename){}
    this.hitList = {};
    this.move = function (){};
    this.kill = function (){
        this.parent.remove(this);
    };
}


Most of these properties can be used in many ways to determine the exact behaviors. For now I'll be focusing on the HIT() and move() functions.

The move() function will be called every frame and move the projectile by multiplying the heading by the speed. It also checks to verify that the range of the projectile has not been exceeded. If it has it calls kill().


this.move = function (){
    this.pos.cx += this.heading.vx * this.speed;
    this.pos.cy += this.heading.vy * this.speed;
    this.range -= this.speed;//loses power as it moves
    if (this.range <= 0) this.kill();
};

Easy. Now we have to DRAW them... For this game, the projectiles are actually a sound wave. So I used the arc() method of the canvas 2d context. By using some gradients and color stops, we can make a nice row of faded arcs. I created an "origin" for the center of the arcs 80 px behind the center. That way the arcs themselves are still drawn on the point that will be used for collisions. Then add a gradient that goes from the color to alpha 0. If you get the gradient just right, the arcs fade into nothing.


for (var p = projectiles.list.length - 1; p >= 0; p--){
    if (projectiles.list[p].delay > 0) {
        projectiles.list[p].delay--;
    } else {
        //draw the projectile
        var originx = projectiles.list[p].pos.cx - (projectiles.list[p].heading.vx * 80);
        var originy = projectiles.list[p].pos.cy - (projectiles.list[p].heading.vy * 80);
        var c = projectiles.list[p].color;
        var rangeD = projectiles.list[p].range;
        if (rangeD > 100) rangeD = 1;
        else rangeD = rangeD / 100;
        var dr = projectiles.list[p].irange - projectiles.list[p].range;
        for (n = 1; n<=8; n++){
            if (dr < (9-n) * 4 + 10) continue;//don't draw wave lines behind the character
            var wave = Math.abs((n+frameCount)%fpb-(fpb/2))*10+50;
            var w = 4*(n+4);
            var nOffset = 4*n+48;
            var offX = projectiles.list[p].heading.vx * nOffset + originx;
            var offY = projectiles.list[p].heading.vy * nOffset + originy;
            var b = Math.pow(-1,n)*20 + 80;//for now
            if (b === 100) b = wave;
            var Gradient3 = ctx.createRadialGradient(offX,offY,0,offX,offY,w);
            Gradient3.addColorStop(0  , 'hsla(' + c + ',' + wave + '%,' + b + '%,' + rangeD + ')');
            Gradient3.addColorStop(1  , 'hsla(' + c + ',' + wave + '%,' + b + '%,0)');
            ctx.beginPath();
            var nAngle = getAngle(projectiles.list[p].heading);
            ctx.arc(originx,originy,nOffset, nAngle - (Math.PI/4), nAngle + (Math.PI/4), false); 
            ctx.strokeStyle = Gradient3;
            ctx.lineWidth = 2;
            ctx.stroke();
        }
        projectiles.list[p].move();//move each projectile
    }
}

This code could be rolled into the projectile object... but I left it out while writing. Oh well. #thingsiwillfixlater #imtoolaz

The delay at the beginning is there to hold the drawing until the animation of the character has time to complete his "attack". Although I don't yet have any animations sooo it just counts down for now.

The 'framecount' and 'fbp' (frames per beat) are global variables that keep track of where in the beat the animation will be drawn. This is used for synchronization with the music. Obviously not very necessary, but I think it will look cooler if the wave pulses with the sounds.

And in case you haven't seen previous posts, the getAngle() function is below. It takes an x,y vector and turns it into radians.  Thus telling us which way to "point" the arcs.
function getAngle(p2,p1){//takes two positions as parameters, or a single vector
    if (!p1) var angle = Math.atan2(p2.vy,p2.vx);
    else var angle = Math.atan2(p2.cy-p1.cy,p2.cx-p1.cx);
    return angle;
}

Full code and a playable demo (without sound for now) coming soon. Next post will be about how the waves work in the game play mechanics and the use of color and "dissonance".

0 comments: