Browse Source

initial commit

master
mortie 7 years ago
commit
e8b7d15a13
9 changed files with 667 additions and 0 deletions
  1. 7
    0
      css/style.css
  2. 20
    0
      index.html
  3. 106
    0
      js/assets.js
  4. 151
    0
      js/entities.js
  5. 250
    0
      js/game.js
  6. 17
    0
      js/script.js
  7. 8
    0
      js/util.js
  8. 68
    0
      js/vec2.js
  9. 40
    0
      js/worldgen.js

+ 7
- 0
css/style.css View File

@@ -0,0 +1,7 @@
body {
margin: 0px;
}

canvas {
position: absolute;
}

+ 20
- 0
index.html View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<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>
<script src="js/util.js"></script>
<script src="js/vec2.js"></script>
<script src="js/assets.js"></script>
<script src="js/game.js"></script>
<script src="js/entities.js"></script>
<script src="js/worldgen.js"></script>
<script src="js/script.js"></script>
</body>
</html>

+ 106
- 0
js/assets.js View File

@@ -0,0 +1,106 @@
var assets = {
};

/*
* Image source.
* If animation, the images must be stacked on top of each other.
*/
function ImageSource(src, frameh) {
var ext = ".png";

this.img = document.createElement("img");
this.ready = false;
this.width = 0;
this.height = 0;
this.frameh = frameh == null ? -1 : frameh;
this.steps = 1;

this.img.onload = function() {
this.ready = true;
this.width = this.img.width;
this.height = this.img.height;

if (this.frameh === -1)
this.frameh = this.height;

this.steps = this.height / this.frameh;
if (this.steps !== Math.round(this.steps)) {
this.steps = Math.round(this.steps);
console.log(
"Warning: '"+src+ext+"': "+
"Height isn't evenly divisible by frame height. "+
"Height: "+this.height+", frame height: "+this.frameh);
}
}.bind(this);

this.img.src = "assets/"+src+ext;
}
ImageSource.prototype.draw = function(ctx, step) {
if (!step) step = 0;

ctx.drawImage(this.img, 0, 0,
this.width, this.frameh,
0, this.frameh * step,
this.width, this.frameh);
}

function Animation(imgSrc, type, loop, fps) {
this.type = type || "forward";
this.loop = loop || false;
this.imgSrc = imgSrc;
this.fps = fps || 12;
this.waitTime = 1000 / this.fps;
this.done = false;

this.doStep = true;

if (type === "forward" || type === "bounce") {
this.step = 0;
this.direction = 1;
} else if (type === "reverse" || type === "bounce-reverse") {
this.step = -1;
this.direction = -1;
}

// We don't care about the direction after setting this.direction
if (this.type === "bounce-reverse")
this.type = "bounce";
else if (this.type === "reverse")
this.type = "forward";
}
Animation.prototype.nextFrame = function() {
if (!this.imgSrc.ready) {
return;
} else if (this.step === -1) {
this.step = this.imgSrc.steps - 1;
return;
} else if (this.imgSrc.steps === 1) {
return;
}

var next = this.step + this.direction;
if (next < 0 || next > this.imgSrc.steps - 1) {
if (this.loop) {
this.direction = -this.direction;
next = this.step + this.direction;
} else {
this.done = true;
return;
}
}
this.step = next;
}
Animation.prototype.draw = function(ctx) {
if (!this.imgSrc.ready)
return;

this.imgSrc.draw(ctx, this.step);

if (this.doStep) {
this.nextFrame();
this.doStep = false;
this.setTimeout(function() {
this.doStep = true;
}.bind(this), this.waitTime);
}
}

+ 151
- 0
js/entities.js View File

@@ -0,0 +1,151 @@

/*
* Player
*/

