@@ -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]); | |||
} |