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