function Player(game) {
makeEnt(this, game, 100);
this.moves = true;
this.invincible = false;
this.invincibleTimeout = null;
this.started = false;
this.rotation = 0;

this.erectLevel = 0;
this.rise();

this.pos.set({ x: 0, y: game.canvas.height / 2 });
}
Player.prototype.rise = function() {
this.erectLevel += 1;

var w = 30 + ((this.erectLevel - 1) * 15);
var h = 15 + ((this.erectLevel - 1) * 6);
this.shape.push(new Box(w, h, { x: -(w/2), y: -(h/2) }));

this.setInvincible(200);
}
Player.prototype.lower = function() {
if (this.invincible)
return;

this.shape.pop();

this.erectLevel -= 1;
if (this.erectLevel === 0)
this.lose();
else
this.setInvincible(500);
}
Player.prototype.setInvincible = function(time) {
clearTimeout(this.invincibleTimeout);

this.invincible = true;
this.invincibleTimeout = setTimeout(function() {
this.invincible = false;
}.bind(this), time);
}
Player.prototype.update = function() {
// Jump
if (this.game.presses.jump) {
this.started = true;
this.vel.y = -1.3;
}

// Gravity and movement
if (this.started) {
this.force.y = 0.4;
this.force.x = 0.13 + ((this.erectLevel - 1) * 0.02);
}

// Lose if we hit the edge
if (
this.pos.y < 0 ||
this.pos.y > this.game.canvas.height - this.shape.height()) {
this.lose();
return;
}

// Collide
for (var i in this.game.entities) {
var ent = this.game.entities[i];

if (ent === this)
continue;

if (!this.shape.collidesWith(ent.shape))
continue;

if (ent instanceof Obstacle) {
this.lower();
return;
} else if (ent instanceof PowerUp) {
this.rise();
ent.dead = true;
}
}
}
Player.prototype.move = function() {

// Move camera
this.game.camera.x = this.pos.x - (this.game.canvas.width / 7);

// Rotate
this.rotation = this.vel.rotation();
}
Player.prototype.lose = function() {
alert("You died!");
this.game.stop();
}
Player.prototype.draw = function(ctx) {
if (this.invincible)
ctx.fillStyle = "#d5d5bf";
else
ctx.fillStyle = "#f5f5dc";
ctx.rotate(this.rotation);
this.shape.draw(ctx);
}

/*
* Obstacle
*/

function Obstacle(game, x, y) {
makeEnt(this, game, 100);
this.pos.set({ x: x, y: y });

var w = 40;
var h = game.canvas.height;
var gap = 200;

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) }));
}
Obstacle.prototype.draw = function(ctx) {
this.shape.draw(ctx);
}
Obstacle.prototype.update = function() {
if (this.game.camera.x > this.pos.x + this.shape.width())
this.dead = true;
}

/*
* PowerUp
*/

function PowerUp(game, x, y, type) {
makeEnt(this, game, 100);
this.pos.set({ x: x, y: y });
this.type = type;

this.shape.push(new Box(30, 30));
}
PowerUp.prototype.draw = function(ctx) {
ctx.beginPath();
ctx.arc(15, 15, 15, 0, 2 * Math.PI);
ctx.stroke();
}
PowerUp.prototype.update = function() {
if (this.game.camera.x > this.pos.x + this.shape.width())
this.dead = true;
}

+ 250
- 0
js/game.js View File

@@ -0,0 +1,250 @@
var keymap = {
32: "jump",
touch: "jump"
};

/*
* Collision box
*/

function Box(width, height, pos) {
pos = pos || {};
this.width = width;
this.height = height;
this.pos = new Vec2(pos.x, pos.y);
}
Box.prototype.collidesWith = function(box, ent1pos, ent2pos) {
var x1 = ent1pos.x + this.pos.x;
var x2 = ent2pos.x + box.pos.x;
var y1 = ent1pos.y + this.pos.y;
var y2 = ent2pos.y + box.pos.y;
var w1 = this.width;
var w2 = box.width;
var h1 = this.height;
var h2 = box.height;

return ((x1 + w1 >= x2 && x1 <= x2 + w2) &&
(y1 + h1 >= y2 && y1 <= y2 + h2));
}
Box.prototype.trace = function(ctx) {
ctx.beginPath();
ctx.moveTo(this.pos.x, this.pos.y);
ctx.lineTo(this.pos.x, this.pos.y + this.height);
ctx.lineTo(this.pos.x + this.width, this.pos.y + this.height);
ctx.lineTo(this.pos.x + this.width, this.pos.y);
ctx.closePath();
}

