mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-28 17:02:24 -05:00
569 lines
18 KiB
HTML
569 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
<title>Paperoids</title>
|
|
<script type="text/javascript" src="../../dist/paper-full.js"></script>
|
|
<script type="text/paperscript" canvas="canvas">
|
|
|
|
//
|
|
//
|
|
// Paperoids - an Asteroids clone for paper.js
|
|
//
|
|
// Sorry nerds, no enemy ships or sound just yet.
|
|
//
|
|
// Version: 1.2.1
|
|
// Author: David Hirmes (@hirmes)
|
|
// Date: 2011-08-09
|
|
//
|
|
// Revision History:
|
|
// 1.0 - 2011-07-13 - Initial release
|
|
// 1.1 - 2011-07-13 - Optimizations by Jonathan Puckey (@jonathanpuckey)
|
|
// 1.2 - 2011-07-16 - Refactored with closures (@jonathanpuckey)
|
|
// 1.2.1- 2011-08-09 - bug fixes
|
|
//
|
|
|
|
var presets = {
|
|
speed: 0.2,
|
|
maxRockSpeed: 4.5,
|
|
rockCount: 6,
|
|
lives: 3,
|
|
freeShipScore: 10000,
|
|
freeShipIncrement: 10000
|
|
};
|
|
|
|
function initialize() {
|
|
Rocks.add(presets.rockCount);
|
|
Score.update();
|
|
Lives.initialize();
|
|
}
|
|
|
|
function onKeyUp(event) {
|
|
if (event.key == 'space') {
|
|
Ship.moveTo(Point.random() * view.size);
|
|
Ship.stop();
|
|
}
|
|
if (event.key == 'z') {
|
|
Ship.fire();
|
|
}
|
|
// Show stats:
|
|
if (event.key == 'f') {
|
|
var stats = document.getElementById('stats');
|
|
if (stats) {
|
|
stats.style.display = (stats.style.display == 'block')
|
|
? 'none' : 'block';
|
|
}
|
|
}
|
|
}
|
|
|
|
function onFrame() {
|
|
Bullets.move();
|
|
Rocks.iterateExplosions();
|
|
Ship.checkCollisions();
|
|
if (Key.isDown('left')) {
|
|
Ship.turnLeft();
|
|
}
|
|
if (Key.isDown('right')) {
|
|
Ship.turnRight();
|
|
}
|
|
if (Key.isDown('up')) {
|
|
Ship.thrust();
|
|
} else {
|
|
Ship.coast();
|
|
}
|
|
Ship.move();
|
|
}
|
|
|
|
project.currentStyle.strokeColor = 'white';
|
|
|
|
var Game = {
|
|
roundDelay: false,
|
|
over: function() {
|
|
document.getElementById('gameover').style.display = 'block';
|
|
},
|
|
newRound: function() {
|
|
Game.roundDelay = false;
|
|
Rocks.add(presets.rockCount);
|
|
},
|
|
// Stats.js by Mr. Doob - https://github.com/mrdoob/stats.js
|
|
};
|
|
|
|
var assets = {
|
|
destroyedShip: new function() {
|
|
var group = new Group(
|
|
new Path([-10, -8], [10, 0]),
|
|
new Path([10, 0], [-10, 8]),
|
|
new Path([-8, 4], [-8, -4])
|
|
);
|
|
group.visible = false;
|
|
return group;
|
|
},
|
|
explosion: new function() {
|
|
var explosionPath = new Path.Circle(new Point(), 1);
|
|
explosionPath.fillColor = 'white';
|
|
explosionPath.strokeColor = null;
|
|
return new SymbolDefinition(explosionPath);
|
|
}
|
|
};
|
|
|
|
var Ship = new function() {
|
|
var path = new Path([-10, -8], [10, 0], [-10, 8], [-8, 4], [-8, -4]);
|
|
path.closed = true;
|
|
var thrust = new Path([-8, -4], [-14, 0], [-8, 4]);
|
|
var group = new Group(path, thrust);
|
|
group.position = view.bounds.center;
|
|
return {
|
|
item: group,
|
|
|
|
angle: 0,
|
|
|
|
vector: new Point({
|
|
angle: 0.2,
|
|
length: 1
|
|
}),
|
|
|
|
turnLeft: function() {
|
|
group.rotate(-3);
|
|
this.angle -= 3;
|
|
},
|
|
|
|
turnRight: function() {
|
|
group.rotate(3);
|
|
this.angle += 3;
|
|
},
|
|
|
|
thrust: function() {
|
|
thrust.visible = true;
|
|
this.vector += new Point({
|
|
angle: this.angle,
|
|
length: presets.speed
|
|
});
|
|
if (this.vector.length > 8) {
|
|
this.vector.length = 8;
|
|
}
|
|
},
|
|
|
|
stop: function() {
|
|
this.vector.length = 0;
|
|
},
|
|
|
|
fire: function() {
|
|
if (!this.dying)
|
|
Bullets.fire(this.item.position, this.angle);
|
|
},
|
|
|
|
coast: function() {
|
|
thrust.visible = false;
|
|
this.vector *= .992;
|
|
},
|
|
|
|
move: function() {
|
|
group.position += this.vector;
|
|
keepInView(group);
|
|
},
|
|
|
|
moveTo: function(position) {
|
|
group.position = position;
|
|
keepInView(group);
|
|
},
|
|
|
|
destroy: function() {
|
|
this.destroyedShip = assets.destroyedShip.clone();
|
|
this.destroyedShip.position = this.item.position;
|
|
this.destroyedShip.visible = true;
|
|
this.item.visible = false;
|
|
this.stop();
|
|
this.item.position = view.center;
|
|
this.dying = true;
|
|
},
|
|
|
|
destroyed: function() {
|
|
this.item.visible = true;
|
|
this.stop();
|
|
this.item.position = view.center;
|
|
this.dying = false;
|
|
this.destroyedShip.visible = false;
|
|
},
|
|
|
|
checkCollisions: function() {
|
|
var crashRock;
|
|
|
|
// move rocks and do a hit-test
|
|
// between bounding rect of rocks and ship
|
|
for (var i = 0; i < Rocks.children.length; i++) {
|
|
var rock = Rocks.children[i];
|
|
rock.position += rock.vector;
|
|
if (rock.bounds.intersects(this.item.bounds))
|
|
crashRock = rock;
|
|
keepInView(rock);
|
|
}
|
|
|
|
if (this.dying) {
|
|
var children = this.destroyedShip.children;
|
|
children[0].position.x++;
|
|
children[1].position.x--;
|
|
children[2].position.x--;
|
|
children[2].position.y++;
|
|
children[0].rotate(1);
|
|
children[1].rotate(-1);
|
|
children[2].rotate(1);
|
|
this.destroyedShip.opacity *= 0.98;
|
|
|
|
// don't update anything else if the ship is already dead.
|
|
return;
|
|
}
|
|
|
|
|
|
// if bounding rect collision, do a line intersection test
|
|
if (crashRock) {
|
|
var tempRock = crashRock.symbol.definition.clone();
|
|
tempRock.transform(crashRock.matrix);
|
|
tempRock.remove();
|
|
var intersections = this.item.firstChild.getIntersections(tempRock);
|
|
if (intersections.length > 0)
|
|
Lives.remove();
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
var Bullets = new function() {
|
|
var group = new Group();
|
|
var children = group.children;
|
|
|
|
function checkHits(bullet) {
|
|
for (var r = 0; r < Rocks.children.length; r++) {
|
|
var rock = Rocks.children[r];
|
|
if (rock.bounds.contains(bullet.position) ) {
|
|
Score.update(rock.shapeType);
|
|
Rocks.explode(rock);
|
|
if (rock.shapeType < Rocks.TYPE_SMALL ) {
|
|
for (var j = 0; j < 2; j++) {
|
|
Rocks.add(1, rock.shapeType + 4, rock.position);
|
|
}
|
|
}
|
|
rock.remove();
|
|
bullet.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
fire: function(position, angle) {
|
|
// We can only fire 5 bullets at a time:
|
|
if (children.length == 5)
|
|
return;
|
|
var vector = new Point({
|
|
angle: angle,
|
|
length: 10
|
|
});
|
|
var bullet = new Path.Circle({
|
|
center: position + vector,
|
|
radius: 0.5,
|
|
parent: group,
|
|
fillColor: 'white',
|
|
strokeWidth: 'white',
|
|
strokeWidth: 0,
|
|
data: {
|
|
vector: vector,
|
|
timeToDie: 58
|
|
}
|
|
});
|
|
},
|
|
move: function() {
|
|
for (var i = 0; i < children.length; i++) {
|
|
var bullet = children[i];
|
|
bullet.data.timeToDie--;
|
|
if (bullet.data.timeToDie < 1) {
|
|
bullet.remove();
|
|
} else {
|
|
bullet.position += bullet.data.vector;
|
|
checkHits(bullet);
|
|
keepInView(bullet);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
var Rocks = new function() {
|
|
var group = new Group();
|
|
var shapes = [
|
|
new Path(
|
|
[-23, -40.5], [0, -30.5], [24, -40.5], [45, -21.5], [25, -12.5],
|
|
[46, 9.5], [22, 38.5], [-10, 30.5], [-22, 40.5], [-46, 18.5],
|
|
[-33, 0.5], [-44, -21.5], [-23, -40.5]),
|
|
new Path(
|
|
[-45, -9.5], [-12, -40.5], [24, -40.5], [46, -11.5], [45, 10.5],
|
|
[24, 40.5], [0, 40.5], [0, 10.5], [-23, 38.5], [-46, 9.5], [-25, 0.5],
|
|
[-45, -9.5]),
|
|
new Path([-21.5, -39.5], [11.5, -39.5], [45.5, -20.5],
|
|
[45.5, -8.5], [9.5, 0.5], [44.5, 21.5], [22.5, 39.5], [9.5, 31.5],
|
|
[-20.5, 39.5], [-45.5, 10.5], [-45.5, -20.5], [-11.5, -21.5],
|
|
[-21.5, -39.5]),
|
|
new Path(
|
|
[-22.5, -40.5], [-0.5, -19.5], [23.5, -39.5], [44.5, -21.5],
|
|
[33.5, 0.5], [46.5, 19.5], [13.5, 40.5], [-22.5, 39.5], [-46.5, 18.5],
|
|
[-46.5, -18.5], [-22.5, -40.5])
|
|
];
|
|
|
|
// medium rocks
|
|
for (var i = 4; i < 8; i++) {
|
|
shapes[i] = shapes[i - 4].clone();
|
|
shapes[i].scale(0.5);
|
|
}
|
|
|
|
// small rocks
|
|
for (var i = 8; i < 12; i++) {
|
|
shapes[i] = shapes[i - 4].clone();
|
|
shapes[i].scale(0.4);
|
|
}
|
|
|
|
var rockSymbols = [];
|
|
for (var i = 0; i < shapes.length; i++) {
|
|
rockSymbols[i] = new SymbolDefinition(shapes[i]);
|
|
}
|
|
|
|
var explosions = new Group();
|
|
|
|
return {
|
|
shapes: shapes,
|
|
children: group.children,
|
|
make: function(type, pos) {
|
|
var randomRock = type + Math.floor(4 * Math.random());
|
|
var rock = rockSymbols[randomRock].place();
|
|
rock.position = pos ? pos : Point.random() * view.size;
|
|
rock.vector = new Point({
|
|
angle: 360 * Math.random(),
|
|
length: presets.maxRockSpeed * Math.random() + 0.1
|
|
});
|
|
rock.shapeType = type;
|
|
return rock;
|
|
},
|
|
add: function(amount, type, position) {
|
|
for (var i = 0; i < amount; i++) {
|
|
var rock = this.make(type || this.TYPE_BIG, position);
|
|
group.addChild(rock);
|
|
}
|
|
},
|
|
explode: function(rock) {
|
|
var boomRock = rock.symbol.definition.clone();
|
|
boomRock.position = rock.position;
|
|
for (var i = 0; i < boomRock.segments.length; i++) {
|
|
var segmentPoint = boomRock.segments[i].point;
|
|
var placed = assets.explosion.place(segmentPoint);
|
|
placed.vector = (placed.position - rock.position) * 0.1;
|
|
explosions.addChild(placed);
|
|
}
|
|
boomRock.remove();
|
|
},
|
|
iterateExplosions: function() {
|
|
for (var i = 0; i < explosions.children.length; i++) {
|
|
var explosion = explosions.children[i];
|
|
explosion.vector.length *= .7;
|
|
explosion.position += explosion.vector;
|
|
explosion.opacity = explosion.vector.length;
|
|
if (explosion.vector.length < 0.05 ) {
|
|
explosion.remove();
|
|
// if no more rocks, wait a second and start a new round
|
|
if (this.children.length < 1 && !Game.roundDelay) {
|
|
Game.roundDelay = true;
|
|
presets.rockCount += 2;
|
|
setTimeout(Game.newRound, 1000);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
TYPE_BIG: 0,
|
|
TYPE_MEDIUM: 4,
|
|
TYPE_SMALL: 8
|
|
};
|
|
};
|
|
|
|
var Score = new function() {
|
|
var numberGroup = new Group(
|
|
new Path([0, 0], [20, 0], [20, 27], [0, 27], [0, 0]),
|
|
new Path([10, 0], [10, 27]),
|
|
new Path([0, 0], [20, 0], [20, 14], [0, 14], [0, 27], [20, 27]),
|
|
new Path([0, 0], [20, 0], [20, 14], [0, 14], [20, 14], [20, 27], [0, 27]),
|
|
new Path([0, 0], [0, 14], [20, 14], [20, 0], [20, 27]),
|
|
new Path([20, 0], [0, 0], [0, 14], [20, 14], [20, 27], [0, 27]),
|
|
new Path([20, 0], [0, 0], [0, 27], [20, 27], [20, 14], [0, 14]),
|
|
new Path([0, 0], [20, 0], [0, 27]),
|
|
new Path([0, 0], [20, 0], [20, 27], [0, 27], [0, 0], [0, 14], [20, 14]),
|
|
new Path([20, 14], [0, 14], [0, 0], [20, 0], [20, 27])
|
|
);
|
|
numberGroup.visible = false;
|
|
var scoreDisplay = new Group();
|
|
var score = 0;
|
|
return {
|
|
update: function(type) {
|
|
if (type == Rocks.TYPE_BIG) score += 20;
|
|
if (type == Rocks.TYPE_MEDIUM) score += 50;
|
|
if (type == Rocks.TYPE_SMALL) score += 100;
|
|
if (score >= presets.freeShipScore) {
|
|
Lives.add(1);
|
|
presets.freeShipScore += presets.freeShipIncrement;
|
|
}
|
|
scoreDisplay.removeChildren();
|
|
|
|
var scoreString = score + '';
|
|
for (var i = 0; i < scoreString.length; i++) {
|
|
var n = parseInt(scoreString[i], 10);
|
|
scoreDisplay.addChild(numberGroup.children[n].clone());
|
|
scoreDisplay.lastChild.position = [22 + i * 24, 22];
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
var Lives = new function() {
|
|
var currentLives;
|
|
var shipPath = Ship.item.firstChild.clone();
|
|
project.activeLayer.addChild(shipPath);
|
|
shipPath.visible = false;
|
|
Ship.visible = false;
|
|
var group = new Group();
|
|
return {
|
|
initialize: function() {
|
|
currentLives = presets.lives;
|
|
this.display();
|
|
},
|
|
add: function() {
|
|
currentLives++;
|
|
this.display();
|
|
},
|
|
remove: function() {
|
|
currentLives--;
|
|
this.display();
|
|
Ship.destroy();
|
|
setTimeout(function() {
|
|
if (currentLives == 0) {
|
|
Game.over();
|
|
} else {
|
|
Ship.destroyed();
|
|
}
|
|
}, 1200);
|
|
},
|
|
display: function() {
|
|
group.removeChildren();
|
|
for (var i = 0;i < currentLives - 1; i++) {
|
|
var copy = shipPath.clone();
|
|
copy.rotate(-90);
|
|
copy.visible = true;
|
|
group.addChild(copy);
|
|
copy.position = [22+ i * copy.bounds.width, 53];
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
initialize();
|
|
|
|
// Stop left and right keyboard events from propagating.
|
|
function onKeyDown(event) {
|
|
if (event.key == 'left' || event.key == 'right') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function keepInView(item) {
|
|
var position = item.position;
|
|
var itemBounds = item.bounds;
|
|
var bounds = view.bounds;
|
|
|
|
if (itemBounds.left > bounds.width) {
|
|
position.x = -item.bounds.width;
|
|
}
|
|
|
|
if (position.x < -itemBounds.width) {
|
|
position.x = bounds.width;
|
|
}
|
|
|
|
if (itemBounds.top > view.size.height) {
|
|
position.y = -itemBounds.height;
|
|
}
|
|
|
|
if (position.y < -itemBounds.height) {
|
|
position.y = bounds.height + itemBounds.height / 2;
|
|
}
|
|
}
|
|
</script>
|
|
<style type="text/css">
|
|
html,
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
height: 100%;
|
|
}
|
|
|
|
canvas {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
body {
|
|
background: #000;
|
|
font-family:Helvetica,Arial;
|
|
}
|
|
|
|
.footer {
|
|
position:absolute;
|
|
bottom:0;
|
|
color:#ff0000;
|
|
background-color: rgba(60,60,60,0.8);
|
|
font-size:0.75em;
|
|
padding:0.5em;
|
|
color:#ddd;
|
|
width: 100%
|
|
}
|
|
|
|
.footer a {
|
|
color: #fff;
|
|
font-weight:
|
|
bold; text-decoration: none;
|
|
border-bottom: 1px solid #555;
|
|
}
|
|
|
|
.footer b {
|
|
background-color: #660000;
|
|
padding-left: 0.25em;
|
|
padding-right: 0.25em;
|
|
}
|
|
|
|
.gameover {
|
|
display: none;
|
|
position: absolute;
|
|
left: 40%;
|
|
top: 40%;
|
|
color: #fff;
|
|
background-color: rgba(60,60,60,0.8);
|
|
padding: 32px;
|
|
-moz-border-radius: 12px;
|
|
-webkit-border-radius: 12px;
|
|
border-radius: 12px;
|
|
-moz-background-clip: padding;
|
|
-webkit-background-clip: padding-box;
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
.gameover a {
|
|
color: #fff;
|
|
font-weight: bold;
|
|
}
|
|
|
|
#stats {
|
|
position: absolute;
|
|
left: auto !important;
|
|
right: 0px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="canvas" resize stats></canvas>
|
|
<div id="footer" class="footer"><a href=#">Paperoids</a>. To Play: <b>←</b> and <b>→</b> to rotate. <b>↑</b> for thrust. <b>Z</b> to fire. <b>F</b> to show FPS. Follow @<a href="http://twitter.com/#/hirmes">hirmes</a> for updates. Made with the amazing <a href="http://paperjs.org">Paper.js</a></div>
|
|
|
|
<div id="gameover" class="gameover">Game Over. <a href="Paperoids.html">Play again</a>?</div>
|
|
</body>
|
|
</html>
|