@@ -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" | |||
} |