| @@ -1 +1,2 @@ | |||
| conf.js | |||
| node_modules | |||
| @@ -5,3 +5,27 @@ body { | |||
| canvas { | |||
| position: absolute; | |||
| } | |||
| #done-screen { | |||
| position: absolute; | |||
| background-color: rgba(255, 255, 255, 0.7); | |||
| border: 1px solid black; | |||
| border-radius: 20px; | |||
| text-align: center; | |||
| font-size: 2em; | |||
| left: 0px; | |||
| right: 0px; | |||
| top: 0px; | |||
| bottom: 0px; | |||
| margin: auto; | |||
| width: 300px; | |||
| height: 150px; | |||
| padding-top: 50px; | |||
| } | |||
| #done-screen.hidden { | |||
| display: none; | |||
| } | |||
| @@ -5,10 +5,17 @@ | |||
| <title>Flappy Dick</title> | |||
| <link rel="stylesheet" href="css/style.css"> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"> | |||
| </head> | |||
| <body> | |||
| <canvas id="canvas"></canvas> | |||
| <div id="done-screen" class="hidden"> | |||
| Your score: <span id="score"></span> | |||
| <div> | |||
| <button id="restart">Restart</button> | |||
| </div> | |||
| </div> | |||
| <script src="conf.js"></script> | |||
| <script src="js/util.js"></script> | |||
| <script src="js/vec2.js"></script> | |||
| @@ -1,17 +1,27 @@ | |||
| var assetsPath = "assets/"+conf.assets; | |||
| var commonPath = "assets/common"; | |||
| var assets = { | |||
| imgs: { | |||
| player: new ImageSource("player"), | |||
| player: new ImageSource("theme:player"), | |||
| player_flipped: new ImageSource("theme:player_flipped"), | |||
| background: new ImageSource("background", null, ".jpg"), | |||
| wall: new ImageSource("wall") | |||
| wall: new ImageSource("wall"), | |||
| wall_overlap: new ImageSource("wall-overlap"), | |||
| }, | |||
| audio: { | |||
| hurt: new AudioSource("audio/hurt"), | |||
| loss: new AudioSource("audio/loss"), | |||
| flap: new AudioSource("audio/flap"), | |||
| chomp: new AudioSource("audio/chomp"), | |||
| }, | |||
| soundtracks: { | |||
| soundtrack1: new AudioSource("soundtracks/track1"), | |||
| soundtrack2: new AudioSource("soundtracks/track2"), | |||
| soundtrack3: new AudioSource("soundtracks/track3") | |||
| } | |||
| soundtrack3: new AudioSource("soundtracks/track3"), | |||
| }, | |||
| }; | |||
| /* | |||
| @@ -21,10 +31,13 @@ function AudioSource(src, ext) { | |||
| makeEventListener(this); | |||
| var ext = ext || ".mp3"; | |||
| this.audio = document.createElement("audio"); | |||
| this.elems = []; | |||
| for (var i = 0; i < 10; ++i) | |||
| this.elems[i] = new Audio(commonPath+"/"+src+ext); | |||
| this.ready = false; | |||
| this.index = 0; | |||
| this.audio.addEventListener("canplaythrough", function() { | |||
| this.elems[0].addEventListener("canplaythrough", function() { | |||
| if (this.ready) | |||
| return; | |||
| @@ -32,18 +45,21 @@ function AudioSource(src, ext) { | |||
| this.emit("load"); | |||
| }.bind(this)); | |||
| this.audio.addEventListener("ended", function() { | |||
| this.elems[0].addEventListener("ended", function() { | |||
| this.emit("ended"); | |||
| }.bind(this)); | |||
| this.audio.src = assetsPath+"/"+src+ext; | |||
| this.elems[0].onerror = function() { | |||
| console.error("Error with", src); | |||
| } | |||
| } | |||
| AudioSource.prototype.play = function() { | |||
| this.audio.currentTime = 0; | |||
| this.audio.play(); | |||
| this.elems[this.index++].play(); | |||
| this.index %= this.elems.length; | |||
| } | |||
| AudioSource.prototype.pause = function() { | |||
| this.audio.pause(); | |||
| for (var i in this.elems) | |||
| this.elmes[i].pause(); | |||
| } | |||
| /* | |||
| @@ -52,6 +68,12 @@ AudioSource.prototype.pause = function() { | |||
| function ImageSource(src, frameh, ext) { | |||
| var ext = ext || ".png"; | |||
| if (src.indexOf("theme:") == 0) { | |||
| src = assetsPath+"/images/"+src.split(":")[1]; | |||
| } else { | |||
| src = commonPath+"/images/"+src; | |||
| } | |||
| this.img = document.createElement("img"); | |||
| this.ready = false; | |||
| this.width = 0; | |||
| @@ -60,7 +82,6 @@ function ImageSource(src, frameh, ext) { | |||
| this.steps = 1; | |||
| this.img.onload = function() { | |||
| console.log(this); | |||
| this.ready = true; | |||
| this.width = this.img.width; | |||
| this.height = this.img.height; | |||
| @@ -78,7 +99,10 @@ function ImageSource(src, frameh, ext) { | |||
| } | |||
| }.bind(this); | |||
| this.img.src = assetsPath+"/images/"+src+ext; | |||
| this.img.src = src+ext | |||
| this.img.onerror = function() { | |||
| console.error("error with", src); | |||
| } | |||
| } | |||
| ImageSource.prototype.draw = function(ctx, step) { | |||
| if (!step) step = 0; | |||
| @@ -24,8 +24,7 @@ Background.prototype.update = function() { | |||
| 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 }); | |||
| this.vel.set({ x: this.game.ids.player.vel.x * 0.9, y: 0 }); | |||
| if (this.img1x + this.img.width * 2 + this.pos.x < this.game.camera.x) { | |||
| this.img1x = this.img2x; | |||
| @@ -54,8 +53,14 @@ Background.prototype.draw = function(ctx){ | |||
| */ | |||
| function Player(game) { | |||
| makeEnt(this, game, 100); | |||
| makeEnt(this, game, 100, "player"); | |||
| this.img = assets.imgs.player; | |||
| this.img_flipped = assets.imgs.player_flipped; | |||
| this.hurt = assets.audio.hurt; | |||
| this.loss = assets.audio.loss; | |||
| this.flap = assets.audio.flap; | |||
| this.chomp = assets.audio.chomp; | |||
| this.hurtLoop = null; | |||
| game.started = false; | |||
| this.moves = true; | |||
| @@ -63,6 +68,7 @@ function Player(game) { | |||
| this.layer = 1; | |||
| this.invincible = false; | |||
| this.visible = true; | |||
| this.invincibleTimeout = null; | |||
| this.started = false; | |||
| this.rotation = 0; | |||
| @@ -88,7 +94,7 @@ Player.prototype.setBox = function() { | |||
| } | |||
| Player.prototype.rise = function() { | |||
| this.erectLevel += 1; | |||
| this.setInvincible(200); | |||
| this.setInvincible(200, false); | |||
| this.setBox(); | |||
| } | |||
| Player.prototype.lower = function() { | |||
| @@ -96,25 +102,38 @@ Player.prototype.lower = function() { | |||
| return; | |||
| this.erectLevel -= 1; | |||
| if (this.erectLevel === 0) | |||
| if (this.erectLevel === 0) { | |||
| this.lose(); | |||
| else | |||
| this.setInvincible(500); | |||
| } else { | |||
| this.hurt.play(); | |||
| this.setInvincible(2000, true); | |||
| } | |||
| this.setBox(); | |||
| } | |||
| Player.prototype.setInvincible = function(time) { | |||
| Player.prototype.setInvincible = function(time, hurt) { | |||
| clearTimeout(this.invincibleTimeout); | |||
| if (hurt) { | |||
| clearInterval(this.hurtLoop); | |||
| this.hurtLoop = setInterval(function() { | |||
| this.visible = !this.visible; | |||
| }.bind(this), 60); | |||
| } | |||
| this.invincible = true; | |||
| this.invincibleTimeout = setTimeout(function() { | |||
| this.invincible = false; | |||
| this.visible = true; | |||
| clearInterval(this.hurtLoop); | |||
| this.hurtLoop = null; | |||
| }.bind(this), time); | |||
| } | |||
| Player.prototype.onInput = function(name, down) { | |||
| // Jump | |||
| if (name === "jump" && down) { | |||
| this.flap.play(); | |||
| this.game.started = true; | |||
| this.vel.y = -1.3; | |||
| } | |||
| @@ -144,9 +163,10 @@ Player.prototype.update = function() { | |||
| if (ent instanceof Obstacle && this.shape.collidesWith(ent.shape)) { | |||
| this.lower(); | |||
| return; | |||
| this.vel.x = -1; | |||
| } else if (ent instanceof PowerUp && this.bigShape.collidesWith(ent.shape)) { | |||
| this.rise(); | |||
| this.chomp.play(); | |||
| ent.dead = true; | |||
| } | |||
| } | |||
| @@ -160,15 +180,23 @@ Player.prototype.move = function() { | |||
| this.rotation = this.vel.angle(); | |||
| } | |||
| Player.prototype.lose = function() { | |||
| this.game.stop(this.pos.x / 10); | |||
| this.loss.play(); | |||
| this.game.stop(this.pos.x / 100); | |||
| } | |||
| Player.prototype.draw = function(ctx) { | |||
| if (!this.visible) | |||
| return; | |||
| ctx.rotate(this.rotation); | |||
| var scale = 0.4 + this.erectLevel / 8; | |||
| ctx.scale(scale, scale); | |||
| ctx.translate(70, -50); | |||
| ctx.rotate(Math.PI / 2); | |||
| this.img.draw(ctx); | |||
| if (this.vel.y < 0) | |||
| this.img.draw(ctx); | |||
| else | |||
| this.img_flipped.draw(ctx); | |||
| } | |||
| /* | |||
| @@ -178,6 +206,7 @@ Player.prototype.draw = function(ctx) { | |||
| function Obstacle(game, x, y) { | |||
| makeEnt(this, game, 100); | |||
| this.img = assets.imgs.wall; | |||
| this.overlap = assets.imgs.wall_overlap; | |||
| this.collude = false; | |||
| @@ -195,6 +224,11 @@ Obstacle.prototype.draw = function(ctx) { | |||
| ctx.scale(0.9, 1.1); | |||
| this.img.draw(ctx); | |||
| } | |||
| Obstacle.prototype.drawOverlay =function(ctx) { | |||
| ctx.translate(-100, this.game.canvas.height / 2 - 340); | |||
| ctx.scale(0.9, 1.1); | |||
| this.overlap.draw(ctx); | |||
| } | |||
| Obstacle.prototype.update = function() { | |||
| if (this.game.camera.x > this.pos.x + 800) | |||
| this.dead = true; | |||
| @@ -111,7 +111,7 @@ Shape.prototype.height = function() { | |||
| } | |||
| // Make entity from object | |||
| function makeEnt(obj, game, mass) { | |||
| function makeEnt(obj, game, mass, id) { | |||
| obj.pos = new Vec2(); | |||
| obj.vel = new Vec2(); | |||
| obj.force = new Vec2(); | |||
| @@ -125,6 +125,10 @@ function makeEnt(obj, game, mass) { | |||
| obj.mass = mass || 0; | |||
| obj.forceScalar = 1 / obj.mass; | |||
| if (id) { | |||
| game.ids[id] = obj; | |||
| } | |||
| } | |||
| /* | |||
| @@ -141,6 +145,7 @@ function Game(canvas) { | |||
| this.worldgen = null; | |||
| this.entities = []; | |||
| this.ids = {}; | |||
| this.layers = []; | |||
| this.inputListeners = []; | |||
| @@ -252,11 +257,15 @@ Game.prototype.update = function() { | |||
| } | |||
| } | |||
| 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, | |||
| @@ -266,6 +275,26 @@ Game.prototype.update = function() { | |||
| } | |||
| } | |||
| // 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; | |||
| @@ -27,6 +27,11 @@ function playSoundtrack(tracks) { | |||
| } | |||
| playSoundtrack(assets.soundtracks); | |||
| document.querySelector("#restart").addEventListener("click", function() { | |||
| document.querySelector("#done-screen").className = "hidden"; | |||
| run(); | |||
| }); | |||
| function run() { | |||
| var game = new Game(document.getElementById("canvas")); | |||
| game.canvas.width = window.innerWidth; | |||
| @@ -37,8 +42,8 @@ function run() { | |||
| game.start(worldgen); | |||
| game.onstop = function(score) { | |||
| alert("You lost! Score: "+score); | |||
| run(); | |||
| document.querySelector("#score").innerText = score; | |||
| document.querySelector("#done-screen").className = ""; | |||
| } | |||
| } | |||
| @@ -7,8 +7,8 @@ function WorldGen(game) { | |||
| this.powerupCounter = randInt(WorldGen.range[0], WorldGen.range[1]); | |||
| // Spawn player and background | |||
| game.spawn(new Background(game)); | |||
| game.spawn(new Player(game)); | |||
| game.spawn(new Background(game)); | |||
| } | |||
| WorldGen.range = [5, 12]; | |||
| @@ -0,0 +1,15 @@ | |||
| { | |||
| "name": "flappydick", | |||
| "version": "1.0.0", | |||
| "description": "", | |||
| "main": "conf.js", | |||
| "scripts": { | |||
| "test": "echo \"Error: no test specified\" && exit 1" | |||
| }, | |||
| "repository": { | |||
| "type": "git", | |||
| "url": "gogs@git.mort.coffee:mort/flappydick.git" | |||
| }, | |||
| "author": "Martin Dørum <martid0311@gmail.com> (http://mort.coffee)", | |||
| "license": "ISC" | |||
| } | |||