Making a laser beam entity in ImpactJS using HTML5 canvas stroke()

Posted on August 9, 2012 | 5 minute read

JavaScript laserOver the past two days I’ve been working on lasers. It seemed like this would be an easy thing to put in before I started. I mean I already have bullets that you can shoot at will, so a laser is just like a big…bullet…uh…right?

No.

First of all I had to decide on the requirements for my laser:

  1. It has to be toggled on and off, with regular bullets being shot when it is off

  2. Unlike regular bullets, holding SPACE shoots the laser continuously (as opposed to 1 bullet per 1 press)

  3. When the laser runs out of power, it is killed

  4. The laser has to follow the player’s position

  5. The laser can be toggled between BLUE and RED while active

  6. The entire laser beam does damage, not just the tip of the laser

  7. It has to look smooth and fluid as it expands to variable heights, so having an animation sheet based on images would be a problem

So here’s what I ended up doing. I won’t go over the laser power meters here (basically there’s a red and a blue power meter. The blue laser can be shot only when the blue power is => 1 and the red laser can be shot only when the red power is => 1).

As usual, everything is over-commented for this post.

Player.js - Toggling the laser weapon, spawning the laser beam, switching laser kinds

Toggling the laser in main.js

So first I created a new key binding for the laser toggle in the main.js init, making it the “A” key.

        ig.input.bind( ig.KEY.A, 'laserToggle' );

Then, I put in the rules for the laser actually spawning in player.js.

Spawning the laser beam

        if (ig.input.pressed('laserToggle')) {
            this.toggleLaser();	
        }

        // If player holds down SPACE and the laser is toggled
        if (ig.input.state('action') && this.laserToggled) {
            // If laser beam does not exist
            if (!this.laserBeam) {
                // Spawn laser beam
                ig.game.spawnEntity( EntityLaserbeam, this.pos.x + this.size.x / 2, this.pos.y, {kind: this.kind} );
                this.laserBeam = ig.game.getEntitiesByType( EntityLaserbeam )[0];	
            }
        }

        else {
            // If player does not hold down SPACE and a laser beam exists
            if (this.laserBeam) {
                // Kill the laser beam
                this.laserBeam.kill();
                this.laserBeam = null;
            }
        }

toggleLaser function:

    toggleLaser: function() {
        if (!this.laserToggled) {
            this.laserToggled = true;
            console.log('toggled');
        }
        else if (this.laserToggled) {
            this.laserToggled = false;
            console.log('untoggled');
        }
    },

 Switching laser beam kinds

SHIFT is already bound as my key to switch weapons, and already calls the “switchWeapons” function for regular bullets. Each time someone presses SHIFT, the player entity’s “kind” attribute is switched between red and blue. So all I did here was add a new if statement to the switchWeapons function to make the laser beam kind correlate with the player kind:

        if (this.laserBeam) {
            this.laserBeam.kind = this.kind;
            this.laserBeam.setProperties();
        }

Laser.js - The actual laser beam

The laser.js file actually holds two entities - one 1px by 1px entity that holds and draws the laser meter (two meters, for two different kinds of beams). I won’t go over that here. And the EntityLaserbeam entity, which is the actual laser beam itself. Here is EntityLaserbeam. I’m using “fire” and “water” as the two identifiers for the two different kinds of beams. In reality this isn’t really that accurate as I’m not using fire and water anymore, but you get the idea.

The laser beam does not have an animation sheet. It is simply a gradient stroke being drawn on the HTML5 canvas.

ig.global.EntityLaserbeam = ig.Entity.extend({
    size: {x: 1, y: 1},
    offset: {x: 0, y: 0},
    maxVel: {x: 500, y: 500},
    kind: null,

    type: ig.Entity.TYPE.NONE,
    // The entities that the laser beam kills are of TYPE A, so it checks against A
    checkAgainst: ig.Entity.TYPE.A,
    collides: ig.Entity.COLLIDES.NEVER,

    init: function( x, y, settings ) {
        this.parent(x, y, settings);
        this.vel.y = -500;
        this.setProperties();
        console.log('alive');
    },

    setProperties: function() {
        if (this.kind === 'fire') {
            this.lingrad = ig.system.context.createLinearGradient(0,0,0,480);
            this.lingrad.addColorStop(0, '#ff0000');
            this.lingrad.addColorStop(0.5, '#c11700');
            this.lingrad.addColorStop(0.75, '#8f1100');
            this.lingrad.addColorStop(1, '#650c01'); 
        }

        else if (this.kind === 'water') {
            this.lingrad = ig.system.context.createLinearGradient(0,0,0,480);
            this.lingrad.addColorStop(0, '#0000ff');
            this.lingrad.addColorStop(0.5, '#006dc1');
            this.lingrad.addColorStop(0.75, '#00508f');
            this.lingrad.addColorStop(1, '#013a65');
        }
    },

    update: function() {
        // Laser beams follows player's pos x
        this.pos.x = ig.game.player.pos.x + ig.game.player.size.x / 2;

        // Size of the beam changes as it is shot out, growing as it moves
        // This means ANY PART of the beam does damage to ANY relevant entity it hits
        // As opposed to just the tip of the beam. 
        this.size.y = ig.game.player.pos.y - this.pos.y;

        // For every frame the laser beam is alive, 
        // it drains power.
        if (this.kind === 'fire') {
            ig.game.laser.incrementLaser(-0.1,0);
            if (ig.game.laser.fireLaser < 1) {
                this.kill();
                console.log('killing beam');
            }
        }

        else if (this.kind === 'water') {
            ig.game.laser.incrementLaser(0,-0.1);
            if (ig.game.laser.waterLaser < 1) {
                this.kill();
                this.laserBeam = null;
            }
        }	

        this.parent();

        // If the laser beam is not touching an enemy entity (a "block"), its vel.y is -500
        for (var i = 0; i < ig.game.allBlocks.length; i++) {
            var block = ig.game.allBlocks[i];
            if( !this.touches(block)) {
                this.vel.y = -500;
            }	
        }	
    },

    check: function( other ) {
        // If the beam touches a block or hit area of a boss
        if (other instanceof EntityBlock || other instanceof EntityHitarea ) {
            // And its kind does NOT match the vulnerability of the other entity
            if (this.kind != other.vulnerability && other.vulnerability != 'all') {
                // It stops against that entity
                this.vel.y = 0;
                this.pos.y = other.pos.y + other.size.y;
            }

            // Otherwise, if it's touching a block or hit area of a boss 
            // and its kind DOES match the vulnerability of the other entity
            else {
                // Its vel.y is -500 and the other entity takes damage
                this.vel.y = -500;
                other.receiveDamage(1, this);			
            } 
        }
    }, 

     // The actual beam is drawn as a stroke on the canvas
     draw: function() {
        ig.system.context.strokeStyle = this.lingrad;

        ig.system.context.beginPath();
        ig.system.context.moveTo(ig.game.player.pos.x + ig.game.player.size.x / 2,ig.game.player.pos.y);
        ig.system.context.lineTo(this.pos.x,this.pos.y);
        ig.system.context.stroke();
        ig.system.context.closePath();       
    } 

});

I’m sure I can make lots of improvements to this and eventually I will.

Next challenge

Right now the laser beam follows the player on the x axis in a straight, rigid line. That works, but next I want to make it look a lot more fluid, bending left and right as the player moves and it follows.




Categories:game dev dev games
comments powered by Disqus