planck-js
Version:
2D JavaScript physics engine for cross-platform HTML5 game development
505 lines (408 loc) • 12.6 kB
JavaScript
testbed('Breakout', function(testbed) {
var WIDTH = 20;
var HEIGHT = 26;
testbed.width = WIDTH;
testbed.height = HEIGHT * 1.12;
testbed.ratio = 32;
testbed.y = 0;
testbed.keydown = function() {
if (testbed.activeKeys.fire) {
if(state.state == 'gameover') {
state.initGame();
} else if (state.state == 'ready') {
state.startGame();
}
}
};
var physics = new Physics();
var state = new State();
function State() {
var state = this;
state.state = '';
var _score = 0;
var _combo = 1;
var _time = 0;
var _createRowTime = 0;
var _fullPaddleTime = 0;
var _balls = [];
var _bricks = [];
var _drops = [];
function updateStatus() {
if (state.state == 'gameover') {
testbed.status('Gameover!');
testbed.status('Score', _score);
} else if (state.state == 'ready') {
testbed.status('Ready!');
testbed.status('Score', _score);
} else {
testbed.status('');
testbed.status('Score', _score);
}
}
function paddleSpeed() {
return 18;
}
function dropSpeed() {
return -6;
}
function ballSpeed() {
return (13 + _score * 0.05) * 0.7;
}
function createRowTime() {
return Math.max(8000 - 20 * _score, 1000);
}
function resetPaddleTime() {
return 7500;
}
function addBall(ball) {
_balls.push(ball);
ball.speed = ballSpeed();
physics.addBall(ball);
}
function miniPaddle() {
physics.miniPaddle();
}
function fullPaddle() {
physics.fullPaddle();
}
function addDrop(drop) {
_drops.push(drop);
physics.addDrop(drop);
}
function createDrop(brick) {
var random = Math.random();
if (random < 0.06) {
addDrop({i: brick.i, j: brick.j, type: '+', speed : dropSpeed()});
} else if (random < 0.1) {
addDrop({i: brick.i, j: brick.j, type: '-', speed : dropSpeed()});
}
}
function addBrick(brick) {
_bricks.push(brick);
physics.addBrick(brick);
}
function createRow() {
_createRowTime = _time + createRowTime();
var gameover = false;
_bricks.forEach(function(brick) {
brick.j++;
physics.updateBrick(brick);
gameover = gameover | brick >= 10;
});
for (var i = 0; i < 7; i++) {
if (Math.random() < 0.1) {
continue;
}
var one = _score + 1, four = Math.max(0, _score * 1.1 - 60);
if (Math.random() < one / (four + one)) {
addBrick({type: 'normal', i: i, j: 0});
} else {
addBrick({type: 'small', i: i - 0.25, j: -0.25});
addBrick({type: 'small', i: i + 0.25, j: -0.25});
addBrick({type: 'small', i: i - 0.25, j: +0.25});
addBrick({type: 'small', i: i + 0.25, j: +0.25});
}
}
if (gameover) {
endGame();
}
}
testbed.step = function(t) {
_time += t = Math.min(t, 50);
if (state.state !== 'playing' && state.state !== 'ready') {
return;
}
if (testbed.activeKeys.left && !testbed.activeKeys.right) {
physics.movePaddle(-paddleSpeed() * t / 1000);
} else if (!testbed.activeKeys.left && testbed.activeKeys.right) {
physics.movePaddle(+paddleSpeed() * t / 1000);
}
if (state.state !== 'playing') {
return;
}
if (_createRowTime && _time > _createRowTime) {
_createRowTime = 0;
createRow();
}
if (_fullPaddleTime && _time > _fullPaddleTime) {
_fullPaddleTime = 0;
fullPaddle();
}
physics.tick(t);
};
state.hitBrick = function(brick) {
if (!removeFromArray(_bricks, brick)) return;
physics.removeBrick(brick);
!_bricks.length && createRow();
_score += _combo;
// _combo++;
updateStatus();
createDrop(brick);
};
state.hitBall = function() {
// _combo = 1;
};
state.missBall = function(ball) {
if (!removeFromArray(_balls, ball)) return;
physics.removeBall(ball);
if (!_balls.length) {
endGame();
}
};
state.catchDrop = function(drop) {
if (!removeFromArray(_drops, drop)) return;
physics.removeDrop(drop);
if (drop.type == '+') {
addBall({});
} else if (drop.type == '-') {
_fullPaddleTime = _time + resetPaddleTime();
miniPaddle();
}
};
state.missDrop = function(drop) {
if (!removeFromArray(_drops, drop)) return;
physics.removeDrop(drop);
};
function endGame() {
state.state = 'gameover';
updateStatus();
physics.endGame();
}
state.startGame = function() {
state.initGame();
physics.startGame();
state.state = 'playing';
};
state.initGame = function() {
if (state.state == 'ready') return;
state.state = 'ready';
_score = 0;
_combo = 1;
_createRowTime = 0;
_fullPaddleTime = 0;
physics.initGame();
addBall({});
createRow();
createRow();
createRow();
updateStatus();
};
}
function Physics() {
var pl = planck, Vec2 = pl.Vec2;
var world = this.world = new pl.World();
var bottomWall, paddle, balls = [], bricks = [], drops = [];
var BALL = 1, WALL = 2, BRICK = 4, DROP = 8, PADDLE = 16;
var ballFix = {
friction: 0.0,
restitution: 1.0,
filterCategoryBits: BALL,
filterMaskBits: PADDLE | WALL | BRICK
};
var paddleFix = {filterCategoryBits: PADDLE, filterMaskBits: BALL | DROP};
var wallFix = {filterCategoryBits: WALL, filterMaskBits: BALL | DROP};
var brickFix = {filterCategoryBits: BRICK, filterMaskBits: BALL};
var dropFix = {filterCategoryBits: DROP, filterMaskBits: PADDLE | WALL};
var ballShape = pl.Circle(0.5);
var normalBrickShape = pl.Box(1.9 / 2, 1.9 / 2);
var smallBrickShape = pl.Box(0.9 / 2, 0.9 / 2);
var fullPaddleShape = pl.Polygon([
Vec2(1.7, -0.2),
Vec2(1.8, -0.1),
Vec2(1.8, 0.1),
Vec2(1.7, 0.2),
Vec2(1.2, 0.4),
Vec2(0.4, 0.6),
Vec2(-0.4, 0.6),
Vec2(-1.2, 0.4),
Vec2(-1.7, 0.2),
Vec2(-1.8, 0.1),
Vec2(-1.8, -0.1),
Vec2(-1.7, -0.2)
]);
fullPaddleShape.paddleWidth = 3.6;
var miniPaddleShape = pl.Polygon([
Vec2(1.2, -0.1),
Vec2(1.2, 0.1),
Vec2(0.9, 0.4),
Vec2(0.2, 0.6),
Vec2(-0.2, 0.6),
Vec2(-0.9, 0.4),
Vec2(-1.2, 0.1),
Vec2(-1.2, -0.1)
]);
miniPaddleShape.paddleWidth = 2.4;
world.on('pre-solve', function(contact) {
var fA = contact.getFixtureA(), bA = fA.getBody();
var fB = contact.getFixtureB(), bB = fB.getBody();
var ball = bA.isBall && bA || bB.isBall && bB;
var brick = bA.isBrick && bA || bB.isBrick && bB;
var bottom = bA.isBottom && bA || bB.isBottom && bB;
var paddle = bA.isPaddle && bA || bB.isPaddle && bB;
var drop = bA.isDrop && bA || bB.isDrop && bB;
// do not change world immediately
setTimeout(function() {
if (ball && brick) {
state.hitBrick(brick.getUserData());
} else if (ball && bottom) {
state.missBall(ball.getUserData());
} else if (ball && paddle) {
state.hitBall(ball.getUserData());
} else if (drop && paddle) {
state.catchDrop(drop.getUserData());
} else if (drop && bottom) {
state.missDrop(drop.getUserData());
}
}, 1);
});
function createWorld() {
world.createBody(Vec2(+9, -0.5))
.createFixture(pl.Edge(Vec2(0, -12.5), Vec2(0, +11.5)), wallFix);
world.createBody(Vec2(-9, -0.5))
.createFixture(pl.Edge(Vec2(0, -12.5), Vec2(0, +11.5)), wallFix);
world.createBody(Vec2(0, +12))
.createFixture(pl.Edge(Vec2(-8, 0), Vec2(+8, 0)), wallFix);
world.createBody(Vec2(9, 12))
.createFixture(pl.Edge(Vec2(-1, 0), Vec2(0, -1)), wallFix);
world.createBody(Vec2(-9, 12))
.createFixture(pl.Edge(Vec2(1, 0), Vec2(0, -1)), wallFix);
bottomWall = world.createBody(Vec2(0, -13));
bottomWall.createFixture(pl.Edge(Vec2(-9, 0), Vec2(+9, 0)), wallFix);
bottomWall.isBottom = true;
}
function createPaddle(shape) {
var p, v;
if (paddle) {
p = paddle.getPosition();
v = paddle.getLinearVelocity();
world.destroyBody(paddle);
}
paddle = world.createKinematicBody({
position: Vec2(0, -10.5)
});
paddle.paddleWidth = shape.paddleWidth;
paddle.createFixture(shape, paddleFix);
paddle.isPaddle = true;
p && paddle.setPosition(p);
v && paddle.setLinearVelocity(v);
}
function createBall(pos) {
var body = world.createDynamicBody({
bullet: true,
angle: Math.random() * Math.PI * 2,
position: pos
});
body.createFixture(ballShape, ballFix);
body.isBall = true;
balls.push(body);
return body;
}
function createBrick(shape, pos) {
var body = world.createBody(pos);
body.createFixture(shape, brickFix);
body.isBrick = true;
bricks.push(body);
return body;
}
function createDrop(type) {
var body = world.createDynamicBody();
if (type == '+') {
body.createFixture(pl.Box(0.08, 0.32), dropFix);
body.createFixture(pl.Box(0.32, 0.08), dropFix);
} else if (type == '-') {
body.createFixture(pl.Box(0.3, 0.1), dropFix);
} else {
body.createFixture(pl.Circle(0.3), dropFix);
}
body.isDrop = true;
drops.push(body);
return body;
}
this.removeDrop = function(drop) {
if (!removeFromArray(drops, drop.body)) return;
world.destroyBody(drop.body);
};
this.removeBrick = function(brick) {
if (!removeFromArray(bricks, brick.body)) return;
world.destroyBody(brick.body);
};
this.removeBall = function(ball) {
if (!removeFromArray(balls, ball.body)) return;
world.destroyBody(ball.body);
};
this.updateBrick = function(brick) {
brick.body.setPosition(Vec2((brick.i - 3) * 2, 9 - brick.j * 2));
};
this.addBrick = function(brick) {
var shape = brick.type == 'small' ? smallBrickShape : normalBrickShape;
var pos = Vec2((brick.i - 3) * 2, 9 - brick.j * 2);
var body = brick.body = createBrick(shape, pos);
body.setUserData(brick);
};
this.addDrop = function(drop) {
var body = drop.body = createDrop(drop.type);
body.setUserData(drop);
body.setPosition(Vec2((drop.i - 3) * 2, 9 - drop.j * 2));
body.setLinearVelocity(Vec2(0, drop.speed));
};
this.addBall = function(ball) {
var body = ball.body = createBall();
body.setUserData(ball);
var oldball = balls[0];
if (oldball) {
body.setPosition(oldball.getPosition());
body.setLinearVelocity(Vec2(oldball.getLinearVelocity()).mul(-1));
} else {
body.setPosition(Vec2(0, -5));
}
};
this.miniPaddle = function() {
createPaddle(miniPaddleShape);
};
this.fullPaddle = function() {
createPaddle(fullPaddleShape);
};
this.movePaddle = function(dir) {
var p = paddle.getPosition();
p = Vec2(dir, 0).add(p);
p.x = Math.min(9 - paddle.paddleWidth / 2, Math.max(-(9 - paddle.paddleWidth / 2), p.x))
paddle.setPosition(p);
};
this.tick = function(t) {
};
this.endGame = function() {
world.destroyBody(paddle);
};
this.startGame = function() {
var ball = balls[0];
var a = Math.PI * Math.random() * 0.4 - 0.2;
var speed = ball.getUserData().speed;
ball.setLinearVelocity(Vec2(speed * Math.sin(a), speed * Math.cos(a)));
};
this.initGame = function() {
balls.forEach(function(body) {
world.destroyBody(body);
});
bricks.forEach(function(body) {
world.destroyBody(body);
});
drops.forEach(function(body) {
world.destroyBody(body);
});
createPaddle(fullPaddleShape);
};
createWorld();
}
state.initGame();
function removeFromArray(array, item) {
var i = array.indexOf(item);
if (i == -1) {
return false;
} else {
array.splice(i, 1);
return true;
}
}
return physics.world;
});
;
planck.