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).

Unknown

Some say he’s half man half fish, others say he’s more of a seventy/thirty split. Either way he’s a fishy bastard.

0 comments: