Again putting my boring university breaks to good use I made a small space invaders clone.
A Pen by Anthony Del Ciotto on CodePen.
<link href='https://fonts.googleapis.com/css?family=Play:400,700' rel='stylesheet' type='text/css'> | |
<canvas id="game-canvas" width="640" height="640"></canvas> |
/* Simple JavaScript Inheritance | |
* By John Resig http://ejohn.org/ | |
* MIT Licensed. | |
*/ | |
// Inspired by base2 and Prototype | |
(function(){ | |
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; | |
// The base Class implementation (does nothing) | |
this.Class = function(){}; | |
// Create a new Class that inherits from this class | |
Class.extend = function(prop) { | |
var _super = this.prototype; | |
// Instantiate a base class (but only create the instance, | |
// don't run the init constructor) | |
initializing = true; | |
var prototype = new this(); | |
initializing = false; | |
// Copy the properties over onto the new prototype | |
for (var name in prop) { | |
// Check if we're overwriting an existing function | |
prototype[name] = typeof prop[name] == "function" && | |
typeof _super[name] == "function" && fnTest.test(prop[name]) ? | |
(function(name, fn){ | |
return function() { | |
var tmp = this._super; | |
// Add a new ._super() method that is the same method | |
// but on the super-class | |
this._super = _super[name]; | |
// The method only need to be bound temporarily, so we | |
// remove it when we're done executing | |
var ret = fn.apply(this, arguments); | |
this._super = tmp; | |
return ret; | |
}; | |
})(name, prop[name]) : | |
prop[name]; | |
} | |
// The dummy class constructor | |
function Class() { | |
// All construction is actually done in the init method | |
if ( !initializing && this.init ) | |
this.init.apply(this, arguments); | |
} | |
// Populate our constructed prototype object | |
Class.prototype = prototype; | |
// Enforce the constructor to be what we expect | |
Class.prototype.constructor = Class; | |
// And make this class extendable | |
Class.extend = arguments.callee; | |
return Class; | |
}; | |
})(); | |
// ################################################################### | |
// shims | |
// | |
// ################################################################### | |
(function() { | |
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; | |
window.requestAnimationFrame = requestAnimationFrame; | |
})(); | |
(function() { | |
if (!window.performance.now) { | |
window.performance.now = (!Date.now) ? function() { return new Date().getTime(); } : | |
function() { return Date.now(); } | |
} | |
})(); | |
// ################################################################### | |
// Constants | |
// | |
// ################################################################### | |
var IS_CHROME = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); | |
var CANVAS_WIDTH = 640; | |
var CANVAS_HEIGHT = 640; | |
var SPRITE_SHEET_SRC = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAEACAYAAAADRnAGAAACGUlEQVR42u3aSQ7CMBAEQIsn8P+/hiviAAK8zFIt5QbELiTHmfEYE3L9mZE9AAAAqAVwBQ8AAAD6THY5CgAAAKbfbPX3AQAAYBEEAADAuZrC6UUyfMEEAIBiAN8OePXnAQAAsLcmmKFPAQAAgHMbm+gbr3Sdo/LtcAAAANR6GywPAgBAM4D2JXAAABoBzBjA7AmlOx8AAEAzAOcDAADovTc4vQim6wUCABAYQG8QAADd4dPd2fRVYQAAANQG0B4HAABAawDnAwAA6AXgfAAAALpA2uMAAABwPgAAgPoAM9Ci/R4AAAD2dmqcEQIAIC/AiQGuAAYAAECcRS/a/cJXkUf2AAAAoBaA3iAAALrD+gIAAADY9baX/nwAAADNADwFAADo9YK0e5FMX/UFACA5QPSNEAAAAHKtCekmDAAAAADvBljtfgAAAGgMMGOrunvCy2uCAAAACFU6BwAAwF6AGQPa/XsAAADYB+B8AAAAtU+ItD4OAwAAAFVhAACaA0T7B44/BQAAANALwGMQAAAAADYO8If2+P31AgAAQN0SWbhFDwCAZlXgaO1xAAAA1FngnA8AACAeQPSNEAAAAM4CnC64AAAA4GzN4N9NSfgKEAAAAACszO26X8/X6BYAAAD0Anid8KcLAAAAAAAAAJBnwNEvAAAA9Jns1ygAAAAAAAAAAAAAAAAAAABAQ4COCENERERERERERBrnAa1sJuUVr3rsAAAAAElFTkSuQmCC'; | |
var LEFT_KEY = 37; | |
var RIGHT_KEY = 39; | |
var SHOOT_KEY = 88; | |
var TEXT_BLINK_FREQ = 500; | |
var PLAYER_CLIP_RECT = { x: 0, y: 204, w: 62, h: 32 }; | |
var ALIEN_BOTTOM_ROW = [ { x: 0, y: 0, w: 51, h: 34 }, { x: 0, y: 102, w: 51, h: 34 }]; | |
var ALIEN_MIDDLE_ROW = [ { x: 0, y: 137, w: 50, h: 33 }, { x: 0, y: 170, w: 50, h: 34 }]; | |
var ALIEN_TOP_ROW = [ { x: 0, y: 68, w: 50, h: 32 }, { x: 0, y: 34, w: 50, h: 32 }]; | |
var ALIEN_X_MARGIN = 40; | |
var ALIEN_SQUAD_WIDTH = 11 * ALIEN_X_MARGIN; | |
// ################################################################### | |
// Utility functions & classes | |
// | |
// ################################################################### | |
function getRandomArbitrary(min, max) { | |
return Math.random() * (max - min) + min; | |
} | |
function getRandomInt(min, max) { | |
return Math.floor(Math.random() * (max - min + 1)) + min; | |
} | |
function clamp(num, min, max) { | |
return Math.min(Math.max(num, min), max); | |
} | |
function valueInRange(value, min, max) { | |
return (value <= max) && (value >= min); | |
} | |
function checkRectCollision(A, B) { | |
var xOverlap = valueInRange(A.x, B.x, B.x + B.w) || | |
valueInRange(B.x, A.x, A.x + A.w); | |
var yOverlap = valueInRange(A.y, B.y, B.y + B.h) || | |
valueInRange(B.y, A.y, A.y + A.h); | |
return xOverlap && yOverlap; | |
} | |
var Point2D = Class.extend({ | |
init: function(x, y) { | |
this.x = (typeof x === 'undefined') ? 0 : x; | |
this.y = (typeof y === 'undefined') ? 0 : y; | |
}, | |
set: function(x, y) { | |
this.x = x; | |
this.y = y; | |
} | |
}); | |
var Rect = Class.extend({ | |
init: function(x, y, w, h) { | |
this.x = (typeof x === 'undefined') ? 0 : x; | |
this.y = (typeof y === 'undefined') ? 0 : y; | |
this.w = (typeof w === 'undefined') ? 0 : w; | |
this.h = (typeof h === 'undefined') ? 0 : h; | |
}, | |
set: function(x, y, w, h) { | |
this.x = x; | |
this.y = y; | |
this.w = w; | |
this.h = h; | |
} | |
}); | |
// ################################################################### | |
// Globals | |
// | |
// ################################################################### | |
var canvas = null; | |
var ctx = null; | |
var spriteSheetImg = null; | |
var bulletImg = null; | |
var keyStates = null; | |
var prevKeyStates = null; | |
var lastTime = 0; | |
var player = null; | |
var aliens = []; | |
var particleManager = null; | |
var updateAlienLogic = false; | |
var alienDirection = -1; | |
var alienYDown = 0; | |
var alienCount = 0; | |
var wave = 1; | |
var hasGameStarted = false; | |
// ################################################################### | |
// Entities | |
// | |
// ################################################################### | |
var BaseSprite = Class.extend({ | |
init: function(img, x, y) { | |
this.img = img; | |
this.position = new Point2D(x, y); | |
this.scale = new Point2D(1, 1); | |
this.bounds = new Rect(x, y, this.img.width, this.img.height); | |
this.doLogic = true; | |
}, | |
update: function(dt) { }, | |
_updateBounds: function() { | |
this.bounds.set(this.position.x, this.position.y, ~~(0.5 + this.img.width * this.scale.x), ~~(0.5 + this.img.height * this.scale.y)); | |
}, | |
_drawImage: function() { | |
ctx.drawImage(this.img, this.position.x, this.position.y); | |
}, | |
draw: function(resized) { | |
this._updateBounds(); | |
this._drawImage(); | |
} | |
}); | |
var SheetSprite = BaseSprite.extend({ | |
init: function(sheetImg, clipRect, x, y) { | |
this._super(sheetImg, x, y); | |
this.clipRect = clipRect; | |
this.bounds.set(x, y, this.clipRect.w, this.clipRect.h); | |
}, | |
update: function(dt) {}, | |
_updateBounds: function() { | |
var w = ~~(0.5 + this.clipRect.w * this.scale.x); | |
var h = ~~(0.5 + this.clipRect.h * this.scale.y); | |
this.bounds.set(this.position.x - w/2, this.position.y - h/2, w, h); | |
}, | |
_drawImage: function() { | |
ctx.save(); | |
ctx.transform(this.scale.x, 0, 0, this.scale.y, this.position.x, this.position.y); | |
ctx.drawImage(this.img, this.clipRect.x, this.clipRect.y, this.clipRect.w, this.clipRect.h, ~~(0.5 + -this.clipRect.w*0.5), ~~(0.5 + -this.clipRect.h*0.5), this.clipRect.w, this.clipRect.h); | |
ctx.restore(); | |
}, | |
draw: function(resized) { | |
this._super(resized); | |
} | |
}); | |
var Player = SheetSprite.extend({ | |
init: function() { | |
this._super(spriteSheetImg, PLAYER_CLIP_RECT, CANVAS_WIDTH/2, CANVAS_HEIGHT - 70); | |
this.scale.set(0.85, 0.85); | |
this.lives = 3; | |
this.xVel = 0; | |
this.bullets = []; | |
this.bulletDelayAccumulator = 0; | |
this.score = 0; | |
}, | |
reset: function() { | |
this.lives = 3; | |
this.score = 0; | |
this.position.set(CANVAS_WIDTH/2, CANVAS_HEIGHT - 70); | |
}, | |
shoot: function() { | |
var bullet = new Bullet(this.position.x, this.position.y - this.bounds.h / 2, 1, 1000); | |
this.bullets.push(bullet); | |
}, | |
handleInput: function() { | |
if (isKeyDown(LEFT_KEY)) { | |
this.xVel = -175; | |
} else if (isKeyDown(RIGHT_KEY)) { | |
this.xVel = 175; | |
} else this.xVel = 0; | |
if (wasKeyPressed(SHOOT_KEY)) { | |
if (this.bulletDelayAccumulator > 0.5) { | |
this.shoot(); | |
this.bulletDelayAccumulator = 0; | |
} | |
} | |
}, | |
updateBullets: function(dt) { | |
for (var i = this.bullets.length - 1; i >= 0; i--) { | |
var bullet = this.bullets[i]; | |
if (bullet.alive) { | |
bullet.update(dt); | |
} else { | |
this.bullets.splice(i, 1); | |
bullet = null; | |
} | |
} | |
}, | |
update: function(dt) { | |
// update time passed between shots | |
this.bulletDelayAccumulator += dt; | |
// apply x vel | |
this.position.x += this.xVel * dt; | |
// cap player position in screen bounds | |
this.position.x = clamp(this.position.x, this.bounds.w/2, CANVAS_WIDTH - this.bounds.w/2); | |
this.updateBullets(dt); | |
}, | |
draw: function(resized) { | |
this._super(resized); | |
// draw bullets | |
for (var i = 0, len = this.bullets.length; i < len; i++) { | |
var bullet = this.bullets[i]; | |
if (bullet.alive) { | |
bullet.draw(resized); | |
} | |
} | |
} | |
}); | |
var Bullet = BaseSprite.extend({ | |
init: function(x, y, direction, speed) { | |
this._super(bulletImg, x, y); | |
this.direction = direction; | |
this.speed = speed; | |
this.alive = true; | |
}, | |
update: function(dt) { | |
this.position.y -= (this.speed * this.direction) * dt; | |
if (this.position.y < 0) { | |
this.alive = false; | |
} | |
}, | |
draw: function(resized) { | |
this._super(resized); | |
} | |
}); | |
var Enemy = SheetSprite.extend({ | |
init: function(clipRects, x, y) { | |
this._super(spriteSheetImg, clipRects[0], x, y); | |
this.clipRects = clipRects; | |
this.scale.set(0.5, 0.5); | |
this.alive = true; | |
this.onFirstState = true; | |
this.stepDelay = 1; // try 2 secs to start with... | |
this.stepAccumulator = 0; | |
this.doShoot - false; | |
this.bullet = null; | |
}, | |
toggleFrame: function() { | |
this.onFirstState = !this.onFirstState; | |
this.clipRect = (this.onFirstState) ? this.clipRects[0] : this.clipRects[1]; | |
}, | |
shoot: function() { | |
this.bullet = new Bullet(this.position.x, this.position.y + this.bounds.w/2, -1, 500); | |
}, | |
update: function(dt) { | |
this.stepAccumulator += dt; | |
if (this.stepAccumulator >= this.stepDelay) { | |
if (this.position.x < this.bounds.w/2 + 20 && alienDirection < 0) { | |
updateAlienLogic = true; | |
} if (alienDirection === 1 && this.position.x > CANVAS_WIDTH - this.bounds.w/2 - 20) { | |
updateAlienLogic = true; | |
} | |
if (this.position.y > CANVAS_WIDTH - 50) { | |
reset(); | |
} | |
var fireTest = Math.floor(Math.random() * (this.stepDelay + 1)); | |
if (getRandomArbitrary(0, 1000) <= 5 * (this.stepDelay + 1)) { | |
this.doShoot = true; | |
} | |
this.position.x += 10 * alienDirection; | |
this.toggleFrame(); | |
this.stepAccumulator = 0; | |
} | |
this.position.y += alienYDown; | |
if (this.bullet !== null && this.bullet.alive) { | |
this.bullet.update(dt); | |
} else { | |
this.bullet = null; | |
} | |
}, | |
draw: function(resized) { | |
this._super(resized); | |
if (this.bullet !== null && this.bullet.alive) { | |
this.bullet.draw(resized); | |
} | |
} | |
}); | |
var ParticleExplosion = Class.extend({ | |
init: function() { | |
this.particlePool = []; | |
this.particles = []; | |
}, | |
draw: function() { | |
for (var i = this.particles.length - 1; i >= 0; i--) { | |
var particle = this.particles[i]; | |
particle.moves++; | |
particle.x += particle.xunits; | |
particle.y += particle.yunits + (particle.gravity * particle.moves); | |
particle.life--; | |
if (particle.life <= 0 ) { | |
if (this.particlePool.length < 100) { | |
this.particlePool.push(this.particles.splice(i,1)); | |
} else { | |
this.particles.splice(i,1); | |
} | |
} else { | |
ctx.globalAlpha = (particle.life)/(particle.maxLife); | |
ctx.fillStyle = particle.color; | |
ctx.fillRect(particle.x, particle.y, particle.width, particle.height); | |
ctx.globalAlpha = 1; | |
} | |
} | |
}, | |
createExplosion: function(x, y, color, number, width, height, spd, grav, lif) { | |
for (var i =0;i < number;i++) { | |
var angle = Math.floor(Math.random()*360); | |
var speed = Math.floor(Math.random()*spd/2) + spd; | |
var life = Math.floor(Math.random()*lif)+lif/2; | |
var radians = angle * Math.PI/ 180; | |
var xunits = Math.cos(radians) * speed; | |
var yunits = Math.sin(radians) * speed; | |
if (this.particlePool.length > 0) { | |
var tempParticle = this.particlePool.pop(); | |
tempParticle.x = x; | |
tempParticle.y = y; | |
tempParticle.xunits = xunits; | |
tempParticle.yunits = yunits; | |
tempParticle.life = life; | |
tempParticle.color = color; | |
tempParticle.width = width; | |
tempParticle.height = height; | |
tempParticle.gravity = grav; | |
tempParticle.moves = 0; | |
tempParticle.alpha = 1; | |
tempParticle.maxLife = life; | |
this.particles.push(tempParticle); | |
} else { | |
this.particles.push({x:x,y:y,xunits:xunits,yunits:yunits,life:life,color:color,width:width,height:height,gravity:grav,moves:0,alpha:1, maxLife:life}); | |
} | |
} | |
} | |
}); | |
// ################################################################### | |
// Initialization functions | |
// | |
// ################################################################### | |
function initCanvas() { | |
// create our canvas and context | |
canvas = document.getElementById('game-canvas'); | |
ctx = canvas.getContext('2d'); | |
// turn off image smoothing | |
setImageSmoothing(false); | |
// create our main sprite sheet img | |
spriteSheetImg = new Image(); | |
spriteSheetImg.src = SPRITE_SHEET_SRC; | |
preDrawImages(); | |
// add event listeners and initially resize | |
window.addEventListener('resize', resize); | |
document.addEventListener('keydown', onKeyDown); | |
document.addEventListener('keyup', onKeyUp); | |
} | |
function preDrawImages() { | |
var canvas = drawIntoCanvas(2, 8, function(ctx) { | |
ctx.fillStyle = 'white'; | |
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
}); | |
bulletImg = new Image(); | |
bulletImg.src = canvas.toDataURL(); | |
} | |
function setImageSmoothing(value) { | |
this.ctx['imageSmoothingEnabled'] = value; | |
this.ctx['mozImageSmoothingEnabled'] = value; | |
this.ctx['oImageSmoothingEnabled'] = value; | |
this.ctx['webkitImageSmoothingEnabled'] = value; | |
this.ctx['msImageSmoothingEnabled'] = value; | |
} | |
function initGame() { | |
dirtyRects = []; | |
aliens = []; | |
player = new Player(); | |
particleManager = new ParticleExplosion(); | |
setupAlienFormation(); | |
drawBottomHud(); | |
} | |
function setupAlienFormation() { | |
alienCount = 0; | |
for (var i = 0, len = 5 * 11; i < len; i++) { | |
var gridX = (i % 11); | |
var gridY = Math.floor(i / 11); | |
var clipRects; | |
switch (gridY) { | |
case 0: | |
case 1: clipRects = ALIEN_BOTTOM_ROW; break; | |
case 2: | |
case 3: clipRects = ALIEN_MIDDLE_ROW; break; | |
case 4: clipRects = ALIEN_TOP_ROW; break; | |
} | |
aliens.push(new Enemy(clipRects, (CANVAS_WIDTH/2 - ALIEN_SQUAD_WIDTH/2) + ALIEN_X_MARGIN/2 + gridX * ALIEN_X_MARGIN, CANVAS_HEIGHT/3.25 - gridY * 40)); | |
alienCount++; | |
} | |
} | |
function reset() { | |
aliens = []; | |
setupAlienFormation(); | |
player.reset(); | |
} | |
function init() { | |
initCanvas(); | |
keyStates = []; | |
prevKeyStates = []; | |
resize(); | |
} | |
// ################################################################### | |
// Helpful input functions | |
// | |
// ################################################################### | |
function isKeyDown(key) { | |
return keyStates[key]; | |
} | |
function wasKeyPressed(key) { | |
return !prevKeyStates[key] && keyStates[key]; | |
} | |
// ################################################################### | |
// Drawing & Update functions | |
// | |
// ################################################################### | |
function updateAliens(dt) { | |
if (updateAlienLogic) { | |
updateAlienLogic = false; | |
alienDirection = -alienDirection; | |
alienYDown = 25; | |
} | |
for (var i = aliens.length - 1; i >= 0; i--) { | |
var alien = aliens[i]; | |
if (!alien.alive) { | |
aliens.splice(i, 1); | |
alien = null; | |
alienCount--; | |
if (alienCount < 1) { | |
wave++; | |
setupAlienFormation(); | |
} | |
return; | |
} | |
alien.stepDelay = ((alienCount * 20) - (wave * 10)) / 1000; | |
if (alien.stepDelay <= 0.05) { | |
alien.stepDelay = 0.05; | |
} | |
alien.update(dt); | |
if (alien.doShoot) { | |
alien.doShoot = false; | |
alien.shoot(); | |
} | |
} | |
alienYDown = 0; | |
} | |
function resolveBulletEnemyCollisions() { | |
var bullets = player.bullets; | |
for (var i = 0, len = bullets.length; i < len; i++) { | |
var bullet = bullets[i]; | |
for (var j = 0, alen = aliens.length; j < alen; j++) { | |
var alien = aliens[j]; | |
if (checkRectCollision(bullet.bounds, alien.bounds)) { | |
alien.alive = bullet.alive = false; | |
particleManager.createExplosion(alien.position.x, alien.position.y, 'white', 70, 5,5,3,.15,50); | |
player.score += 25; | |
} | |
} | |
} | |
} | |
function resolveBulletPlayerCollisions() { | |
for (var i = 0, len = aliens.length; i < len; i++) { | |
var alien = aliens[i]; | |
if (alien.bullet !== null && checkRectCollision(alien.bullet.bounds, player.bounds)) { | |
if (player.lives === 0) { | |
hasGameStarted = false; | |
} else { | |
alien.bullet.alive = false; | |
particleManager.createExplosion(player.position.x, player.position.y, 'green', 100, 8,8,6,0.001,40); | |
player.position.set(CANVAS_WIDTH/2, CANVAS_HEIGHT - 70); | |
player.lives--; | |
break; | |
} | |
} | |
} | |
} | |
function resolveCollisions() { | |
resolveBulletEnemyCollisions(); | |
resolveBulletPlayerCollisions(); | |
} | |
function updateGame(dt) { | |
player.handleInput(); | |
prevKeyStates = keyStates.slice(); | |
player.update(dt); | |
updateAliens(dt); | |
resolveCollisions(); | |
} | |
function drawIntoCanvas(width, height, drawFunc) { | |
var canvas = document.createElement('canvas'); | |
canvas.width = width; | |
canvas.height = height; | |
var ctx = canvas.getContext('2d'); | |
drawFunc(ctx); | |
return canvas; | |
} | |
function fillText(text, x, y, color, fontSize) { | |
if (typeof color !== 'undefined') ctx.fillStyle = color; | |
if (typeof fontSize !== 'undefined') ctx.font = fontSize + 'px Play'; | |
ctx.fillText(text, x, y); | |
} | |
function fillCenteredText(text, x, y, color, fontSize) { | |
var metrics = ctx.measureText(text); | |
fillText(text, x - metrics.width/2, y, color, fontSize); | |
} | |
function fillBlinkingText(text, x, y, blinkFreq, color, fontSize) { | |
if (~~(0.5 + Date.now() / blinkFreq) % 2) { | |
fillCenteredText(text, x, y, color, fontSize); | |
} | |
} | |
function drawBottomHud() { | |
ctx.fillStyle = '#02ff12'; | |
ctx.fillRect(0, CANVAS_HEIGHT - 30, CANVAS_WIDTH, 2); | |
fillText(player.lives + ' x ', 10, CANVAS_HEIGHT - 7.5, 'white', 20); | |
ctx.drawImage(spriteSheetImg, player.clipRect.x, player.clipRect.y, player.clipRect.w, | |
player.clipRect.h, 45, CANVAS_HEIGHT - 23, player.clipRect.w * 0.5, | |
player.clipRect.h * 0.5); | |
fillText('CREDIT: ', CANVAS_WIDTH - 115, CANVAS_HEIGHT - 7.5); | |
fillCenteredText('SCORE: ' + player.score, CANVAS_WIDTH/2, 20); | |
fillBlinkingText('00', CANVAS_WIDTH - 25, CANVAS_HEIGHT - 7.5, TEXT_BLINK_FREQ); | |
} | |
function drawAliens(resized) { | |
for (var i = 0; i < aliens.length; i++) { | |
var alien = aliens[i]; | |
alien.draw(resized); | |
} | |
} | |
function drawGame(resized) { | |
player.draw(resized); | |
drawAliens(resized); | |
particleManager.draw(); | |
drawBottomHud(); | |
} | |
function drawStartScreen() { | |
fillCenteredText("Space Invaders", CANVAS_WIDTH/2, CANVAS_HEIGHT/2.75, '#FFFFFF', 36); | |
fillBlinkingText("Press enter to play!", CANVAS_WIDTH/2, CANVAS_HEIGHT/2, 500, '#FFFFFF', 36); | |
} | |
function animate() { | |
var now = window.performance.now(); | |
var dt = now - lastTime; | |
if (dt > 100) dt = 100; | |
if (wasKeyPressed(13) && !hasGameStarted) { | |
initGame(); | |
hasGameStarted = true; | |
} | |
if (hasGameStarted) { | |
updateGame(dt / 1000); | |
} | |
ctx.fillStyle = 'black'; | |
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); | |
if (hasGameStarted) { | |
drawGame(false); | |
} else { | |
drawStartScreen(); | |
} | |
lastTime = now; | |
requestAnimationFrame(animate); | |
} | |
// ################################################################### | |
// Event Listener functions | |
// | |
// ################################################################### | |
function resize() { | |
var w = window.innerWidth; | |
var h = window.innerHeight; | |
// calculate the scale factor to keep a correct aspect ratio | |
var scaleFactor = Math.min(w / CANVAS_WIDTH, h / CANVAS_HEIGHT); | |
if (IS_CHROME) { | |
canvas.width = CANVAS_WIDTH * scaleFactor; | |
canvas.height = CANVAS_HEIGHT * scaleFactor; | |
setImageSmoothing(false); | |
ctx.transform(scaleFactor, 0, 0, scaleFactor, 0, 0); | |
} else { | |
// resize the canvas css properties | |
canvas.style.width = CANVAS_WIDTH * scaleFactor + 'px'; | |
canvas.style.height = CANVAS_HEIGHT * scaleFactor + 'px'; | |
} | |
} | |
function onKeyDown(e) { | |
e.preventDefault(); | |
keyStates[e.keyCode] = true; | |
} | |
function onKeyUp(e) { | |
e.preventDefault(); | |
keyStates[e.keyCode] = false; | |
} | |
// ################################################################### | |
// Start game! | |
// | |
// ################################################################### | |
window.onload = function() { | |
init(); | |
animate(); | |
}; |
Again putting my boring university breaks to good use I made a small space invaders clone.
A Pen by Anthony Del Ciotto on CodePen.
body, html{ | |
background-color: #EEEEEE; | |
width: 100%; | |
height: 100%; | |
overflow: hidden; | |
} | |
canvas { | |
display: block; | |
margin: auto; | |
position :absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
image-rendering: optimizeSpeed; | |
image-rendering: -moz-crisp-edges; | |
image-rendering: -webkit-optimize-contrast; | |
image-rendering: optimize-contrast | |
} |
@jakeisnt cool!
Just wanted to make it clear: This game is not mine, all credit goes to John Resig http://ejohn.org/
I don't remember why I copied it into a gist here, or how this is even discoverable because it's named 'index.html', but just wanted to give credit where credit is due!
Good to hear that people enjoy it
Ah thanks! This was the first search result on DuckDuckGo for "space invaders game html canvas". Not sure why it showed up first!
I copied all to my visual studio and it doesn't work
I wanted something that I could throw into an existing project as an easter egg, this gave a good start, but there was a sever lack of modern JS features (like classes) and obviously typing.
So I re-wrote this into an import-able package, you can find it here
Thanks for making this discoverable in the first place. 👍
@mrfrase3 cool! Nice job
@afgs99, I ended changing the variable
SHOOT_KEY
to32
- this allowed me to use 'Space' instead of 'x' for shooting, which I found a bit more comfortable.Great work with this game by the way!