Invaders

Space Invaders with the MC Engine. Key lesson from this project is the use of Sprite type “anim” and use of a Sprite Sheet. These are created in a Sprite Factory (called “getSprite()”). Another key thing to look at is the use of a “Screen” variable to track game states.

Code

// MC Library
// Space Invaders Example
// 03 Jan 2019
// Purely an Alpha


window.onload = (function() {

// AQUIRE HTML DOM CANVAS REFERENCE
//     HTML has to have a HTML5 canvas element tagged as "canvas"
var c=document.getElementById("canvas");

//////////////////////////
// INITIALISE MC Engine //
//////////////////////////
// CHOOSE 1 // 

// CHOICE 1: Canvas Size fixed by HTML
MC.init(c);

// CHOICE 2: Full Window Option (Canvas will be resized to fit window)
//MC.init(c,"fullWindow");

///////////////////////////
// END OF Initialisation //
///////////////////////////
//-----------------------------------------------------------------------------
//////////////////////////
// VARIABLE DECLARATION //
//////////////////////////

// screenset up .. 900x700 canvas ... 600x500 central game bounds
MC.canvas.setSize(900,700);
MC.game.setBounds(0.5/7,6.5/7,2/9,7/9);

MC.game.paused = true;

// Container for all required Game State variables
SI = {
    screen: 0, // 0 awaiting start, 1 = running, 2= Awaiting new level restart 3 = game over awaiting restart
    level: 1,
    lives: 4,
    score: 0,
    topScore: 0,
    spritesheet: new MC.Picture("Images/SI3.bmp"),
    playerCooldown: 0.3,
    scoreType: new MC.Typeface("Arial", 30,"White"),
    goingLeft: false,
    leftBounds: 0,
    rightBounds: 0,
    topHeight: MC.canvas.top,
    leftEdge: MC.canvas.left + 30,
    rightEdge: MC.canvas.right - 30,
    invaderCount: 0,
    titleTF: new MC.Typeface("Impact",25,"White"),
    con1: new MC.ConBox({type: "r",text: "Controls",width: 150, height: 45}),
    con2: new MC.ConBox({type: "r",text: "Left: A",width: 150, height: 30}),
    con3: new MC.ConBox({type: "r",text: "Right: D",width: 150, height: 30}),
    con4: new MC.ConBox({type: "r",text: "Fire: W/Space",width: 150, height: 30}),
    GUI: new MC.GUI()
    };

SI.GUI.push(SI.con1,SI.con2,SI.con3,SI.con4);

var invaders = new MC.SpriteBin();
spawnInvaders(SI.level);

var playerShots = new MC.SpriteBin();
var invaderShots = new MC.SpriteBin();

var player = getSprite(5);
player.moveTo(new MC.Point(MC.canvas.midX, MC.canvas.bottom - 100));
player.dev.timer = 0;
player.dev.update = function () {
    var dt = MC.game.deltaTime;
    player.dev.timer = MC.maths.maxOf(0,player.dev.timer - dt);
    if (SI.screen == 1) { // game running
        if (MC.keys.a.down && player.pos.x > MC.canvas.left + 30) { // move left
        player.moveBy(new MC.Point(-70 * dt,0));
        }
        if (MC.keys.d.down && player.pos.x < MC.canvas.right - 30) { // move right
            player.moveBy(new MC.Point(70 * dt,0));
        }
        if ((MC.keys.space.down || MC.keys.w.down) && player.dev.timer <= 0 ) { // fire
            player.dev.fire();
        } 
    }
};
player.dev.fire = function () {
    player.dev.timer = SI.playerCooldown;
    playerShots.push(getSprite(0));  
};



///////////////////
// GAME FUNCTION DECLARATIONS
//////////////////

function manageInvaders() {
    checkInvaders();
    if ( (SI.goingLeft && (SI.leftBounds - SI.leftEdge) < 0) || (!SI.goingLeft && (SI.rightEdge - SI.rightBounds) < 0) ) {
        swapDirection();
    }
}

// Helper function, counts invaders and populates the SI.left/rightBounds of the swarm
function checkInvaders() {
    SI.invaderCount = 0;
    var leftX = SI.rightEdge;
    var rightX = SI.leftEdge;
    var len = invaders.bin.length;    
    for (var i = 0; i < len; i++) {
        var s = invaders.bin[i];
            if (s !== undefined && s !== null && s.dead !== true && s.collider == true) {
                SI.invaderCount ++;
                leftX = MC.maths.minOf(leftX,s.pos.x);
                rightX = MC.maths.maxOf(rightX,s.pos.x);
            }
    }
    if (SI.invaderCount == 0) {
        gameState(true);
        return;
    }
    SI.leftBounds = leftX;
    SI.rightBounds = rightX;
};

function swapDirection() {
     var down = new MC.Point(0,30);
     var len = invaders.bin.length;
     for (var i = 0; i < len; i++) {
        var s = invaders.bin[i];
            if (s !== undefined && s !== null && s.dead !== true && s.collider == true) {
                    s.moveBy(down);
                    s.vel.times(-1);  
            }
    }
    SI.goingLeft = !SI.goingLeft;
}


// Sprite Factory
function getSprite(code) {
    // Player shot is a non-Sprite
    if (code == 0) {
        var ret = new MC.Sprite({ type: "rect",
                                size1: 4,
                                size2: 20,
                                fillColor: "Red",
                                edgeColor: "Yellow",
                                edgeWidth: 1,
                                vel: new MC.Point(0,-175)
                                });
        ret.pos.set(player.pos).add(0,-20);
        ret.dev.update = function () {
            if (ret.pos.y < MC.canvas.top + (ret.size2/2)) {
            ret.kill();
            }
            if (ret.hit(invaders)) {
                ret.kill();
                ret.lastHit.kill();
            }
        };
        return ret;
    }  

    var ret = new MC.Sprite({type: "anim",
                    picture: SI.spritesheet,
                       scale: 1});
    switch (code) {
        case 1: ret.setAnimation(2,5,0,0,1,0,1); // pink invader
                ret.dev.score = 200;
            break;
        case 2: ret.setAnimation(2,5,0,1,1,1,1); // green invader
                ret.dev.score = 100;
            break;
        case 3: ret.setAnimation(2,5,0,2,1,2,1); // blue invader
                ret.dev.score = 50;
            break;
        case 4: ret.setAnimation(2,5,0,3,1,3,12); // explosion
                ret.scale = 1.5;
                ret.collider = false;
            break;
        case 5: ret.setAnimation(2,5,0,4,0,4,0); // player
            ret.scale = 1.4;
            break;
        case 6: ret.setAnimation(4,5,2,4,3,4,6); // invader shot
                ret.vel.set(0,175);
                ret.scale = 1.5;
                ret.dev.update = function () {
                    if (ret.pos.y > player.pos.y) {
                        ret.kill();
                    }
                    else if (ret.hit(player)) {
                        if (SI.lives > 1) {
                            SI.lives -= 1;
                        }
                        else {
                            gameState(false);
                        }
                        ret.setAnimation(2,5,0,3,1,3,12);
                        ret.scale = 1.5;
                        ret.collider = false;
                        ret.kill(1/6);
                        ret.vel.set(0,0);
                    }
                };
            break;
        default:
            break;
    }
    if (code == 1 || code == 2 || code == 3) {
     ret.vel.set(30,0);   
     ret.dev.onKill = function () {
            SI.score += ret.dev.score;        
            var s = getSprite(4);
            s.pos.set(ret.pos);
            s.kill(1/6);
            invaders.push(s);
        }
      ret.dev.update = function () {
        if (SI.screen == 1 && MC.maths.randBetween(1,600) == 1 && !ret.hit(invaderShots)) {
            ret.dev.fire();
        }
      }
      ret.dev.fire = function () {
        var s = getSprite(6);
        s.moveTo(ret.pos);
        invaderShots.push(s);       
      }
    }
    return ret;
}




// Customise the Engine mouse Event Listener
MC.mouse.onClickL = function () {
    if (SI.screen == 0 || SI.screen == 3) {
        gameState();
    }
    else if (SI.screen == 2) {
        SI.screen = 1;
        MC.game.paused = false;
    }
};

// Populates the Invaders SpriteBin
function spawnInvaders(level) {
    var o = new MC.Point(SI.leftEdge, SI.topHeight + (level * 30));
    for (var x = 0; x < 9; x++) {
        for (var y = 0; y < 6; y++) {
            var s;
            if (y <=1 ) {
                s = getSprite(1);
            }
            else if (y <= 3) {
                s = getSprite(2);
            }
            else {
                s = getSprite(3);
            }
            var pos = new MC.Point();
            pos.set(o).add(x*40,y*30);
            s.moveTo(pos);
            invaders.push(s);
        }
    }

}


// gameState changes
function gameState(value) {
  switch (SI.screen) {
    case 0: // Awaiting Start .... press to progress
        MC.game.paused = false;
        SI.screen = 1;
        break;
    case 1: // running have a result ..... so ....
        MC.game.paused = true;
        if (value) { // have won         
            SI.screen = 2;
            if (SI.lives < 4) {
                SI.lives +=1;
            }
        }
        else { // have lost
            SI.screen = 3;
        }
        break;
    case 2: // Won, new level
        invaders.empty();
        playerShots.empty();
        invaderShots.empty();
        SI.level += 1;
        spawnInvaders(SI.level);
        SI.goingLeft = false;
        MC.game.paused = true;
        break;
    case 3: // Lost, reset it all
        invaders.empty();
        playerShots.empty();
        invaderShots.empty();
        SI.level = 1;
        SI.score = 0;
        SI.lives = 4;
        spawnInvaders(SI.level);
        SI.goingLeft = false;        
        SI.screen = 0;
        MC.game.paused = true;
        break;       
    default:
        break;
  }
}


//////////////////////////
// END OF declarations  //
//////////////////////////
//-----------------------------------------------------------------------------
//////////////////////////
//      GAME LOOP       //
//////////////////////////
// first call, request subsequently made within
gameLoop(); 

function gameLoop() {

    requestAnimationFrame(gameLoop);   
    // Essential to update MC.game to ensure MC.game.deltaTime is available
    MC.game.update();
    
    // physicsUpdate Loop
    for (var z = 0; z < MC.game.physicsUpdates; z++)
    {
      invaders.update();
      playerShots.update();
      invaderShots.update();
      player.update();
      manageInvaders();
      if (player.hit(invaders)) {
        gameState(false);
      }
    }
    MC.draw.clearCanvas("Olive");
    MC.draw.clearInBounds("Black");
    playerShots.render(); 
    player.render();
    invaderShots.render();
    invaders.render();     
    renderLives();
    SI.GUI.render(5,50);
    renderText();
}

function renderLives() {
    if (SI.lives > 1) {
        var o = new MC.Point(MC.canvas.left + 40, MC.canvas.bottom - 25);
        var h = SI.spritesheet.height;
        var w = SI.spritesheet.width;
        var p1 = new MC.Point(0,0.8);
        var p2 = new MC.Point(0.5,1);
        for (var i = 0; i < SI.lives -1; i++) {
            SI.spritesheet.render(o,0,w,h/2.5,p1,p2);
            o.add(60,0);            
        }
    }
}

function renderText() {
    //score
    MC.draw.text("Score: "+ SI.score,new MC.Point(MC.canvas.left + 200,MC.canvas.bottom - 25),SI.scoreType);
    // level
    MC.draw.text("Lvl: "+ SI.level,new MC.Point(MC.canvas.right - 85,MC.canvas.bottom - 25),SI.scoreType);
    // title
    MC.draw.text("Space Invaders (nearly)", new MC.Point(325, 35),SI.titleTF);
    switch (SI.screen) {
        case 0:
            MC.draw.text("Click anywhere to Start", new MC.Point(300,MC.canvas.midY),SI.scoreType);
            break;
        case 2:
            MC.draw.text("Well Done, that showed 'em!", new MC.Point(270,MC.canvas.midY),SI.scoreType);
            MC.draw.text("Click to Start New Level", new MC.Point(300,MC.canvas.midY+40),SI.scoreType);
            break;
        case 3:
            MC.draw.text("GAME OVER, unlucky", new MC.Point(300,MC.canvas.midY),SI.scoreType);
            MC.draw.text("Click to Reset", new MC.Point(350,MC.canvas.midY+40),SI.scoreType);
        default:
            break;
    }
}

}); // EoF