var keymap = { 32: "jump", touch: "jump" }; /* * Collision box */ function Box(width, height, pos) { pos = pos || {}; this.width = width; this.height = height; this.pos = new Vec2(pos.x, pos.y); } Box.prototype.collidesWith = function(box, ent1pos, ent2pos) { var x1 = ent1pos.x + this.pos.x; var x2 = ent2pos.x + box.pos.x; var y1 = ent1pos.y + this.pos.y; var y2 = ent2pos.y + box.pos.y; var w1 = this.width; var w2 = box.width; var h1 = this.height; var h2 = box.height; return ((x1 + w1 >= x2 && x1 <= x2 + w2) && (y1 + h1 >= y2 && y1 <= y2 + h2)); } Box.prototype.trace = function(ctx) { ctx.beginPath(); ctx.moveTo(this.pos.x, this.pos.y); ctx.lineTo(this.pos.x, this.pos.y + this.height); ctx.lineTo(this.pos.x + this.width, this.pos.y + this.height); ctx.lineTo(this.pos.x + this.width, this.pos.y); ctx.closePath(); } /* * Collision shape */ function Shape(ent) { this.ent = ent; this.boxes = []; this._height = -1; this._width = -1; } Shape.prototype.push = function(box) { this._height = -1; this._width = -1; this.boxes.push(box); } Shape.prototype.pop = function() { this._height = -1; this._width = -1; return this.boxes.pop(); } Shape.prototype.draw = function(ctx) { for (var i = 0; i < this.boxes.length; ++i) { var box = this.boxes[i]; box.trace(ctx); ctx.stroke(); ctx.fill(); } } Shape.prototype.collidesWith = function(shape) { for (var i = 0; i < this.boxes.length; ++i) { var box = this.boxes[i]; for (var j = 0; j < shape.boxes.length; ++j) { var otherBox = shape.boxes[j]; if (box.collidesWith(otherBox, this.ent.pos, shape.ent.pos)) return true; } } return false; } Shape.prototype.width = function() { if (this._width !== -1) return this._width; var minX = 0; var maxX = 0; this.boxes.forEach(function(box) { if (box.pos.x < minX) minX = box.pos.x; if (box.pos.x + box.width > maxX) maxX = box.pos.x + box.width; }); this._width = Math.abs(maxX - minX); return this._width; } Shape.prototype.height = function() { if (this._height !== -1) return this._height; var minY = 0; var maxY = 0; this.boxes.forEach(function(box) { if (box.pos.y < minY) minY = box.pos.y; if (box.pos.y + box.height > maxY) maxY = box.pos.y + box.height; }); this._height = Math.abs(maxY - minY); return this._height; } // Make entity from object function makeEnt(obj, game, mass, id) { obj.pos = new Vec2(); obj.vel = new Vec2(); obj.force = new Vec2(); obj.game = game; obj.shape = new Shape(obj); obj.moves = false; obj.dead = false; obj.inputListener = false; obj.collude = true; obj.layer = 0; obj.mass = mass || 0; obj.forceScalar = 1 / obj.mass; if (id) { game.ids[id] = obj; } } /* * Game */ function Game(canvas) { this.canvas = canvas; this.ctx = canvas.getContext("2d"); this.raf = null; this.prevTime = null; this.stopped = true; this.camera = new Vec2(); this.worldgen = null; this.entities = []; this.ids = {}; this.layers = []; this.inputListeners = []; this.keys = {}; this.onkey = function onkey(evt) { var down = (evt.type === "keydown" || evt.type === "touchstart"); var code = evt.keyCode || "touch"; var name = keymap[code]; if (name) { this.keys[name] = down; for (var i = 0; i < this.inputListeners.length; ++i) { var ent = this.inputListeners[i]; if (ent === null) { continue; } else if (ent.dead) { delete this.inputListeners[i]; continue; } this.inputListeners[i].onInput(name, down); } } }.bind(this); } Game.prototype.start = function(worldgen) { window.addEventListener("keydown", this.onkey); window.addEventListener("keyup", this.onkey); window.addEventListener("touchstart", this.onkey); window.addEventListener("touchend", this.onkey); this.prevTime = new Date().getTime(); this.stopped = false; this.worldgen = worldgen; this.update(); } Game.prototype.spawn = function(ent) { this.entities.push(ent); if (!this.layers[ent.layer]) this.layers[ent.layer] = []; this.layers[ent.layer].push(ent); if (ent.inputListener) this.inputListeners.push(ent); } Game.prototype.update = function() { var time = new Date().getTime(); var dt = time - this.prevTime; if (this.stopped) return; // Go through and update var xRatio = 1 / (1 + (dt * 0.005)); for (var i = 0; i < this.entities.length; ++i) { var ent = this.entities[i]; // Remove dead entities, replace them with the last entity if (ent.dead) { if (i + 1 === this.entities.length) { this.entities.pop(); continue; } else { this.entities[i] = this.entities.pop(); ent = this.entities[i]; } } if (ent.update) ent.update(); if (ent.moves) { ent.force.scale(ent.forceScalar * dt); ent.vel.add(ent.force); ent.force.set({ x: 0, y: 0 }); ent.vel.scale(xRatio); ent.pos.add(ent.vel.clone().scale(dt)); } if (ent.move) ent.move(); } // Exit if stopped if (this.stopped) return; // Tick worldgen this.worldgen.update(); // Go through and draw this.canvas.width = this.canvas.width; for (var i = 0; i < this.layers.length; ++i) { var layer = this.layers[i]; for (var j = 0; j < layer.length; ++j) { var ent = layer[j]; // Remove dead entities, replace them with the last entity if (ent.dead) { if (i + 1 === layer.length) { layer.pop(); continue; } else { layer[j] = layer.pop(); ent = layer[j]; } } ent.drawn = false; if (ent.collude && ent.pos.x + ent.shape.width() < this.camera.x) continue; if (ent.collude && ent.pos.x > this.camera.x + this.canvas.width) continue; ent.drawn = true; this.ctx.save(); this.ctx.translate( ent.pos.x - this.camera.x, ent.pos.y - this.camera.y); ent.draw(this.ctx); this.ctx.restore(); } } // Go through and draw overlap for (var i = 0; i < this.layers.length; ++i) { var layer = this.layers[i]; for (var j = 0; j < layer.length; ++j) { var ent = layer[j]; if (!ent.drawn) continue; if (!ent.drawOverlay) continue; this.ctx.save(); this.ctx.translate( ent.pos.x - this.camera.x, ent.pos.y - this.camera.y); ent.drawOverlay(this.ctx); this.ctx.restore(); } } // Clear presses for (var i in this.presses) { this.presses[i] = false; } this.prevTime = time; if (!this.stopped) this.raf = reqAnimFrame(this.update.bind(this)); } Game.prototype.stop = function(score) { this.stopped = true; cancelAnimFrame(this.raf); window.removeEventListener("keyup", this.onkey); window.removeEventListener("keydown", this.onkey); if (this.onstop) this.onstop(Math.floor(score)); }