/*
* Collision shape
*/

function Shape(ent) {
this.ent = ent;
this.boxes = [];
this._height = -1;
this._width = -1;
}
Shape.prototype.push = function(box) {
this._height = -1;
this._width = -1;
this.boxes.push(box);
}
Shape.prototype.pop = function() {
this._height = -1;
this._width = -1;
return this.boxes.pop();
}
Shape.prototype.draw = function(ctx) {
for (var i = 0; i < this.boxes.length; ++i) {
var box = this.boxes[i];
box.trace(ctx);
ctx.stroke();
ctx.fill();
}
}
Shape.prototype.collidesWith = function(shape) {
for (var i = 0; i < this.boxes.length; ++i) {
var box = this.boxes[i];
for (var j = 0; j < shape.boxes.length; ++j) {
var otherBox = shape.boxes[j];
if (box.collidesWith(otherBox, this.ent.pos, shape.ent.pos))
return true;
}
}

return false;
}
Shape.prototype.width = function() {
if (this._width !== -1)
return this._width;

var minX = 0;
var maxX = 0;

this.boxes.forEach(function(box) {
if (box.pos.x < minX)
minX = box.pos.x;
if (box.pos.x + box.width > maxX)
maxX = box.pos.x + box.width;
});

this._width = Math.abs(maxX - minX);
return this._width;
}
Shape.prototype.height = function() {
if (this._height !== -1)
return this._height;

var minY = 0;
var maxY = 0;

this.boxes.forEach(function(box) {
if (box.pos.y < minY)
minY = box.pos.y;
if (box.pos.y + box.height > maxY)
maxY = box.pos.y + box.height;
});

this._height = Math.abs(maxY - minY);
return this._height;
}

// Make entity from object
function makeEnt(obj, game, mass) {
obj.pos = new Vec2();
obj.vel = new Vec2();
obj.force = new Vec2();
obj.game = game;
obj.shape = new Shape(obj);
obj.moves = false;
this.dead = false;

obj.mass = mass || 0;
obj.forceScalar = 1 / obj.mass;
}

/*
* Game
*/

function Game(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.raf = null;
this.prevTime = null;
this.stopped = true;
this.camera = new Vec2();
this.worldgen = null;

this.entities = [];

this.keys = {};
this.presses = {};
this.onkey = function onkey(evt) {
var down = (evt.type === "keydown" || evt.type === "touchstart");
var code = evt.keyCode || "touch";

var name = keymap[code];
if (name) {
this.keys[name] = down;
if (down)
this.presses[name] = true;
}
}.bind(this);
}

Game.prototype.start = function(worldgen) {
window.addEventListener("keydown", this.onkey);
window.addEventListener("keyup", this.onkey);
window.addEventListener("touchstart", this.onkey);
window.addEventListener("touchend", this.onkey);
this.prevTime = new Date().getTime();
this.stopped = false;
this.worldgen = worldgen;

this.update();
}
Game.prototype.update = function() {
var time = new Date().getTime();
var dt = time - this.prevTime;

if (this.stopped)
return;

// Go through and update
var xRatio = 1 / (1 + (dt * 0.005));
for (var i = 0; i < this.entities.length; ++i) {
var ent = this.entities[i];

// Remove dead entities, replace them with the last entity
if (ent.dead) {
if (i + 1 === this.entities.length) {
this.entities.pop();
} else {
this.entities[i] = this.entities.pop();
var ent = this.entities[i];
}
}

if (ent.update)
ent.update();

if (ent.moves) {
ent.force.scale(ent.forceScalar * dt);
ent.vel.add(ent.force);
ent.force.set({ x: 0, y: 0 });
ent.vel.scale(xRatio);
ent.pos.add(ent.vel.clone().scale(dt));
}

if (ent.move)
ent.move();
}

// Exit if stopped
if (this.stopped)
return;

// Tick worldgen
this.worldgen.update();

// 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;

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
for (var i in this.presses) {
this.presses[i] = false;
}

this.prevTime = time;
if (!this.stopped)
this.raf = reqAnimFrame(this.update.bind(this));
}
Game.prototype.stop = function() {
this.stopped = true;
cancelAnimFrame(this.raf);
window.removeEventListener("keyup", this.onkey);
window.removeEventListener("keydown", this.onkey);

if (this.onstop)
this.onstop();
}

