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

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: