| conf.js |
| var conf = { | |||||
| assets: "nsfw" | |||||
| }; |
| </head> | </head> | ||||
| <body> | <body> | ||||
| <canvas id="canvas"></canvas> | <canvas id="canvas"></canvas> | ||||
| <script src="conf.js"></script> | |||||
| <script src="js/util.js"></script> | <script src="js/util.js"></script> | ||||
| <script src="js/vec2.js"></script> | <script src="js/vec2.js"></script> | ||||
| <script src="js/assets.js"></script> | <script src="js/assets.js"></script> |
| var assetsPath = "assets/"+conf.assets; | |||||
| var 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. | * 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.img = document.createElement("img"); | ||||
| this.ready = false; | this.ready = false; | ||||
| this.frameh = frameh == null ? -1 : frameh; | this.frameh = frameh == null ? -1 : frameh; | ||||
| this.steps = 1; | this.steps = 1; | ||||
| console.log("image", assetsPath, assetsPath+"/images/"+src+ext); | |||||
| this.img.onload = function() { | this.img.onload = function() { | ||||
| console.log(this); | |||||
| this.ready = true; | this.ready = true; | ||||
| this.width = this.img.width; | this.width = this.img.width; | ||||
| this.height = this.img.height; | this.height = this.img.height; | ||||
| "Height isn't evenly divisible by frame height. "+ | "Height isn't evenly divisible by frame height. "+ | ||||
| "Height: "+this.height+", frame height: "+this.frameh); | "Height: "+this.height+", frame height: "+this.frameh); | ||||
| } | } | ||||
| console.log(this); | |||||
| }.bind(this); | }.bind(this); | ||||
| this.img.src = "assets/"+src+ext; | |||||
| this.img.src = assetsPath+"/images/"+src+ext; | |||||
| } | } | ||||
| ImageSource.prototype.draw = function(ctx, step) { | ImageSource.prototype.draw = function(ctx, step) { | ||||
| if (!step) step = 0; | if (!step) step = 0; | ||||
| if (!this.ready) | |||||
| return; | |||||
| ctx.drawImage(this.img, | ctx.drawImage(this.img, | ||||
| 0, this.frameh * step, | 0, this.frameh * step, |
| /* | |||||
| * 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 | * Player | ||||
| */ | */ | ||||
| function Player(game) { | function Player(game) { | ||||
| makeEnt(this, game, 100); | makeEnt(this, game, 100); | ||||
| this.img = assets.imgs.player; | |||||
| game.started = false; | |||||
| this.moves = true; | this.moves = true; | ||||
| this.inputListener = true; | this.inputListener = true; | ||||
| this.layer = 1; | |||||
| this.invincible = false; | this.invincible = false; | ||||
| this.invincibleTimeout = null; | this.invincibleTimeout = null; | ||||
| this.started = false; | this.started = false; | ||||
| this.rotation = 0; | this.rotation = 0; | ||||
| this.bigBox = new Box(60, 40, { x: -30, y: -20 }); | |||||
| this.box = new Box(); | this.box = new Box(); | ||||
| this.shape.push(this.box); | this.shape.push(this.box); | ||||
| this.pos.set({ x: 0, y: game.canvas.height / 2 }); | this.pos.set({ x: 0, y: game.canvas.height / 2 }); | ||||
| } | } | ||||
| Player.prototype.setBox = function() { | 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); | var h = 15 + ((this.erectLevel - 1) * 6); | ||||
| this.box.width = w; | this.box.width = w; | ||||
| this.box.height = h; | this.box.height = h; | ||||
| // Jump | // Jump | ||||
| if (name === "jump" && down) { | if (name === "jump" && down) { | ||||
| this.started = true; | |||||
| this.game.started = true; | |||||
| this.vel.y = -1.3; | this.vel.y = -1.3; | ||||
| } | } | ||||
| } | } | ||||
| Player.prototype.update = function() { | Player.prototype.update = function() { | ||||
| // Gravity and movement | // Gravity and movement | ||||
| if (this.started) { | |||||
| if (this.game.started) { | |||||
| this.force.y = 0.4; | this.force.y = 0.4; | ||||
| this.force.x = 0.13 + ((this.erectLevel - 1) * 0.02); | this.force.x = 0.13 + ((this.erectLevel - 1) * 0.02); | ||||
| } | } | ||||
| this.rotation = this.vel.angle(); | this.rotation = this.vel.angle(); | ||||
| } | } | ||||
| Player.prototype.lose = function() { | Player.prototype.lose = function() { | ||||
| alert("You died!"); | |||||
| this.game.stop(); | |||||
| this.game.stop(this.pos.x / 10); | |||||
| } | } | ||||
| Player.prototype.draw = function(ctx) { | Player.prototype.draw = function(ctx) { | ||||
| if (this.invincible) | |||||
| ctx.fillStyle = "#d5d5bf"; | |||||
| else | |||||
| ctx.fillStyle = "#f5f5dc"; | |||||
| ctx.rotate(this.rotation); | 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); | |||||
| } | } | ||||
| /* | /* | ||||
| function Obstacle(game, x, y) { | function Obstacle(game, x, y) { | ||||
| makeEnt(this, game, 100); | makeEnt(this, game, 100); | ||||
| this.pos.set({ x: x, y: y }); | |||||
| this.img = assets.imgs.wall; | |||||
| this.collude = false; | |||||
| var w = 70; | var w = 70; | ||||
| var h = game.canvas.height; | var h = game.canvas.height; | ||||
| 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.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) { | 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() { | 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; | this.dead = true; | ||||
| } | } | ||||
| function PowerUp(game, x, y, type) { | function PowerUp(game, x, y, type) { | ||||
| makeEnt(this, game, 100); | makeEnt(this, game, 100); | ||||
| this.pos.set({ x: x, y: y }); | |||||
| this.layer = 1; | |||||
| this.type = type; | this.type = type; | ||||
| this.shape.push(new Box(30, 30)); | this.shape.push(new Box(30, 30)); | ||||
| this.pos.set({ x: x, y: y }); | |||||
| } | } | ||||
| PowerUp.prototype.draw = function(ctx) { | PowerUp.prototype.draw = function(ctx) { | ||||
| ctx.fillStyle = "#33ee33"; | ctx.fillStyle = "#33ee33"; |
| obj.moves = false; | obj.moves = false; | ||||
| obj.dead = false; | obj.dead = false; | ||||
| obj.inputListener = false; | obj.inputListener = false; | ||||
| obj.collude = true; | |||||
| obj.layer = 0; | |||||
| obj.mass = mass || 0; | obj.mass = mass || 0; | ||||
| obj.forceScalar = 1 / obj.mass; | obj.forceScalar = 1 / obj.mass; | ||||
| this.worldgen = null; | this.worldgen = null; | ||||
| this.entities = []; | this.entities = []; | ||||
| this.layers = []; | |||||
| this.inputListeners = []; | this.inputListeners = []; | ||||
| this.keys = {}; | this.keys = {}; | ||||
| } | } | ||||
| Game.prototype.spawn = function(ent) { | Game.prototype.spawn = function(ent) { | ||||
| this.entities.push(ent); | this.entities.push(ent); | ||||
| if (!this.layers[ent.layer]) | |||||
| this.layers[ent.layer] = []; | |||||
| this.layers[ent.layer].push(ent); | |||||
| if (ent.inputListener) | if (ent.inputListener) | ||||
| this.inputListeners.push(ent); | this.inputListeners.push(ent); | ||||
| } | } | ||||
| this.entities.pop(); | this.entities.pop(); | ||||
| } else { | } else { | ||||
| this.entities[i] = this.entities.pop(); | this.entities[i] = this.entities.pop(); | ||||
| var ent = this.entities[i]; | |||||
| ent = this.entities[i]; | |||||
| } | } | ||||
| } | } | ||||
| // Go through and draw | // Go through and draw | ||||
| this.canvas.width = this.canvas.width; | 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 | // Clear presses | ||||
| if (!this.stopped) | if (!this.stopped) | ||||
| this.raf = reqAnimFrame(this.update.bind(this)); | this.raf = reqAnimFrame(this.update.bind(this)); | ||||
| } | } | ||||
| Game.prototype.stop = function() { | |||||
| Game.prototype.stop = function(score) { | |||||
| this.stopped = true; | this.stopped = true; | ||||
| cancelAnimFrame(this.raf); | cancelAnimFrame(this.raf); | ||||
| window.removeEventListener("keyup", this.onkey); | window.removeEventListener("keyup", this.onkey); | ||||
| window.removeEventListener("keydown", this.onkey); | window.removeEventListener("keydown", this.onkey); | ||||
| if (this.onstop) | if (this.onstop) | ||||
| this.onstop(); | |||||
| this.onstop(Math.floor(score)); | |||||
| } | } |
| evt.preventDefault(); | 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() { | function run() { | ||||
| var game = new Game(document.getElementById("canvas")); | var game = new Game(document.getElementById("canvas")); | ||||
| game.canvas.width = window.innerWidth; | game.canvas.width = window.innerWidth; | ||||
| game.start(worldgen); | game.start(worldgen); | ||||
| game.onstop = run; | |||||
| game.onstop = function(score) { | |||||
| alert("You lost! Score: "+score); | |||||
| run(); | |||||
| } | |||||
| } | } | ||||
| run(); | run(); |
| window.cancelAnimFrame = window.cancelAnimationFrame || window.clearTimeout; | window.cancelAnimFrame = window.cancelAnimationFrame || window.clearTimeout; | ||||
| // Random number from min (inclusive) to max (exclusive) | |||||
| function randInt(min, max) { | function randInt(min, max) { | ||||
| return Math.floor(Math.random() * (max - min)) + min; | 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); | |||||
| }); | |||||
| } |
| this.maxY = 400; | this.maxY = 400; | ||||
| this.powerupCounter = randInt(WorldGen.range[0], WorldGen.range[1]); | 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)); | game.spawn(new Player(game)); | ||||
| } | } | ||||
| WorldGen.range = [5, 12]; | WorldGen.range = [5, 12]; | ||||
| if (y > this.maxY) y = this.maxY; | if (y > this.maxY) y = this.maxY; | ||||
| var obstacle = new Obstacle(this.game, x, y); | var obstacle = new Obstacle(this.game, x, y); | ||||
| this.game.entities.push(obstacle); | |||||
| this.game.spawn(obstacle); | |||||
| this.prevX = x; | this.prevX = x; | ||||
| x - 50, | x - 50, | ||||
| y + 80 + (this.game.canvas.height / 2)); | 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]); | this.powerupCounter = randInt(WorldGen.range[0], WorldGen.range[1]); | ||||
| } | } |