| conf.js | conf.js | ||||
| node_modules |
| canvas { | canvas { | ||||
| position: absolute; | 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; | |||||
| } |
| <title>Flappy Dick</title> | <title>Flappy Dick</title> | ||||
| <link rel="stylesheet" href="css/style.css"> | <link rel="stylesheet" href="css/style.css"> | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"> | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"> | ||||
| </head> | </head> | ||||
| <body> | <body> | ||||
| <canvas id="canvas"></canvas> | <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="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> |
| var assetsPath = "assets/"+conf.assets; | var assetsPath = "assets/"+conf.assets; | ||||
| var commonPath = "assets/common"; | |||||
| var assets = { | var assets = { | ||||
| imgs: { | imgs: { | ||||
| player: new ImageSource("player"), | |||||
| player: new ImageSource("theme:player"), | |||||
| player_flipped: new ImageSource("theme:player_flipped"), | |||||
| background: new ImageSource("background", null, ".jpg"), | 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: { | soundtracks: { | ||||
| soundtrack1: new AudioSource("soundtracks/track1"), | soundtrack1: new AudioSource("soundtracks/track1"), | ||||
| soundtrack2: new AudioSource("soundtracks/track2"), | soundtrack2: new AudioSource("soundtracks/track2"), | ||||
| soundtrack3: new AudioSource("soundtracks/track3") | |||||
| } | |||||
| soundtrack3: new AudioSource("soundtracks/track3"), | |||||
| }, | |||||
| }; | }; | ||||
| /* | /* | ||||
| makeEventListener(this); | makeEventListener(this); | ||||
| var ext = ext || ".mp3"; | 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.ready = false; | ||||
| this.index = 0; | |||||
| this.audio.addEventListener("canplaythrough", function() { | |||||
| this.elems[0].addEventListener("canplaythrough", function() { | |||||
| if (this.ready) | if (this.ready) | ||||
| return; | return; | ||||
| this.emit("load"); | this.emit("load"); | ||||
| }.bind(this)); | }.bind(this)); | ||||
| this.audio.addEventListener("ended", function() { | |||||
| this.elems[0].addEventListener("ended", function() { | |||||
| this.emit("ended"); | this.emit("ended"); | ||||
| }.bind(this)); | }.bind(this)); | ||||
| this.audio.src = assetsPath+"/"+src+ext; | |||||
| this.elems[0].onerror = function() { | |||||
| console.error("Error with", src); | |||||
| } | |||||
| } | } | ||||
| AudioSource.prototype.play = function() { | 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() { | AudioSource.prototype.pause = function() { | ||||
| this.audio.pause(); | |||||
| for (var i in this.elems) | |||||
| this.elmes[i].pause(); | |||||
| } | } | ||||
| /* | /* | ||||
| function ImageSource(src, frameh, ext) { | function ImageSource(src, frameh, ext) { | ||||
| var ext = ext || ".png"; | 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.img = document.createElement("img"); | ||||
| this.ready = false; | this.ready = false; | ||||
| this.width = 0; | this.width = 0; | ||||
| this.steps = 1; | this.steps = 1; | ||||
| 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; | ||||
| } | } | ||||
| }.bind(this); | }.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) { | ImageSource.prototype.draw = function(ctx, step) { | ||||
| if (!step) step = 0; | if (!step) step = 0; |
| if (this.img.ready && this.img2x === 0) | if (this.img.ready && this.img2x === 0) | ||||
| this.img2x = this.img1x + this.img.width * 2; | 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) { | if (this.img1x + this.img.width * 2 + this.pos.x < this.game.camera.x) { | ||||
| this.img1x = this.img2x; | this.img1x = this.img2x; | ||||
| */ | */ | ||||
| function Player(game) { | function Player(game) { | ||||
| makeEnt(this, game, 100); | |||||
| makeEnt(this, game, 100, "player"); | |||||
| this.img = assets.imgs.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; | game.started = false; | ||||
| this.moves = true; | this.moves = true; | ||||
| this.layer = 1; | this.layer = 1; | ||||
| this.invincible = false; | this.invincible = false; | ||||
| this.visible = true; | |||||
| this.invincibleTimeout = null; | this.invincibleTimeout = null; | ||||
| this.started = false; | this.started = false; | ||||
| this.rotation = 0; | this.rotation = 0; | ||||
| } | } | ||||
| Player.prototype.rise = function() { | Player.prototype.rise = function() { | ||||
| this.erectLevel += 1; | this.erectLevel += 1; | ||||
| this.setInvincible(200); | |||||
| this.setInvincible(200, false); | |||||
| this.setBox(); | this.setBox(); | ||||
| } | } | ||||
| Player.prototype.lower = function() { | Player.prototype.lower = function() { | ||||
| return; | return; | ||||
| this.erectLevel -= 1; | this.erectLevel -= 1; | ||||
| if (this.erectLevel === 0) | |||||
| if (this.erectLevel === 0) { | |||||
| this.lose(); | this.lose(); | ||||
| else | |||||
| this.setInvincible(500); | |||||
| } else { | |||||
| this.hurt.play(); | |||||
| this.setInvincible(2000, true); | |||||
| } | |||||
| this.setBox(); | this.setBox(); | ||||
| } | } | ||||
| Player.prototype.setInvincible = function(time) { | |||||
| Player.prototype.setInvincible = function(time, hurt) { | |||||
| clearTimeout(this.invincibleTimeout); | clearTimeout(this.invincibleTimeout); | ||||
| if (hurt) { | |||||
| clearInterval(this.hurtLoop); | |||||
| this.hurtLoop = setInterval(function() { | |||||
| this.visible = !this.visible; | |||||
| }.bind(this), 60); | |||||
| } | |||||
| this.invincible = true; | this.invincible = true; | ||||
| this.invincibleTimeout = setTimeout(function() { | this.invincibleTimeout = setTimeout(function() { | ||||
| this.invincible = false; | this.invincible = false; | ||||
| this.visible = true; | |||||
| clearInterval(this.hurtLoop); | |||||
| this.hurtLoop = null; | |||||
| }.bind(this), time); | }.bind(this), time); | ||||
| } | } | ||||
| Player.prototype.onInput = function(name, down) { | Player.prototype.onInput = function(name, down) { | ||||
| // Jump | // Jump | ||||
| if (name === "jump" && down) { | if (name === "jump" && down) { | ||||
| this.flap.play(); | |||||
| this.game.started = true; | this.game.started = true; | ||||
| this.vel.y = -1.3; | this.vel.y = -1.3; | ||||
| } | } | ||||
| if (ent instanceof Obstacle && this.shape.collidesWith(ent.shape)) { | if (ent instanceof Obstacle && this.shape.collidesWith(ent.shape)) { | ||||
| this.lower(); | this.lower(); | ||||
| return; | |||||
| this.vel.x = -1; | |||||
| } else if (ent instanceof PowerUp && this.bigShape.collidesWith(ent.shape)) { | } else if (ent instanceof PowerUp && this.bigShape.collidesWith(ent.shape)) { | ||||
| this.rise(); | this.rise(); | ||||
| this.chomp.play(); | |||||
| ent.dead = true; | ent.dead = true; | ||||
| } | } | ||||
| } | } | ||||
| this.rotation = this.vel.angle(); | this.rotation = this.vel.angle(); | ||||
| } | } | ||||
| Player.prototype.lose = function() { | 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) { | Player.prototype.draw = function(ctx) { | ||||
| if (!this.visible) | |||||
| return; | |||||
| ctx.rotate(this.rotation); | ctx.rotate(this.rotation); | ||||
| var scale = 0.4 + this.erectLevel / 8; | var scale = 0.4 + this.erectLevel / 8; | ||||
| ctx.scale(scale, scale); | ctx.scale(scale, scale); | ||||
| ctx.translate(70, -50); | ctx.translate(70, -50); | ||||
| ctx.rotate(Math.PI / 2); | ctx.rotate(Math.PI / 2); | ||||
| this.img.draw(ctx); | |||||
| if (this.vel.y < 0) | |||||
| this.img.draw(ctx); | |||||
| else | |||||
| this.img_flipped.draw(ctx); | |||||
| } | } | ||||
| /* | /* | ||||
| function Obstacle(game, x, y) { | function Obstacle(game, x, y) { | ||||
| makeEnt(this, game, 100); | makeEnt(this, game, 100); | ||||
| this.img = assets.imgs.wall; | this.img = assets.imgs.wall; | ||||
| this.overlap = assets.imgs.wall_overlap; | |||||
| this.collude = false; | this.collude = false; | ||||
| ctx.scale(0.9, 1.1); | ctx.scale(0.9, 1.1); | ||||
| this.img.draw(ctx); | 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() { | Obstacle.prototype.update = function() { | ||||
| if (this.game.camera.x > this.pos.x + 800) | if (this.game.camera.x > this.pos.x + 800) | ||||
| this.dead = true; | this.dead = true; |
| } | } | ||||
| // Make entity from object | // Make entity from object | ||||
| function makeEnt(obj, game, mass) { | |||||
| function makeEnt(obj, game, mass, id) { | |||||
| obj.pos = new Vec2(); | obj.pos = new Vec2(); | ||||
| obj.vel = new Vec2(); | obj.vel = new Vec2(); | ||||
| obj.force = new Vec2(); | obj.force = new Vec2(); | ||||
| obj.mass = mass || 0; | obj.mass = mass || 0; | ||||
| obj.forceScalar = 1 / obj.mass; | obj.forceScalar = 1 / obj.mass; | ||||
| if (id) { | |||||
| game.ids[id] = obj; | |||||
| } | |||||
| } | } | ||||
| /* | /* | ||||
| this.worldgen = null; | this.worldgen = null; | ||||
| this.entities = []; | this.entities = []; | ||||
| this.ids = {}; | |||||
| this.layers = []; | this.layers = []; | ||||
| this.inputListeners = []; | this.inputListeners = []; | ||||
| } | } | ||||
| } | } | ||||
| ent.drawn = false; | |||||
| if (ent.collude && ent.pos.x + ent.shape.width() < this.camera.x) | if (ent.collude && ent.pos.x + ent.shape.width() < this.camera.x) | ||||
| continue; | continue; | ||||
| if (ent.collude && ent.pos.x > this.camera.x + this.canvas.width) | if (ent.collude && ent.pos.x > this.camera.x + this.canvas.width) | ||||
| continue; | continue; | ||||
| ent.drawn = true; | |||||
| this.ctx.save(); | this.ctx.save(); | ||||
| this.ctx.translate( | this.ctx.translate( | ||||
| ent.pos.x - this.camera.x, | ent.pos.x - this.camera.x, | ||||
| } | } | ||||
| } | } | ||||
| // 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 | // Clear presses | ||||
| for (var i in this.presses) { | for (var i in this.presses) { | ||||
| this.presses[i] = false; | this.presses[i] = false; |
| } | } | ||||
| playSoundtrack(assets.soundtracks); | playSoundtrack(assets.soundtracks); | ||||
| document.querySelector("#restart").addEventListener("click", function() { | |||||
| document.querySelector("#done-screen").className = "hidden"; | |||||
| run(); | |||||
| }); | |||||
| 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 = function(score) { | game.onstop = function(score) { | ||||
| alert("You lost! Score: "+score); | |||||
| run(); | |||||
| document.querySelector("#score").innerText = score; | |||||
| document.querySelector("#done-screen").className = ""; | |||||
| } | } | ||||
| } | } | ||||
| this.powerupCounter = randInt(WorldGen.range[0], WorldGen.range[1]); | this.powerupCounter = randInt(WorldGen.range[0], WorldGen.range[1]); | ||||
| // Spawn player and background | // Spawn player and background | ||||
| game.spawn(new Background(game)); | |||||
| game.spawn(new Player(game)); | game.spawn(new Player(game)); | ||||
| game.spawn(new Background(game)); | |||||
| } | } | ||||
| WorldGen.range = [5, 12]; | WorldGen.range = [5, 12]; | ||||
| { | |||||
| "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" | |||||
| } |