| @@ -0,0 +1 @@ | |||
| conf.js | |||
| @@ -0,0 +1,3 @@ | |||
| var conf = { | |||
| assets: "nsfw" | |||
| }; | |||
| @@ -9,6 +9,7 @@ | |||
| </head> | |||
| <body> | |||
| <canvas id="canvas"></canvas> | |||
| <script src="conf.js"></script> | |||
| <script src="js/util.js"></script> | |||
| <script src="js/vec2.js"></script> | |||
| <script src="js/assets.js"></script> | |||
| @@ -1,12 +1,53 @@ | |||
| var assetsPath = "assets/"+conf.assets; | |||
| var assets = { | |||
| imgs: { | |||
| player: new ImageSource("player"), | |||
| background: new ImageSource("background", null, ".jpg"), | |||
| wall: new ImageSource("wall") | |||
| }, | |||
| soundtracks: { | |||
| soundtrack1: new AudioSource("soundtracks/track1"), | |||
| soundtrack2: new AudioSource("soundtracks/track2"), | |||
| soundtrack3: new AudioSource("soundtracks/track3") | |||
| } | |||
| }; | |||
| /* | |||
| * Audio source. | |||
| */ | |||
| function AudioSource(src, ext) { | |||
| makeEventListener(this); | |||
| var ext = ext || ".mp3"; | |||
| this.audio = document.createElement("audio"); | |||
| this.ready = false; | |||
| this.audio.addEventListener("canplaythrough", function() { | |||
| this.ready = true; | |||
| this.emit("load"); | |||
| }.bind(this)); | |||
| this.audio.addEventListener("ended", function() { | |||
| this.emit("ended"); | |||
| }.bind(this)); | |||
| this.audio.src = assetsPath+"/"+src+ext; | |||
| } | |||
| AudioSource.prototype.play = function() { | |||
| this.audio.currentTime = 0; | |||
| this.audio.play(); | |||
| } | |||
| AudioSource.prototype.pause = function() { | |||
| this.audio.pause(); | |||
| } | |||
| /* | |||
| * Image source. | |||
| * If animation, the images must be stacked on top of each other. | |||
| */ | |||
| function ImageSource(src, frameh) { | |||
| var ext = ".png"; | |||
| function ImageSource(src, frameh, ext) { | |||
| var ext = ext || ".png"; | |||
| this.img = document.createElement("img"); | |||
| this.ready = false; | |||
| @@ -15,7 +56,10 @@ function ImageSource(src, frameh) { | |||
| this.frameh = frameh == null ? -1 : frameh; | |||
| this.steps = 1; | |||
| console.log("image", assetsPath, assetsPath+"/images/"+src+ext); | |||
| this.img.onload = function() { | |||
| console.log(this); | |||
| this.ready = true; | |||
| this.width = this.img.width; | |||
| this.height = this.img.height; | |||
| @@ -31,14 +75,14 @@ function ImageSource(src, frameh) { | |||
| "Height isn't evenly divisible by frame height. "+ | |||
| "Height: "+this.height+", frame height: "+this.frameh); | |||
| } | |||
| console.log(this); | |||
| }.bind(this); | |||
| this.img.src = "assets/"+src+ext; | |||
| this.img.src = assetsPath+"/images/"+src+ext; | |||
| } | |||
| ImageSource.prototype.draw = function(ctx, step) { | |||
| if (!step) step = 0; | |||
| if (!this.ready) | |||
| return; | |||
| ctx.drawImage(this.img, | |||
| 0, this.frameh * step, | |||
| @@ -1,17 +1,72 @@ | |||
| /* | |||
| * Background | |||
| */ | |||
| function Background(game) { | |||
| makeEnt(this, game, 100); | |||
| this.img = assets.imgs.background; | |||
| this.img1x = 0; | |||
| this.img1y = -300; | |||
| this.img2x = 0; | |||
| this.img2y = -100; | |||
| this.collude = false; | |||
| this.moves = true; | |||
| this.started = false; | |||
| this.pos.set({ x: -400, y: 0 }); | |||
| } | |||
| Background.prototype.update = function() { | |||
| if (!this.img.ready) | |||
| return; | |||
| if (this.img.ready && this.img2x === 0) | |||
| this.img2x = this.img1x + this.img.width * 2; | |||
| if (this.game.started) | |||
| this.force.add({ x: 0.09, y: 0 }); | |||
| if (this.img1x + this.img.width * 2 + this.pos.x < this.game.camera.x) { | |||
| this.img1x = this.img2x; | |||
| this.img2x += this.img.width * 2; | |||
| var tmpy = this.img1y; | |||
| this.img1y = this.img2y; | |||
| this.img2y = tmpy; | |||
| } | |||
| } | |||
| Background.prototype.draw = function(ctx){ | |||
| ctx.save(); | |||
| ctx.translate(this.img1x, this.img1y); | |||
| ctx.scale(2, 2); | |||
| this.img.draw(ctx); | |||
| ctx.restore(); | |||
| ctx.translate(this.img2x, this.img2y); | |||
| ctx.scale(2, 2); | |||
| this.img.draw(ctx); | |||
| } | |||
| /* | |||
| * Player | |||
| */ | |||
| function Player(game) { | |||
| makeEnt(this, game, 100); | |||
| this.img = assets.imgs.player; | |||
| game.started = false; | |||
| this.moves = true; | |||
| this.inputListener = true; | |||
| this.layer = 1; | |||
| this.invincible = false; | |||
| this.invincibleTimeout = null; | |||
| this.started = false; | |||
| this.rotation = 0; | |||
| this.bigBox = new Box(60, 40, { x: -30, y: -20 }); | |||
| this.box = new Box(); | |||
| this.shape.push(this.box); | |||
| @@ -21,7 +76,7 @@ function Player(game) { | |||
| this.pos.set({ x: 0, y: game.canvas.height / 2 }); | |||
| } | |||
| Player.prototype.setBox = function() { | |||
| var w = 30 + ((this.erectLevel - 1) * 15); | |||
| var w = 25 + ((this.erectLevel - 1) * 10); | |||
| var h = 15 + ((this.erectLevel - 1) * 6); | |||
| this.box.width = w; | |||
| this.box.height = h; | |||
| @@ -57,14 +112,14 @@ Player.prototype.onInput = function(name, down) { | |||
| // Jump | |||
| if (name === "jump" && down) { | |||
| this.started = true; | |||
| this.game.started = true; | |||
| this.vel.y = -1.3; | |||
| } | |||
| } | |||
| Player.prototype.update = function() { | |||
| // Gravity and movement | |||
| if (this.started) { | |||
| if (this.game.started) { | |||
| this.force.y = 0.4; | |||
| this.force.x = 0.13 + ((this.erectLevel - 1) * 0.02); | |||
| } | |||
| @@ -105,17 +160,15 @@ Player.prototype.move = function() { | |||
| this.rotation = this.vel.angle(); | |||
| } | |||
| Player.prototype.lose = function() { | |||
| alert("You died!"); | |||
| this.game.stop(); | |||
| this.game.stop(this.pos.x / 10); | |||
| } | |||
| Player.prototype.draw = function(ctx) { | |||
| if (this.invincible) | |||
| ctx.fillStyle = "#d5d5bf"; | |||
| else | |||
| ctx.fillStyle = "#f5f5dc"; | |||
| ctx.rotate(this.rotation); | |||
| this.shape.draw(ctx); | |||
| var scale = 0.4 + this.erectLevel / 8; | |||
| ctx.scale(scale, scale); | |||
| ctx.translate(70, -50); | |||
| ctx.rotate(Math.PI / 2); | |||
| this.img.draw(ctx); | |||
| } | |||
| /* | |||
| @@ -124,7 +177,9 @@ Player.prototype.draw = function(ctx) { | |||
| function Obstacle(game, x, y) { | |||
| makeEnt(this, game, 100); | |||
| this.pos.set({ x: x, y: y }); | |||
| this.img = assets.imgs.wall; | |||
| this.collude = false; | |||
| var w = 70; | |||
| var h = game.canvas.height; | |||
| @@ -132,12 +187,16 @@ function Obstacle(game, x, y) { | |||
| this.shape.push(new Box(w, h, { x: 0, y: -(h / 2) - (gap / 2) })); | |||
| this.shape.push(new Box(w, h, { x: 0, y: (h / 2) + (gap / 2) })); | |||
| this.pos.set({ x: x, y: y }); | |||
| } | |||
| Obstacle.prototype.draw = function(ctx) { | |||
| this.shape.draw(ctx); | |||
| ctx.translate(-100, this.game.canvas.height / 2 - 340); | |||
| ctx.scale(0.9, 1.1); | |||
| this.img.draw(ctx); | |||
| } | |||
| Obstacle.prototype.update = function() { | |||
| if (this.game.camera.x > this.pos.x + this.shape.width()) | |||
| if (this.game.camera.x > this.pos.x + 800) | |||
| this.dead = true; | |||
| } | |||
| @@ -147,10 +206,13 @@ Obstacle.prototype.update = function() { | |||
| function PowerUp(game, x, y, type) { | |||
| makeEnt(this, game, 100); | |||
| this.pos.set({ x: x, y: y }); | |||
| this.layer = 1; | |||
| this.type = type; | |||
| this.shape.push(new Box(30, 30)); | |||
| this.pos.set({ x: x, y: y }); | |||
| } | |||
| PowerUp.prototype.draw = function(ctx) { | |||
| ctx.fillStyle = "#33ee33"; | |||
| @@ -120,6 +120,8 @@ function makeEnt(obj, game, mass) { | |||
| obj.moves = false; | |||
| obj.dead = false; | |||
| obj.inputListener = false; | |||
| obj.collude = true; | |||
| obj.layer = 0; | |||
| obj.mass = mass || 0; | |||
| obj.forceScalar = 1 / obj.mass; | |||
| @@ -139,6 +141,7 @@ function Game(canvas) { | |||
| this.worldgen = null; | |||
| this.entities = []; | |||
| this.layers = []; | |||
| this.inputListeners = []; | |||
| this.keys = {}; | |||
| @@ -178,6 +181,11 @@ Game.prototype.start = function(worldgen) { | |||
| } | |||
| 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); | |||
| } | |||
| @@ -199,7 +207,7 @@ Game.prototype.update = function() { | |||
| this.entities.pop(); | |||
| } else { | |||
| this.entities[i] = this.entities.pop(); | |||
| var ent = this.entities[i]; | |||
| ent = this.entities[i]; | |||
| } | |||
| } | |||
| @@ -227,23 +235,33 @@ Game.prototype.update = function() { | |||
| // Go through and draw | |||
| this.canvas.width = this.canvas.width; | |||
| for (var i = 0; i < this.entities.length; ++i) { | |||
| var ent = this.entities[i]; | |||
| if (ent.dead) | |||
| continue; | |||
| if (ent.pos.x + ent.shape.width() < this.camera.x) | |||
| continue; | |||
| if (ent.pos.x > this.camera.x + this.canvas.width) | |||
| continue; | |||
| 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 === this.entities.length) { | |||
| layer.pop(); | |||
| } else { | |||
| layer[j] = layer.pop(); | |||
| ent = layer[j]; | |||
| } | |||
| } | |||
| 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(); | |||
| 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; | |||
| 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(); | |||
| } | |||
| } | |||
| // Clear presses | |||
| @@ -255,12 +273,12 @@ Game.prototype.update = function() { | |||
| if (!this.stopped) | |||
| this.raf = reqAnimFrame(this.update.bind(this)); | |||
| } | |||
| Game.prototype.stop = function() { | |||
| 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(); | |||
| this.onstop(Math.floor(score)); | |||
| } | |||
| @@ -2,6 +2,36 @@ window.ontouchmove = function(evt) { | |||
| evt.preventDefault(); | |||
| } | |||
| var currentSoundtrack = -1; | |||
| function playSoundtrack(tracks) { | |||
| console.log("Playing track..."); | |||
| var keys = Object.keys(tracks); | |||
| if (currentSoundtrack === -1) | |||
| currentSoundtrack = randInt(0, keys.length); | |||
| var key = keys[currentSoundtrack]; | |||
| var track = tracks[key]; | |||
| console.log("Playing track", key); | |||
| var trackId = currentSoundtrack; | |||
| while (currentSoundtrack === trackId) | |||
| currentSoundtrack = randInt(0, keys.length); | |||
| if (track.ready) { | |||
| track.play(); | |||
| track.once("ended", function() { playSoundtrack(tracks) }); | |||
| console.log("playing immediately, as it's loaded"); | |||
| } else { | |||
| console.log("loading it..."); | |||
| track.once("load", function() { | |||
| console.log("loaded."); | |||
| track.play(); | |||
| track.once("ended", function() { playSoundtrack(tracks) }); | |||
| }); | |||
| } | |||
| } | |||
| playSoundtrack(assets.soundtracks); | |||
| function run() { | |||
| var game = new Game(document.getElementById("canvas")); | |||
| game.canvas.width = window.innerWidth; | |||
| @@ -11,7 +41,10 @@ function run() { | |||
| game.start(worldgen); | |||
| game.onstop = run; | |||
| game.onstop = function(score) { | |||
| alert("You lost! Score: "+score); | |||
| run(); | |||
| } | |||
| } | |||
| run(); | |||
| @@ -3,6 +3,49 @@ window.reqAnimFrame = window.requestAnimationFrame || (function(fn) { | |||
| window.cancelAnimFrame = window.cancelAnimationFrame || window.clearTimeout; | |||
| // Random number from min (inclusive) to max (exclusive) | |||
| function randInt(min, max) { | |||
| return Math.floor(Math.random() * (max - min)) + min; | |||
| } | |||
| function makeEventListener(obj) { | |||
| obj.__callbacks = {}; | |||
| obj.on = makeEventListener.on; | |||
| obj.off = makeEventListener.off; | |||
| obj.once = makeEventListener.once; | |||
| obj.emit = makeEventListener.emit; | |||
| } | |||
| makeEventListener.on = function on(name, fn) { | |||
| if (!this.__callbacks[name]) | |||
| this.__callbacks[name] = []; | |||
| this.__callbacks[name].push(fn); | |||
| } | |||
| makeEventListener.once = function once(name, fn) { | |||
| var self = this; | |||
| this.on(name, function f() { | |||
| self.off(name, f); | |||
| fn.apply(arguments); | |||
| }); | |||
| } | |||
| makeEventListener.off = function off(name, fn) { | |||
| if (!this.__callbacks[name]) | |||
| return; | |||
| for (var i in this.__callbacks[name]) { | |||
| if (this.__callbacks[name] === fn) | |||
| delete this.__callbacks[name][i]; | |||
| } | |||
| } | |||
| makeEventListener.emit = function emit(name) { | |||
| if (!this.__callbacks[name]) | |||
| return; | |||
| var args = [].slice.apply(arguments); | |||
| args.splice(0, 1); | |||
| this.__callbacks[name].forEach(function(f) { | |||
| f.apply(args); | |||
| }); | |||
| } | |||
| @@ -6,7 +6,8 @@ function WorldGen(game) { | |||
| this.maxY = 400; | |||
| this.powerupCounter = randInt(WorldGen.range[0], WorldGen.range[1]); | |||
| // Spawn player | |||
| // Spawn player and background | |||
| game.spawn(new Background(game)); | |||
| game.spawn(new Player(game)); | |||
| } | |||
| WorldGen.range = [5, 12]; | |||
| @@ -23,7 +24,7 @@ WorldGen.prototype.genNext = function() { | |||
| if (y > this.maxY) y = this.maxY; | |||
| var obstacle = new Obstacle(this.game, x, y); | |||
| this.game.entities.push(obstacle); | |||
| this.game.spawn(obstacle); | |||
| this.prevX = x; | |||
| @@ -33,7 +34,7 @@ WorldGen.prototype.genNext = function() { | |||
| x - 50, | |||
| y + 80 + (this.game.canvas.height / 2)); | |||
| this.game.entities.push(powerup); | |||
| this.game.spawn(powerup); | |||
| this.powerupCounter = randInt(WorldGen.range[0], WorldGen.range[1]); | |||
| } | |||