+ 17
- 0
js/script.js View File

@@ -0,0 +1,17 @@
window.ontouchmove = function(evt) {
evt.preventDefault();
}

function run() {
var game = new Game(document.getElementById("canvas"));
game.canvas.width = window.innerWidth;
game.canvas.height = window.innerHeight;

var worldgen = new WorldGen(game);

game.start(worldgen);

game.onstop = run;
}

run();

+ 8
- 0
js/util.js View File

@@ -0,0 +1,8 @@
window.reqAnimFrame = window.requestAnimationFrame || (function(fn) {
return setTimeout(fn, 1000 / 60) });

window.cancelAnimFrame = window.cancelAnimationFrame || window.clearTimeout;

function randInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}

+ 68
- 0
js/vec2.js View File

@@ -0,0 +1,68 @@
function Vec2(x, y) {
this.x = x || 0;
this.y = y || 0;
return this;
}

Vec2.prototype.clone = function() {
return new Vec2(this.x, this.y);
}

Vec2.prototype.length = function() {
return Math.sqrt((this.x * this.x) + (this.y * this.y));
}

Vec2.prototype.set = function(vec) {
this.x = vec.x;
this.y = vec.y;

return this;
}

Vec2.prototype.add = function(vec) {
this.x += vec.x;
this.y += vec.y;

return this;
}

Vec2.prototype.sub = function(vec) {
this.x -= vec.x;
this.y -= vec.y;

return this;
}

Vec2.prototype.scale = function(num) {
this.x *= num;
this.y *= num;

return this;
}

Vec2.prototype.normalize = function() {
var len = this.length();

if (len === 0) {
this.x = 1;
this.y = 0;
} else {
this.scale(1 / len);
}

return this;
}

Vec2.prototype.rotate = function(rad) {
var x = this.x;
var y = this.y;

this.x = x * Math.cos(rad) - y * Math.sin(rad);
this.y = y * Math.cos(rad) + x * Math.sin(rad);

return this;
}

Vec2.prototype.rotation = function() {
return Math.atan2(this.y, this.x);
}

+ 40
- 0
js/worldgen.js View File

@@ -0,0 +1,40 @@
function WorldGen(game) {
this.game = game;
this.prevX = 0;

this.minY = -400;
this.maxY = 400;
this.powerupCounter = randInt(WorldGen.range[0], WorldGen.range[1]);

// Spawn player
game.entities.push(new Player(game));
}
WorldGen.range = [5, 12];

WorldGen.prototype.update = function() {
while (this.game.camera.x + this.game.canvas.width > this.prevX)
this.genNext();
}
WorldGen.prototype.genNext = function() {
var x = this.prevX + 300;
var y = Math.round((Math.random() - 0.5) * 300);

if (y < this.minY) y = this.minY;
if (y > this.maxY) y = this.maxY;

var obstacle = new Obstacle(this.game, x, y);
this.game.entities.push(obstacle);

this.prevX = x;

if (--this.powerupCounter === 0) {
var powerup = new PowerUp(
this.game,
x - 50,
y + 80 + (this.game.canvas.height / 2));

this.game.entities.push(powerup);

this.powerupCounter = randInt(WorldGen.range[0], WorldGen.range[1]);
}
}

Loading…
Cancel
Save