A small project I did in spare time :) It should ressemble the old space invaders game, i know it's not perfect but I like it anyway.
A Pen by Patrick Stillhart on CodePen.
| <p class="center">Space Invadors destroyed this page! Take revenge on them! | |
| <br/> Use <span class="label label-danger">Space</span> to shoot and <span class="label label-danger">←</span> <span class="label label-danger">→</span> to move!   <button class="btn btn-default btn-xs" id="restart">Restart</button></p> | |
| <canvas id="space-invaders"/> |
| ;(function() { | |
| "use strict"; | |
| // General | |
| var canvas, | |
| screen, | |
| gameSize, | |
| game; | |
| // Assets | |
| var invaderCanvas, | |
| invaderMultiplier, | |
| invaderSize = 20, | |
| initialOffsetInvader, | |
| invaderAttackRate, | |
| invaderSpeed, | |
| invaderSpawnDelay = 250; | |
| // Counter | |
| var i = 0, | |
| kills = 0, | |
| spawnDelayCounter = invaderSpawnDelay; | |
| var invaderDownTimer; | |
| // Text | |
| var blocks = [ | |
| [3, 4, 8, 9, 10, 15, 16], | |
| [2, 4, 7, 11, 14, 16], | |
| [1, 4, 7, 11, 13, 16], | |
| [1, 2, 3, 4, 5, 7, 11, 13, 14, 15, 16, 17], | |
| [4, 7, 11, 16], | |
| [4, 8, 9, 10, 16] | |
| ]; | |
| // Game Controller | |
| // --------------- | |
| var Game = function() { | |
| this.level = -1; | |
| this.lost = false; | |
| this.player = new Player(); | |
| this.invaders = []; | |
| this.invaderShots = []; | |
| if (invaderDownTimer === undefined) { | |
| invaderDownTimer = setInterval(function() { | |
| for (i = 0; i < game.invaders.length; i++) game.invaders[i].move(); | |
| }, 1000 - (this.level * 1.8)); | |
| }; | |
| } | |
| Game.prototype = { | |
| update: function() { | |
| // Next level | |
| if (game.invaders.length === 0) { | |
| spawnDelayCounter += 1; | |
| if (spawnDelayCounter < invaderSpawnDelay) return; | |
| this.level += 1; | |
| invaderAttackRate -= 0.002; | |
| invaderSpeed += 10; | |
| game.invaders = createInvaders(); | |
| spawnDelayCounter = 0; | |
| } | |
| if (!this.lost) { | |
| // Collision | |
| game.player.projectile.forEach(function(projectile) { | |
| game.invaders.forEach(function(invader) { | |
| if (collides(projectile, invader)) { | |
| invader.destroy(); | |
| projectile.active = false; | |
| } | |
| }); | |
| }); | |
| this.invaderShots.forEach(function(invaderShots) { | |
| if (collides(invaderShots, game.player)) { | |
| game.player.destroy(); | |
| } | |
| }); | |
| for (i = 0; i < game.invaders.length; i++) game.invaders[i].update(); | |
| } | |
| // Don't stop player & projectiles.. they look nice | |
| game.player.update(); | |
| for (i = 0; i < game.invaderShots.length; i++) game.invaderShots[i].update(); | |
| this.invaders = game.invaders.filter(function(invader) { | |
| return invader.active; | |
| }); | |
| }, | |
| draw: function() { | |
| if (this.lost) { | |
| screen.fillStyle = "rgba(0, 0, 0, 0.03)"; | |
| screen.fillRect(0, 0, gameSize.width, gameSize.height); | |
| screen.font = "55px Lucida Console"; | |
| screen.textAlign = "center"; | |
| screen.fillStyle = "white"; | |
| screen.fillText("You lost", gameSize.width / 2, gameSize.height / 2); | |
| screen.font = "20px Lucida Console"; | |
| screen.fillText("Points: " + kills, gameSize.width / 2, gameSize.height / 2 + 30); | |
| } else { | |
| screen.clearRect(0, 0, gameSize.width, gameSize.height); | |
| screen.font = "10px Lucida Console"; | |
| screen.textAlign = "right"; | |
| screen.fillText("Points: " + kills, gameSize.width, gameSize.height - 12); | |
| } | |
| screen.beginPath(); | |
| var i; | |
| this.player.draw(); | |
| if (!this.lost) | |
| for (i = 0; i < this.invaders.length; i++) this.invaders[i].draw(); | |
| for (i = 0; i < this.invaderShots.length; i++) this.invaderShots[i].draw(); | |
| screen.fill(); | |
| }, | |
| invadersBelow: function(invader) { | |
| return this.invaders.filter(function(b) { | |
| return Math.abs(invader.coordinates.x - b.coordinates.x) === 0 && | |
| b.coordinates.y > invader.coordinates.y; | |
| }).length > 0; | |
| } | |
| }; | |
| // Invaders | |
| // -------- | |
| var Invader = function(coordinates) { | |
| this.active = true; | |
| this.coordinates = coordinates; | |
| this.size = { | |
| width: invaderSize, | |
| height: invaderSize | |
| }; | |
| this.patrolX = 0; | |
| this.speedX = invaderSpeed; | |
| }; | |
| Invader.prototype = { | |
| update: function() { | |
| if (Math.random() > invaderAttackRate && !game.invadersBelow(this)) { | |
| var projectile = new Projectile({ | |
| x: this.coordinates.x + this.size.width / 2, | |
| y: this.coordinates.y + this.size.height - 5 | |
| }, { | |
| x: 0, | |
| y: 2 | |
| }); | |
| game.invaderShots.push(projectile); | |
| } | |
| }, | |
| draw: function() { | |
| if (this.active) screen.drawImage(invaderCanvas, this.coordinates.x, this.coordinates.y); | |
| }, | |
| move: function() { | |
| if (this.patrolX < 0 || this.patrolX > 100) { | |
| this.speedX = -this.speedX; | |
| this.patrolX += this.speedX; | |
| this.coordinates.y += this.size.height; | |
| if (this.coordinates.y + this.size.height * 2 > gameSize.height) game.lost = true; | |
| } else { | |
| this.coordinates.x += this.speedX; | |
| this.patrolX += this.speedX; | |
| } | |
| }, | |
| destroy: function() { | |
| this.active = false; | |
| kills += 1; | |
| } | |
| }; | |
| // Player | |
| // ------ | |
| var Player = function() { | |
| this.active = true; | |
| this.size = { | |
| width: 16, | |
| height: 8 | |
| }; | |
| this.shooterHeat = -3; | |
| this.coordinates = { | |
| x: gameSize.width / 2 - (this.size.width / 2) | 0, | |
| y: gameSize.height - this.size.height * 2 | |
| }; | |
| this.projectile = []; | |
| this.keyboarder = new KeyController(); | |
| }; | |
| Player.prototype = { | |
| update: function() { | |
| for (var i = 0; i < this.projectile.length; i++) this.projectile[i].update(); | |
| this.projectile = this.projectile.filter(function(projectile) { | |
| return projectile.active; | |
| }); | |
| if (!this.active) return; | |
| if (this.keyboarder.isDown(this.keyboarder.KEYS.LEFT) && this.coordinates.x > 0) this.coordinates.x -= 2; | |
| else if (this.keyboarder.isDown(this.keyboarder.KEYS.RIGHT) && this.coordinates.x < gameSize.width - this.size.width) this.coordinates.x += 2; | |
| if (this.keyboarder.isDown(this.keyboarder.KEYS.Space)) { | |
| this.shooterHeat += 1; | |
| if (this.shooterHeat < 0) { | |
| var projectile = new Projectile({ | |
| x: this.coordinates.x + this.size.width / 2 - 1, | |
| y: this.coordinates.y - 1 | |
| }, { | |
| x: 0, | |
| y: -7 | |
| }); | |
| this.projectile.push(projectile); | |
| } else if (this.shooterHeat > 12) this.shooterHeat = -3; | |
| } else { | |
| this.shooterHeat = -3; | |
| } | |
| }, | |
| draw: function() { | |
| if (this.active) { | |
| screen.rect(this.coordinates.x, this.coordinates.y, this.size.width, this.size.height); | |
| screen.rect(this.coordinates.x - 2, this.coordinates.y + 2, 20, 6); | |
| screen.rect(this.coordinates.x + 6, this.coordinates.y - 4, 4, 4); | |
| } | |
| for (var i = 0; i < this.projectile.length; i++) this.projectile[i].draw(); | |
| }, | |
| destroy: function() { | |
| this.active = false; | |
| game.lost = true; | |
| } | |
| }; | |
| // Projectile | |
| // ------ | |
| var Projectile = function(coordinates, velocity) { | |
| this.active = true; | |
| this.coordinates = coordinates; | |
| this.size = { | |
| width: 3, | |
| height: 3 | |
| }; | |
| this.velocity = velocity; | |
| }; | |
| Projectile.prototype = { | |
| update: function() { | |
| this.coordinates.x += this.velocity.x; | |
| this.coordinates.y += this.velocity.y; | |
| if (this.coordinates.y > gameSize.height || this.coordinates.y < 0) this.active = false; | |
| }, | |
| draw: function() { | |
| if (this.active) screen.rect(this.coordinates.x, this.coordinates.y, this.size.width, this.size.height); | |
| } | |
| }; | |
| // Keyboard input tracking | |
| // ----------------------- | |
| var KeyController = function() { | |
| this.KEYS = { | |
| LEFT: 37, | |
| RIGHT: 39, | |
| Space: 32 | |
| }; | |
| var keyCode = [37, 39, 32]; | |
| var keyState = {}; | |
| var counter; | |
| window.addEventListener('keydown', function(e) { | |
| for (counter = 0; counter < keyCode.length; counter++) | |
| if (keyCode[counter] == e.keyCode) { | |
| keyState[e.keyCode] = true; | |
| e.preventDefault(); | |
| } | |
| }); | |
| window.addEventListener('keyup', function(e) { | |
| for (counter = 0; counter < keyCode.length; counter++) | |
| if (keyCode[counter] == e.keyCode) { | |
| keyState[e.keyCode] = false; | |
| e.preventDefault(); | |
| } | |
| }); | |
| this.isDown = function(keyCode) { | |
| return keyState[keyCode] === true; | |
| }; | |
| }; | |
| // Other functions | |
| // --------------- | |
| function collides(a, b) { | |
| return a.coordinates.x < b.coordinates.x + b.size.width && | |
| a.coordinates.x + a.size.width > b.coordinates.x && | |
| a.coordinates.y < b.coordinates.y + b.size.height && | |
| a.coordinates.y + a.size.height > b.coordinates.y; | |
| } | |
| function getPixelRow(rowRaw) { | |
| var textRow = [], | |
| placer = 0, | |
| row = Math.floor(rowRaw / invaderMultiplier); | |
| if (row >= blocks.length) return []; | |
| for (var i = 0; i < blocks[row].length; i++) { | |
| var tmpContent = blocks[row][i] * invaderMultiplier; | |
| for (var j = 0; j < invaderMultiplier; j++) textRow[placer + j] = tmpContent + j; | |
| placer += invaderMultiplier; | |
| } | |
| return textRow; | |
| } | |
| // Write Text | |
| // ----------- | |
| function createInvaders() { | |
| var invaders = []; | |
| var i = blocks.length * invaderMultiplier; | |
| while (i--) { | |
| var j = getPixelRow(i); | |
| for (var k = 0; k < j.length; k++) { | |
| invaders.push(new Invader({ | |
| x: j[k] * invaderSize, | |
| y: i * invaderSize | |
| })); | |
| } | |
| } | |
| return invaders; | |
| } | |
| // Start game | |
| // ---------- | |
| window.addEventListener('load', function() { | |
| var invaderAsset = new Image; | |
| invaderAsset.onload = function() { | |
| invaderCanvas = document.createElement('canvas'); | |
| invaderCanvas.width = invaderSize; | |
| invaderCanvas.height = invaderSize; | |
| invaderCanvas.getContext("2d").drawImage(invaderAsset, 0, 0); | |
| // Game Creation | |
| canvas = document.getElementById("space-invaders"); | |
| screen = canvas.getContext('2d'); | |
| initGameStart(); | |
| loop(); | |
| }; | |
| invaderAsset.src = "//stillh.art/project/spaceInvaders/invader.gif"; | |
| }); | |
| window.addEventListener('resize', function() { | |
| initGameStart(); | |
| }); | |
| document.getElementById('restart').addEventListener('click', function() { | |
| initGameStart(); | |
| }); | |
| function initGameStart() { | |
| if (window.innerWidth > 1200) { | |
| screen.canvas.width = 1200; | |
| screen.canvas.height = 500; | |
| gameSize = { | |
| width: 1200, | |
| height: 500 | |
| }; | |
| invaderMultiplier = 3; | |
| initialOffsetInvader = 420; | |
| } else if (window.innerWidth > 800) { | |
| screen.canvas.width = 900; | |
| screen.canvas.height = 600; | |
| gameSize = { | |
| width: 900, | |
| height: 600 | |
| }; | |
| invaderMultiplier = 2; | |
| initialOffsetInvader = 280; | |
| } else { | |
| screen.canvas.width = 600; | |
| screen.canvas.height = 300; | |
| gameSize = { | |
| width: 600, | |
| height: 300 | |
| }; | |
| invaderMultiplier = 1; | |
| initialOffsetInvader = 140; | |
| } | |
| kills = 0; | |
| invaderAttackRate = 0.999; | |
| invaderSpeed = 20; | |
| spawnDelayCounter = invaderSpawnDelay; | |
| game = new Game(); | |
| } | |
| function loop() { | |
| game.update(); | |
| game.draw(); | |
| requestAnimationFrame(loop); | |
| } | |
| })(); |
A small project I did in spare time :) It should ressemble the old space invaders game, i know it's not perfect but I like it anyway.
A Pen by Patrick Stillhart on CodePen.
| .center{text-align:center} | |
| #space-invaders { | |
| margin: 0 auto; | |
| display: block; | |
| background: white | |
| } |
| <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" /> |