├── .gitignore
├── README.md
├── audio
├── explosion-0.mp3
├── explosion-1.mp3
├── explosion-2.mp3
├── pew-0.mp3
├── pew-1.mp3
└── pew-2.mp3
├── collisionhelpers.js
├── docs
├── README.md
├── asteroid.js
├── audio
│ ├── explosion-0.mp3
│ ├── explosion-1.mp3
│ ├── explosion-2.mp3
│ └── pew.mp3
├── entity.js
├── hud.js
├── index.html
├── input.js
├── laser.js
├── libraries
│ ├── p5.dom.js
│ ├── p5.js
│ └── p5.sound.js
├── ship.js
└── sketch.js
├── entities
├── asteroid.js
├── entity.js
├── laser.js
└── ship.js
├── index.html
├── input.js
├── libraries
├── p5.dom.js
├── p5.js
└── p5.sound.js
├── managers
├── entitymanager.js
├── levelmanager.js
└── uimanager.js
├── player.js
├── shape.js
├── sketch.js
├── ui
├── hud.js
├── playercontrols.js
└── uielement.js
└── world.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Asteroids
2 |
3 | [](https://gitter.im/CodingRainbow/Asteroids?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 |
5 | This repo is for the Asteroids project from Coding Challenge 45.
6 |
7 | ### Video Tutorials
8 | 1. Part 1: https://youtu.be/hacZU523FyM
9 | 2. Part 2: https://youtu.be/xTTuih7P0c0
10 |
11 | ### Play the game!
12 | * https://CodingTrain.github.io/asteroids-advanced/
13 |
14 | ### Info
15 |
16 | The code here is an updated and improved version of the game based on viewer submissions via pull requests. The original code from the challenge itself can be found here:
17 |
18 | https://github.com/CodingRainbow/Rainbow-Code/tree/master/challenges/CC_46_Asteroids
19 |
20 | To submit a new issue, [file an issue describing your idea](https://github.com/CodingRainbow/Asteroids/issues/new) and [make a pull request](https://github.com/CodingRainbow/Asteroids/pulls)! If you make your own version of the game in a separate repo or fork, pull request this README file to add a link.
21 |
22 | I'll make a video about how to contribute soon too!
23 |
24 | ### Viewer Remixes of the Game
25 |
26 | * Add your name and link here!
27 | * Serge Korzh - https://github.com/qw8/Asteroids-1
28 | * Timo Köhler - https://navetk.github.io/Asteroids/
29 |
--------------------------------------------------------------------------------
/audio/explosion-0.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/audio/explosion-0.mp3
--------------------------------------------------------------------------------
/audio/explosion-1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/audio/explosion-1.mp3
--------------------------------------------------------------------------------
/audio/explosion-2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/audio/explosion-2.mp3
--------------------------------------------------------------------------------
/audio/pew-0.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/audio/pew-0.mp3
--------------------------------------------------------------------------------
/audio/pew-1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/audio/pew-1.mp3
--------------------------------------------------------------------------------
/audio/pew-2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/audio/pew-2.mp3
--------------------------------------------------------------------------------
/collisionhelpers.js:
--------------------------------------------------------------------------------
1 | function cross(v1, v2) {
2 | return v1.x * v2.y - v2.x * v1.y;
3 | }
4 |
5 | function lineIntersectCircle(l1, l2, c, r) {
6 | var dx = l2.x - l1.x;
7 | var dy = l2.y - l1.y;
8 | var dx2 = c.x - l1.x;
9 | var dy2 = c.y - l1.y;
10 | var x = dx * dx + dy * dy;
11 | var a = pow(dx2 * dy - dy2 * dx, 2) / x;
12 | return a <= r * r;
13 | }
14 |
15 | //intersection between two closed of lines
16 | function lineIntersect(l1v1, l1v2, l2v1, l2v2) {
17 | var base = p5.Vector.sub(l1v1, l2v1);
18 | var l1_vector = p5.Vector.sub(l1v2, l1v1);
19 | var l2_vector = p5.Vector.sub(l2v2, l2v1);
20 | var direction_cross = cross(l2_vector, l1_vector);
21 | var t = cross(base, l1_vector) / direction_cross;
22 | var u = cross(base, l2_vector) / direction_cross;
23 | if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
24 | return true;
25 | } else {
26 | return false;
27 | }
28 | }
29 |
30 | //intersection between one infinite line determined by point and angle and a closed of line
31 | function lineIntersect2(l1v1, angle, l2v1, l2v2) {
32 | var base = p5.Vector.sub(l1v1, l2v1);
33 | var l1_vector = p5.Vector.fromAngle(angle);
34 | var l2_vector = p5.Vector.sub(l2v2, l2v1);
35 | var direction_cross = cross(l2_vector, l1_vector);
36 | var t = cross(base, l1_vector) / direction_cross;
37 | if (t >= 0 && t <= 1) {
38 | return true;
39 | } else {
40 | return false;
41 | }
42 | }
43 |
44 | //intersection point between two infinite lines determined by two points each
45 | function lineIntersectionPoint(p1, p2, p3, p4) {
46 | var d = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
47 | if (d === 0) {
48 | return undefined;
49 | }
50 | var px = (cross(p1, p2) * (p3.x - p4.x) - (p1.x - p2.x) * cross(p3, p4)) / d;
51 | var py = (cross(p1, p2) * (p3.y - p4.y) - (p1.y - p2.y) * cross(p3, p4)) / d;
52 | return createVector(px, py);
53 | }
54 |
55 | //modulo that works with negative numbers because JS is stupid
56 | function mod(n, m) {
57 | return ((n % m) + m) % m;
58 | }
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Asteroids
2 |
3 | [](https://gitter.im/CodingRainbow/Asteroids?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 |
5 | This repo is for the Asteroids project from Coding Challenge 45.
6 |
7 | ### Video Tutorials
8 | 1. Part 1: https://youtu.be/hacZU523FyM
9 | 2. Part 2: https://youtu.be/xTTuih7P0c0
10 | 3. Stay tuned for Siraj's tutorial!
11 |
12 | ### Play the game!
13 | * https://codingrainbow.github.io/asteroids-advanced/
14 |
15 | ### Info
16 |
17 | You are required to read and acknowledge [Issue #1](https://github.com/CodingRainbow/asteroids-advanced/issues/1). More info will be posted here soon.
18 |
19 | ### Viewer Remixes of the Game
20 |
21 | * Add your name and link here!
22 | * Serge Korzh - https://github.com/qw8/Asteroids-1
23 | * Timo Köhler - https://navetk.github.io/Asteroids/
24 |
--------------------------------------------------------------------------------
/docs/asteroid.js:
--------------------------------------------------------------------------------
1 | // Daniel Shiffman
2 | // http://codingrainbow.com
3 | // http://patreon.com/codingrainbow
4 | // Code for: https://youtu.be/hacZU523FyM
5 |
6 | function Asteroid(pos, r, size) {
7 | if (pos == null) {
8 | pos = createVector(random(width), random(height));
9 | }
10 |
11 | r = r != null ? r * 0.5 : random(40, 60);
12 | Entity.call(this, pos.x, pos.y, r);
13 |
14 | this.vel = p5.Vector.random2D();
15 | this.total = floor(random(7, 15));
16 |
17 | //smaller asteroids go a bit faster
18 | this.size = size;
19 | switch(size) {
20 | case 1:
21 | this.vel.mult(1.5); break;
22 | case 0:
23 | this.vel.mult(2); break;
24 | }
25 |
26 |
27 | this.offset = [];
28 | for (var i = 0; i < this.total; i++) {
29 | this.offset[i] = random(-this.r * 0.2, this.r * 0.5);
30 | }
31 |
32 | Entity.prototype.setRotation.call(this, random(-0.03, 0.03));
33 |
34 | this.render = function() {
35 | push();
36 | stroke(255);
37 | noFill();
38 | translate(this.pos.x, this.pos.y);
39 | rotate(this.heading);
40 | beginShape();
41 | for (var i = 0; i < this.total; i++) {
42 | var angle = map(i, 0, this.total, 0, TWO_PI);
43 | var r = this.r + this.offset[i];
44 | vertex(r * cos(angle), r * sin(angle));
45 | }
46 | endShape(CLOSE);
47 | pop();
48 | }
49 |
50 | this.playSoundEffect = function(soundArray){
51 | soundArray[floor(random(0,soundArray.length))].play();
52 | }
53 |
54 | this.breakup = function() {
55 | if(size > 0)
56 | return [new Asteroid(this.pos, this.r, this.size-1), new Asteroid(this.pos, this.r, this.size-1)];
57 | else
58 | return [];
59 | }
60 |
61 | this.vertices = function() {
62 | var vertices = []
63 | for(var i = 0; i < this.total; i++) {
64 | var angle = this.heading + map(i, 0, this.total, 0, TWO_PI);
65 | var r = this.r + this.offset[i];
66 | vertices.push(p5.Vector.add(createVector(r * cos(angle), r * sin(angle)), this.pos));
67 | }
68 |
69 | return vertices;
70 | }
71 | }
72 |
73 | Asteroid.prototype = Object.create(Entity.prototype);
74 |
--------------------------------------------------------------------------------
/docs/audio/explosion-0.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/docs/audio/explosion-0.mp3
--------------------------------------------------------------------------------
/docs/audio/explosion-1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/docs/audio/explosion-1.mp3
--------------------------------------------------------------------------------
/docs/audio/explosion-2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/docs/audio/explosion-2.mp3
--------------------------------------------------------------------------------
/docs/audio/pew.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodingTrain/asteroids-advanced/f1b08e345b2381f85bc1114361bd44780924d268/docs/audio/pew.mp3
--------------------------------------------------------------------------------
/docs/entity.js:
--------------------------------------------------------------------------------
1 | function Entity(x, y, radius)
2 | {
3 | this.pos = createVector(x, y);
4 | this.r = radius;
5 | this.heading = 0;
6 | this.rotation = 0;
7 | this.vel = createVector(0, 0);
8 | this.accelMagnitude = 0;
9 | }
10 |
11 | Entity.prototype.update = function() {
12 | this.heading += this.rotation;
13 |
14 | // Accelerate using the heading and the accelMagnitude
15 | var force = p5.Vector.fromAngle(this.heading);
16 | force.mult(this.accelMagnitude);
17 | this.vel.add(force);
18 |
19 | this.pos.add(this.vel);
20 | this.edges();
21 | }
22 |
23 | Entity.prototype.setAccel = function(magnitude)
24 | {
25 | this.accelMagnitude = magnitude;
26 | }
27 |
28 | Entity.prototype.edges = function() {
29 | if (this.pos.x > width + this.r) {
30 | this.pos.x = -this.r;
31 | } else if (this.pos.x < -this.r) {
32 | this.pos.x = width + this.r;
33 | }
34 | if (this.pos.y > height + this.r) {
35 | this.pos.y = -this.r;
36 | } else if (this.pos.y < -this.r) {
37 | this.pos.y = height + this.r;
38 | }
39 | }
40 |
41 | Entity.prototype.setRotation = function(rot) {
42 | this.rotation = rot;
43 | }
44 |
--------------------------------------------------------------------------------
/docs/hud.js:
--------------------------------------------------------------------------------
1 | function Hud() {
2 | var size = 20;
3 | var padding = 10;
4 | var lifeWidth = 20;
5 |
6 | /*
7 | --0--
8 | 1 2
9 | --3--
10 | 4 5
11 | --6--
12 | */
13 | var digitMaps = [
14 | //return a digit map
15 | [true, true, true, false, true, true, true], //0
16 | [false, false, true, false, false, true, false], //1
17 | [true, false, true, true, true, false, true], //2
18 | [true, false, true, true, false, true, true], //3
19 | [false, true, true, true, false, true, false], //4
20 | [true, true, false, true, false, true, true], //5
21 | [true, true, false, true, true, true, true], //6
22 | [true, false, true, false, false, true, false], //7
23 | [true, true, true, true, true, true, true], //8
24 | [true, true, true, true, false, true, true] //9
25 |
26 | ];
27 |
28 | this.render = function() {
29 | var scoreString = "" + score;
30 | var digitPos = createVector((width / 2 - (scoreString.length * (size + padding)) / 2), padding);
31 | for(var i = 0; i < scoreString.length; i++) {
32 | var dmap = digitMaps[scoreString.charAt(i)];
33 | drawDigit(dmap, i, digitPos);
34 | digitPos.x += size + padding;
35 | }
36 | drawLives();
37 | if(lives < 0) {
38 | push();
39 | textSize(32);
40 | fill(255);
41 | text("GAME OVER", (width / 2) - 100, height / 2);
42 | }
43 | }
44 |
45 | function drawLives() {
46 | push();
47 | stroke(255);
48 | fill(0);
49 | var top = createVector((width / 2) + lifeWidth * 2, padding * 2 + size * 2);
50 | for(var i = 0; i < lives; i++) {
51 | triangle(top.x, top.y, top.x - lifeWidth / 2, top.y + 25, top.x + lifeWidth / 2, top.y + 25);
52 | top.x -= 20 + padding;
53 | }
54 | pop();
55 | }
56 |
57 | //draws the digit based on the digit map
58 | function drawDigit(digitMap, index, pos) {
59 | push();
60 | stroke(255);
61 | for(var i = 0; i < digitMap.length; i++) {
62 | if(digitMap[i] === true)
63 | drawLine(i, pos);
64 | }
65 | pop();
66 | }
67 |
68 | //draws a line based on the line map
69 | function drawLine(lineMap, pos) {
70 | switch(lineMap) {
71 | case 0:
72 | line(pos.x, pos.y, pos.x + size, pos.y);
73 | break;
74 | case 1:
75 | line(pos.x, pos.y, pos.x, pos.y + size);
76 | break;
77 | case 2:
78 | line(pos.x + size, pos.y, pos.x + size, pos.y + size);
79 | break;
80 | case 3:
81 | line(pos.x, pos.y + size, pos.x + size, pos.y + size);
82 | break;
83 | case 4:
84 | line(pos.x, pos.y + size, pos.x, pos.y + 2 * size);
85 | break;
86 | case 5:
87 | line(pos.x + size, pos.y + size, pos.x + size, pos.y + 2 * size);
88 | break;
89 | case 6:
90 | line(pos.x, pos.y + size * 2, pos.x + size, pos.y + 2 * size);
91 | break;
92 | default:
93 | console.log("line map is invalid");
94 | break;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Community Asteroids! - Coding Rainbow
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/docs/input.js:
--------------------------------------------------------------------------------
1 | var input = {
2 | listeners: {},
3 | reset: function() {
4 | this.listeners = {};
5 | },
6 | registerAsListener: function(index, callback) {
7 | if (this.listeners[index] == undefined) {
8 | this.listeners[index] = [];
9 | }
10 |
11 | this.listeners[index].push(callback);
12 | },
13 | handleEvent: function(char, code, press) {
14 | if (this.listeners[code] != undefined) {
15 | for (var i = 0; i < this.listeners[code].length; i++) {
16 | this.listeners[code][i](char, code, press);
17 | }
18 | }
19 | }
20 | };
21 |
22 | function keyReleased() {
23 | input.handleEvent(key, keyCode, false);
24 | }
25 |
26 | function keyPressed() {
27 | input.handleEvent(key, keyCode, true);
28 | }
29 |
--------------------------------------------------------------------------------
/docs/laser.js:
--------------------------------------------------------------------------------
1 | // Daniel Shiffman
2 | // http://codingrainbow.com
3 | // http://patreon.com/codingrainbow
4 | // Code for: https://youtu.be/hacZU523FyM
5 |
6 | var colors = [[248, 12, 18], [238, 17, 0], [255, 51, 17], [255, 68, 34], [255, 102, 68], [255, 153, 51], [254, 174, 45], [204, 187, 51], [208, 195, 16], [170, 204, 34], [105, 208, 37], [34, 204, 170], [18, 189, 185], [17, 170, 187], [68, 68, 221], [51, 17, 187], [59, 12, 189], [68, 34, 153]]
7 |
8 | function Laser(spos, angle) {
9 | Entity.call(this, spos.x, spos.y, 4);
10 |
11 | this.pos = createVector(spos.x, spos.y);
12 | this.vel = p5.Vector.fromAngle(angle);
13 | this.vel.mult(10);
14 | this.color = colors[floor(random(0,colors.length-1))];
15 |
16 | this.render = function() {
17 | push();
18 | stroke(this.color[0], this.color[1], this.color[2]);
19 | strokeWeight(this.r);
20 | point(this.pos.x, this.pos.y);
21 | pop();
22 | }
23 |
24 | this.playSoundEffect = function(sound){
25 | if (!sound.isPlaying()){
26 | sound.play();
27 | }
28 | }
29 |
30 | this.hits = function(asteroid) {
31 | var last_pos = p5.Vector.sub(this.pos, this.vel);
32 | var asteroid_vertices = asteroid.vertices();
33 | for(var i = 0; i < asteroid_vertices.length - 1; i++) {
34 | if(lineIntersect(last_pos, this.pos, asteroid_vertices[i], asteroid_vertices[i + 1])) {
35 | return true;
36 | }
37 | }
38 | return false;
39 | }
40 |
41 | this.offscreen = function() {
42 | if (this.pos.x > width || this.pos.x < 0) {
43 | return true;
44 | }
45 | if (this.pos.y > height || this.pos.y < 0) {
46 | return true;
47 | }
48 | return false;
49 | }
50 |
51 |
52 | }
53 |
54 | Laser.prototype = Object.create(Entity.prototype);
55 |
--------------------------------------------------------------------------------
/docs/ship.js:
--------------------------------------------------------------------------------
1 | // Daniel Shiffman
2 | // http://codingrainbow.com
3 | // http://patreon.com/codingrainbow
4 | // Code for: https://youtu.be/hacZU523FyM
5 |
6 | function Ship(pos, r) {
7 | Entity.call(this, width / 2, height / 2, 20);
8 | this.isDestroyed = false;
9 | this.destroyFrames = 600;
10 | this.shields = shieldTime;
11 |
12 | var scope = this;
13 | input.registerAsListener(" ".charCodeAt(0), function(char, code, press) {
14 | if (!press) {
15 | return;
16 | }
17 |
18 | var laser = new Laser(scope.pos, scope.heading);
19 | laser.playSoundEffect(laserSoundEffect);
20 | lasers.push(laser);
21 | });
22 | input.registerAsListener(RIGHT_ARROW, function(char, code, press) { scope.setRotation(press ? 0.08 : 0); });
23 | input.registerAsListener(LEFT_ARROW, function(char, code, press) { scope.setRotation(press ? -0.08 : 0); });
24 | input.registerAsListener(UP_ARROW, function(char, code, press) { scope.setAccel(press ? 0.1 : 0); });
25 |
26 | this.update = function() {
27 | Entity.prototype.update.call(this);
28 | this.vel.mult(0.99);
29 | if(this.isDestroyed) {
30 | for(var i = 0; i < this.brokenParts.length; i++) {
31 | this.brokenParts[i].pos.add(this.brokenParts[i].vel);
32 | this.brokenParts[i].heading += this.brokenParts[i].rot;
33 | }
34 | } else {
35 | this.vel.mult(0.99);
36 | }
37 | if (this.shields > 0) {
38 | this.shields -= 1;
39 | }
40 | }
41 |
42 | this.brokenParts = [];
43 | this.destroy = function() {
44 | this.isDestroyed = true;
45 | for(var i = 0; i < 4; i++)
46 | this.brokenParts[i] = {
47 | pos: this.pos.copy(),
48 | vel: p5.Vector.random2D(),
49 | heading: random(0, 360),
50 | rot: random(-0.07, 0.07)
51 | };
52 | }
53 |
54 | this.hits = function(asteroid) {
55 | if (this.shields > 0) {
56 | return false;
57 | }
58 | var vertices = [
59 | createVector(this.pos.x - 2/3 * this.r, this.pos.y - this.r),
60 | createVector(this.pos.x - 2/3 * this.r, this.pos.y + this.r),
61 | createVector(this.pos.x + 4/3 * this.r, this.pos.y + 0)
62 | ];
63 | var asteroid_vertices = asteroid.vertices();
64 | for(var i = 0; i < asteroid_vertices.length; i++) {
65 | for(var j = 0; j < vertices.length; j++) {
66 | var opposite = vertices.slice(0);
67 | opposite.splice(j, 1);
68 | if(lineIntersect(opposite[0], opposite[1], asteroid_vertices[i], asteroid_vertices[(i + 1) % asteroid_vertices.length])) {
69 | return true;
70 | }
71 | }
72 | }
73 | return false;
74 | }
75 |
76 | this.render = function() {
77 | if(this.isDestroyed) {
78 | for(var i = 0; i < this.brokenParts.length; i++) {
79 | push();
80 | stroke(floor(255 * ((this.destroyFrames--) / 600)));
81 | var bp = this.brokenParts[i];
82 | translate(bp.pos.x, bp.pos.y);
83 | rotate(bp.heading);
84 | line(-this.r / 2, -this.r / 2, this.r / 2, this.r / 2);
85 | pop();
86 | }
87 | } else {
88 | push();
89 | translate(this.pos.x, this.pos.y);
90 | rotate(this.heading);
91 | fill(0);
92 | var shieldCol = random(map(this.shields, 0, shieldTime, 255, 0), 255);
93 | stroke(shieldCol, shieldCol, 255);
94 | triangle(-2/3*this.r, -this.r, -2/3*this.r, this.r, 4/3*this.r, 0);
95 |
96 | if(this.accelMagnitude != 0) {
97 | translate(-this.r, 0);
98 | rotate(random(PI / 4, 3 * PI / 4));
99 | line(0, 0, 0, 10);
100 | }
101 |
102 | pop();
103 | }
104 | }
105 | }
106 |
107 | Ship.prototype = Object.create(Entity.prototype);
--------------------------------------------------------------------------------
/docs/sketch.js:
--------------------------------------------------------------------------------
1 | // Daniel Shiffman
2 | // http://codingrainbow.com
3 | // http://patreon.com/codingrainbow
4 | // Code for: https://youtu.be/hacZU523FyM
5 |
6 | var ship;
7 | var hud;
8 | var asteroids = [];
9 | var lasers = [];
10 | var laserSoundEffect;
11 | var explosionSoundEffects = [];
12 | var canPlay = true;
13 | var shieldTime = 180;
14 |
15 | function preload() {
16 | laserSoundEffect = loadSound('audio/pew.mp3');
17 | for (var i =0; i < 3; i++){
18 | explosionSoundEffects[i] = loadSound('audio/explosion-'+i+'.mp3');
19 | }
20 | }
21 | var score = 0;
22 | var lives = 3;
23 | var points = [100, 50, 20]; // small, med, large points
24 | var level = 0;
25 |
26 | function setup() {
27 | createCanvas(windowWidth, windowHeight);
28 | ship = new Ship();
29 | hud = new Hud();
30 | spawnAsteroids();
31 | }
32 |
33 | function draw() {
34 | for(var i = 0; i < asteroids.length; i++) {
35 | if(ship.hits(asteroids[i]) && canPlay) {
36 | canPlay = false;
37 | ship.destroy();
38 | input.reset();
39 | setTimeout(function() {
40 | lives--;
41 | if(lives >= 0) {
42 | ship = new Ship();
43 | canPlay = true;
44 | }
45 | }, 3000);
46 | }
47 | asteroids[i].update();
48 | }
49 |
50 | for(var i = lasers.length - 1; i >= 0; i--) {
51 | lasers[i].update();
52 | if(lasers[i].offscreen()) {
53 | lasers.splice(i, 1);
54 |
55 | continue;
56 | }
57 |
58 | for (var j = asteroids.length - 1; j >= 0; j--) {
59 | if (lasers[i].hits(asteroids[j])) {
60 | asteroids[j].playSoundEffect(explosionSoundEffects);
61 | score += points[asteroids[j].size];
62 | var newAsteroids = asteroids[j].breakup();
63 | asteroids = asteroids.concat(newAsteroids);
64 | asteroids.splice(j, 1);
65 | lasers.splice(i, 1);
66 | if(asteroids.length == 0) {
67 | level++;
68 | spawnAsteroids();
69 | ship.shields = shieldTime;
70 | }
71 | break;
72 | }
73 | }
74 | }
75 |
76 | ship.update();
77 |
78 | // Render
79 | background(0);
80 |
81 | for (var i = 0; i < asteroids.length; i++) {
82 | asteroids[i].render();
83 | }
84 |
85 | for (var i = lasers.length - 1; i >= 0; i--) {
86 | lasers[i].render();
87 | }
88 |
89 | ship.render();
90 | hud.render();
91 | }
92 |
93 | function spawnAsteroids() {
94 | for(var i = 0; i < level + 5; i++) {
95 | asteroids.push(new Asteroid(null, null, 2));
96 | }
97 | }
98 |
99 | function cross(v1, v2) {
100 | return v1.x * v2.y - v2.x * v1.y;
101 | }
102 |
103 | function lineIntersect(l1v1, l1v2, l2v1, l2v2) {
104 | var base = p5.Vector.sub(l1v1, l2v1);
105 | var l1_vector = p5.Vector.sub(l1v2, l1v1);
106 | var l2_vector = p5.Vector.sub(l2v2, l2v1);
107 | var direction_cross = cross(l2_vector, l1_vector);
108 | var t = cross(base, l1_vector) / direction_cross;
109 | var u = cross(base, l2_vector) / direction_cross;
110 | if(t >= 0 && t <= 1 && u >= 0 && u <= 1) {
111 | return true;
112 | } else {
113 | return false;
114 | }
115 | }
--------------------------------------------------------------------------------
/entities/asteroid.js:
--------------------------------------------------------------------------------
1 | // Daniel Shiffman
2 | // http://codingrainbow.com
3 | // http://patreon.com/codingrainbow
4 | // Code for: https://youtu.be/hacZU523FyM
5 |
6 | function Asteroid(world, params) {
7 | var levelmanager = params.levelmanager;
8 | params.pos = params.pos !== undefined ? params.pos : createVector(random(-world.halfwidth, world.halfwidth), random(-world.halfheight, world.halfheight));
9 | params.r = params.r !== undefined ? params.r : random(80, 160);
10 | params.mass = params.mass !== undefined ? params.mass : PI * params.r * params.r;
11 | Entity.call(this, params);
12 | this.vel = params.vel !== undefined ? params.vel : createVector(0, 0);
13 | Entity.prototype.applyForce.call(this, params.force !== undefined ? params.force : p5.Vector.random2D().mult(5000));
14 | Entity.prototype.applyTorque.call(this, random(-0.03, 0.03));
15 | this.heading = params.heading !== undefined ? params.heading : 0;
16 | this.c = params.c !== undefined ? params.c : color(255);
17 | const minArea = 750;
18 |
19 | if (params.shape === undefined) {
20 | vertices = [];
21 | this.total = floor(random(7, 15));
22 | var range = this.r * 0.5;
23 | for (var i = 0; i < this.total; i++) {
24 | var angle = map(i, 0, this.total, 0, TWO_PI);
25 | var r = this.r - random(0, range);
26 | vertices.push(createVector(r * cos(angle), r * sin(angle)));
27 | }
28 | this.shape = new Shape(vertices);
29 | } else {
30 | this.shape = params.shape;
31 | this.total = this.shape.vertices.length;
32 | var offset = this.shape.centroid;
33 | this.pos.add(offset.rotate(this.heading));
34 | this.shape.recenter();
35 | }
36 |
37 | if(params.debris === true) {
38 | this.shape.breakAnime(20);
39 | this.canCollide = false;
40 | this.rotation = 0;
41 | }
42 | else {
43 | levelmanager.recordAsteroidCreation();
44 | }
45 |
46 | this.render = function() {
47 | push();
48 | strokeWeight(3);
49 | colorMode(RGB);
50 | noFill();
51 | if (this.canCollide) {
52 | stroke(255);
53 | } else {
54 | stroke(red(this.c), green(this.c), blue(this.c), this.shape.fade());
55 | }
56 | translate(this.pos.x, this.pos.y);
57 | rotate(this.heading);
58 | if (!this.shape.draw()) this.dead = true;
59 | pop();
60 | }
61 |
62 | this.collides = function(entity) { }
63 |
64 | this.collision = function(entity) {
65 | if (!this.dead && entity.toString() === "[object Laser]") {
66 | playSoundEffect(explosionSoundEffects[floor(random(0, explosionSoundEffects.length))]);
67 |
68 | var destroyedArea = 0;
69 | if (this.shape.area < minArea * 2) {
70 | this.c = entity.c;
71 | this.shape.breakAnime(20);
72 | this.canCollide = false;
73 | this.rotation = 0;
74 | destroyedArea = this.shape.area;
75 | } else {
76 | this.intersectionPoints = this.shape.intersections(p5.Vector.sub(p5.Vector.sub(entity.pos, entity.vel), this.pos).rotate(-this.heading), entity.vel.heading()-this.heading);
77 | if(this.intersectionPoints.length > 0) {
78 | var impact = this.intersectionPoints[0].copy().rotate(this.heading);
79 | impact.add(this.pos);
80 |
81 | vertices = [];
82 | for (var i = 0; i < TWO_PI; i += TWO_PI / 10) {
83 | var r = 30 + random(10);
84 | vertices.push(createVector(r * cos(i), r * sin(i)));
85 | }
86 | crateShape = new Shape(vertices);
87 |
88 | var newShapes = this.shape.sub(this.pos, this.heading, impact, 0, crateShape);
89 |
90 | newShapes = Shape.makeAsteroidSized(newShapes);
91 |
92 | while(newShapes[0].length == 1) {
93 | newShapes[0] = newShapes[0][0].splitAtWeakestPoint();
94 | newShapes = Shape.makeAsteroidSized(newShapes);
95 | }
96 |
97 | var scope = this;
98 | world.addEndFrameTask(function() {
99 | for(var j = 0; j < newShapes[0].length; j++) {
100 | world.createEntity(Asteroid, {
101 | pos: scope.pos.copy(),
102 | r: newShapes[0][j].r,
103 | shape: newShapes[0][j],
104 | vel: scope.vel.copy(),
105 | heading: scope.heading,
106 | force: newShapes[0][j].centroid.copy().mult(50),
107 | levelmanager: levelmanager
108 | });
109 | }
110 | for(j = 0; j < newShapes[1].length; j++) {
111 | world.createEntity(Asteroid, {
112 | pos: scope.pos.copy(),
113 | r: newShapes[1][j].r,
114 | shape: newShapes[1][j],
115 | vel: scope.vel.copy(),
116 | heading: scope.heading,
117 | debris: true,
118 | force: createVector(0, 0),
119 | c: entity.c,
120 | levelmanager: levelmanager
121 | });
122 | }
123 | });
124 |
125 | destroyedArea = this.shape.area;
126 | for( var j = 0; j < newShapes[0].length; j++ ) {
127 | destroyedArea -= newShapes[0][j].area;
128 | }
129 |
130 | this.dead = true;
131 | }
132 | }
133 | levelmanager.recordKill(entity.owner, destroyedArea);
134 | }
135 | }
136 |
137 | this.globalVertices = function() {
138 | return this.shape.globalVertices(this.pos, this.heading);
139 | }
140 |
141 | this.toString = function() {
142 | return "[object Asteroid]";
143 | }
144 | }
145 |
146 |
147 | Asteroid.prototype = Object.create(Entity.prototype);
--------------------------------------------------------------------------------
/entities/entity.js:
--------------------------------------------------------------------------------
1 | function Entity(params) {
2 | this.id = -1;
3 | this.canCollide = true;
4 | this.dead = false;
5 | this.mass = params.mass !== undefined ? params.mass : 1;
6 | this.heading = 0;
7 | this.pos = params.pos !== undefined ? params.pos : createVector(0, 0);
8 | this.r = params.r !== undefined ? params.r : 1;
9 | this.rotation = 0;
10 | this.vel = createVector(0, 0);
11 | this.force = createVector(0, 0);
12 | this.torque = 0;
13 | this.velMu = 0;
14 | this.rotMu = 0;
15 | this.velDrag = 0;
16 | this.rotDrag = 0;
17 | this.owner = params.owner !== undefined ? params.owner : -1;
18 | }
19 |
20 | Entity.prototype.registerId = function(id) {
21 | this.id = id;
22 | }
23 |
24 | Entity.prototype.edges = function() {
25 | if (this.pos.x - this.r > world.halfwidth) {
26 | this.pos.x = this.pos.x % world.halfwidth - world.halfwidth;
27 | } else if (this.pos.x + this.r < -world.halfwidth) {
28 | this.pos.x = this.pos.x % world.halfwidth + world.halfwidth;
29 | }
30 | if (this.pos.y - this.r > world.halfheight) {
31 | this.pos.y = this.pos.y % world.halfheight - world.halfheight;
32 | } else if (this.pos.y + this.r < -world.halfheight) {
33 | this.pos.y = this.pos.y % world.halfheight + world.halfheight;
34 | }
35 | var playerPos = world.getLocalPlayer().getEntity().pos;
36 | var relPos = p5.Vector.sub(this.pos, playerPos);
37 | var halfWinWid = windowWidth / 2;
38 | var halfWinHig = windowHeight / 2
39 | if (relPos.x + world.width - this.r < halfWinWid) this.pos.x += world.width;
40 | else if (relPos.x - world.width + this.r > -halfWinWid) this.pos.x -= world.width;
41 | if (relPos.y + world.height - this.r < halfWinHig) this.pos.y += world.height;
42 | else if (relPos.y - world.height + this.r > -halfWinHig) this.pos.y -= world.height;
43 | }
44 |
45 | Entity.prototype.applyForce = function(force) {
46 | this.force.add(force);
47 | }
48 |
49 | Entity.prototype.applyTorque = function(torque) {
50 | this.torque += torque;
51 | }
52 |
53 | Entity.prototype.predictVelocity = function() {
54 | var accel = this.force.copy().div(this.mass);
55 | return this.vel.copy().add(accel);
56 | }
57 |
58 | Entity.prototype.predictRotation = function() {
59 | var rotAccel = this.torque / this.mass;
60 | return this.rotation + rotAccel;
61 | }
62 |
63 | Entity.prototype.momentum = function() {
64 | var momentum = this.vel.copy();
65 | momentum.mult(this.mass);
66 | return momentum;
67 | }
68 |
69 | const g = 9.81;
70 |
71 | Entity.prototype.calculateMu = function(breakThrough) {
72 | var R = this.mass * g;
73 | return breakThrough / R;
74 | }
75 |
76 | Entity.calculateDragCo = function(maxForce, maxVel) {
77 | return maxForce / (maxVel * maxVel);
78 | }
79 |
80 | Entity.calculateMoment = function(localPoint, force) {
81 | return cross(localPoint, force) / localPoint.mag();
82 | }
83 |
84 | Entity.globalPoint = function(scope, localPoint) {
85 | var point = localPoint.copy();
86 | point.rotate(scope.heading);
87 | point.add(scope.pos);
88 | return point;
89 | }
90 |
91 | Entity.prototype.collides = function(entity) {
92 | if (!(this.canCollide && entity.canCollide)) {
93 | return false;
94 | }
95 |
96 | var dx = this.pos.x - entity.pos.x;
97 | var dy = this.pos.y - entity.pos.y;
98 | var dr = this.r + entity.r;
99 | return dx * dx + dy * dy <= dr * dr;
100 | }
101 |
102 | Entity.prototype.collision = function() {}
103 |
104 | Entity.prototype.update = function() {
105 | if (this.dead) {
106 | return true;
107 | }
108 |
109 | var R = this.mass * g;
110 | if (this.velMu > 0) {
111 | var F = this.velMu * R;
112 | if (this.vel.magSq() > 0) {
113 | var normVel = this.vel.copy().normalize();
114 | var frict = normVel.copy().mult(-F);
115 | this.applyForce(frict);
116 | if (p5.Vector.dot(this.vel, this.predictVelocity()) < 0) {
117 | frict.mult(-1);
118 | var force = normVel;
119 | force.mult(-p5.Vector.dot(normVel, this.force.copy()));
120 | this.applyForce(frict);
121 | this.applyForce(force);
122 | this.vel.mult(0);
123 | }
124 | } else if (this.force.magSq() > F * F) {
125 | this.applyForce(this.force.copy().normalize().mult(-F));
126 | } else {
127 | this.force.mult(0);
128 | }
129 | }
130 |
131 | if (this.rotMu > 0) {
132 | var F = this.rotMu * R;
133 | if (this.rotation != 0) {
134 | this.applyTorque(-F * (this.rotation > 0 ? 1 : -1));
135 | if ((this.rotation > 0) != (this.predictRotation() > 0)) {
136 | this.torque = 0;
137 | this.rotation = 0;
138 | }
139 | } else if (abs(this.torque) > F) {
140 | this.applyTorque(-F * (this.torque > 0 ? 1 : -1));
141 | } else {
142 | this.torque = 0;
143 | }
144 | }
145 |
146 | if (this.velDrag != 0) {
147 | this.applyForce(this.vel.copy().mult(-this.velDrag * this.vel.mag()));
148 | }
149 |
150 | if (this.rotDrag > 0) {
151 | var drag = this.rotDrag * this.rotation * this.rotation;
152 | this.applyTorque(this.rotation > 0 ? -drag : drag);
153 | }
154 |
155 | // Acceleration
156 | if (this.force.x != 0 || this.force.y != 0) this.vel.add(this.force.div(this.mass));
157 | // Rotational Acceleration
158 | if (this.torque != 0) this.rotation += this.torque / this.mass;
159 |
160 | this.pos.add(this.vel);
161 | this.heading += this.rotation;
162 |
163 | this.edges();
164 | if (this.force.x != 0 || this.force.y != 0) this.force.mult(0);
165 | this.torque = 0;
166 | }
167 |
168 | Entity.prototype.render = function() {
169 | push();
170 | translate(this.pos.x, this.pos.y);
171 | rotate(this.heading);
172 | fill(0);
173 | stroke(255);
174 | ellipse(this.pos.x, this.pos.y, this.r);
175 | pop();
176 | }
177 |
--------------------------------------------------------------------------------
/entities/laser.js:
--------------------------------------------------------------------------------
1 | // Daniel Shiffman
2 | // http://codingrainbow.com
3 | // http://patreon.com/codingrainbow
4 | // Code for: https://youtu.be/hacZU523FyM
5 |
6 | function Laser(world, params) {
7 | params.r = params.r !== undefined ? params.r : 4;
8 | Entity.call(this, params);
9 |
10 | this.vel = p5.Vector.fromAngle(params.heading).mult(40);
11 | this.vel.add(params.initialVel);
12 | this.c = params.c ? params.c : color(255);
13 | this.duration = params.duration !== undefined ? params.duration : 20;
14 | var maxDuration = this.duration;
15 |
16 | playSoundEffect(laserSoundEffect[floor(random(3))]);
17 |
18 | this.update = function() {
19 | this.duration--;
20 | return Entity.prototype.update.call(this) || this.duration < 0;
21 | }
22 |
23 | this.render = function() {
24 | push();
25 | translate(this.pos.x, this.pos.y);
26 | colorMode(RGB);
27 | stroke(red(this.c), green(this.c), blue(this.c), 55 + 200 * this.duration / maxDuration);
28 | strokeWeight(this.r);
29 | strokeCap(SQUARE);
30 | var halfLine = this.vel.copy();
31 | halfLine.mult(0.5);
32 | line(-halfLine.x, -halfLine.y, halfLine.x, halfLine.y);
33 | pop();
34 | }
35 |
36 |
37 | this.collides = function(entity) {
38 | var tail = p5.Vector.sub(this.pos, this.vel.copy().mult(1.2));
39 | if (entity.toString() !== "[object Asteroid]"
40 | || !Entity.prototype.collides.call(this, entity) || !lineIntersectCircle(this.pos, tail, entity.pos, entity.r)) {
41 | return false;
42 | }
43 |
44 | var verts = entity.shape.vertices;
45 | for (var i = 0, j = entity.total - 1; i < entity.total; j = i++)
46 | if (lineIntersect(Entity.globalPoint(entity, verts[i]), Entity.globalPoint(entity, verts[j]), this.pos, tail)) return true;
47 | return false;
48 | }
49 |
50 | this.collision = function(entity) {
51 | if (entity.toString() === "[object Asteroid]") {
52 | this.dead = true;
53 | }
54 | }
55 |
56 | this.toString = function() {
57 | return "[object Laser]";
58 | }
59 | }
60 |
61 | Laser.prototype = Object.create(Entity.prototype);
62 |
--------------------------------------------------------------------------------
/entities/ship.js:
--------------------------------------------------------------------------------
1 | // Daniel Shiffman
2 | // http://codingrainbow.com
3 | // http://patreon.com/codingrainbow
4 | // Code for: https://youtu.be/hacZU523FyM
5 |
6 | function Ship(world, params) {
7 | Entity.call(this, params);
8 | this.lives = params.lives !== undefined ? params.lives : 3;
9 | var shieldDuration = params.shieldDuration !== undefined ? params.shieldDuration : 180;
10 | this.shields = shieldDuration;
11 | var resetPos = this.pos.copy();
12 | var respawnFramesReset = 300;
13 | var respawnFrames;
14 | this.mass = 1000;
15 | this.thrustPower = params.thrustPower !== undefined ? params.thrustPower : {
16 | forward: 200,
17 | backward: 100,
18 | left: 180,
19 | right: 180,
20 | stabilization: 200,
21 | rotation: 80
22 | };
23 | this.maxThrust = params.maxThrust !== undefined ? params.maxThrust : 100;
24 | this.coefficients = {
25 | velMu: 1 / this.thrustPower.stabilization,
26 | rotMu: 0,
27 | velDrag: Entity.calculateDragCo(this.maxThrust, 15),
28 | rotDrag: 0
29 | }
30 | this.velMu = this.coefficients.velMu;
31 | this.velDrag = this.coefficients.velDrag;
32 | this.front = createVector(4 / 3 * this.r, 0);
33 | this.shape = new Shape([
34 | createVector(-2 / 3 * this.r, -this.r),
35 | createVector(-2 / 3 * this.r, this.r),
36 | this.front
37 | ]);
38 | this.colors = [
39 | color(243, 89 , 86 ),
40 | color(241, 197, 0 ),
41 | color(73 , 187, 108),
42 | color(36 , 148, 193),
43 | color(150, 89 , 167)
44 | ]
45 | this.colorIndex = 0;
46 |
47 | var fireColors = [];
48 | for (var i = 0; i * 10 <= 255; i++) {
49 | fireColors[i] = "rgb(255," + i * 10 + ",0)";
50 | }
51 |
52 | var stabToggle = true;
53 | var rateOfFire = 40;
54 | var lastShot = 0;
55 | var scope = this;
56 |
57 | var inputs = {
58 | targetPoint: createVector(1, 0),
59 | thrustVector: createVector(0, 0),
60 | laser: false
61 | };
62 |
63 | this.setInputs = function(targetPoint, thrustForward, thrustBackwards, thrustLeft, thrustRight, stabilizationToggle, laser) {
64 | var upRight = p5.Vector.dot(p5.Vector.fromAngle(this.heading), createVector(0, -1));
65 | inputs.thrustVector = createVector(
66 | (thrustForward ? this.thrustPower.forward : 0) + (thrustBackwards ? -this.thrustPower.backward : 0),
67 | (upRight > -0.259 ? 1 : -1) * ((thrustRight ? this.thrustPower.right : 0) + (thrustLeft ? -this.thrustPower.left : 0))
68 | );
69 | inputs.targetPoint = targetPoint;
70 | inputs.laser = laser;
71 |
72 | if (stabilizationToggle) {
73 | stabToggle = !stabToggle;
74 | }
75 | }
76 |
77 | this.registerId = function(id) {
78 | Entity.prototype.registerId.call(this, id);
79 | }
80 |
81 | this.collides = function(entity) {
82 | if (this.shields > 0 ||
83 | entity.toString() !== "[object Asteroid]" ||
84 | !Entity.prototype.collides.call(this, entity)) {
85 | return false;
86 | }
87 |
88 | var verts = this.globalVertices();
89 | var asteroid_vertices = entity.globalVertices();
90 | for (var i = 0; i < asteroid_vertices.length; i++) {
91 | for (var j = 0; j < verts.length; j++) {
92 | if (lineIntersect(verts[j], verts[(j + 1) % verts.length], asteroid_vertices[i], asteroid_vertices[(i + 1) % asteroid_vertices.length])) {
93 | return true;
94 | }
95 | }
96 | }
97 | return false;
98 | }
99 |
100 | this.collision = function(entity) {
101 | if (entity.toString(entity) === "[object Asteroid]") {
102 | this.lives--;
103 | if (this.lives === 0 && this.owner !== -1) {
104 | world.getPlayer(this.owner).dead = true;
105 | }
106 |
107 | this.canCollide = false;
108 | this.shape.breakAnime();
109 | respawnFrames = respawnFramesReset;
110 | }
111 | }
112 |
113 | this.regenShields = function() {
114 | this.shields = shieldDuration;
115 | }
116 |
117 | this.update = function() {
118 |
119 | if (this.canCollide) {
120 | var force = p5.Vector.sub(inputs.targetPoint.copy(), this.front.copy().rotate(this.heading));
121 | force.normalize();
122 | force.mult(this.thrustPower.rotation);
123 | this.applyTorque(Entity.calculateMoment(inputs.targetPoint, force));
124 | var sinTheta = cross(p5.Vector.fromAngle(this.heading), force) / force.mag();
125 | if (abs(sin(this.predictRotation())) > abs(sinTheta)) {
126 | this.torque = 0;
127 | this.rotation = 0;
128 | this.heading += asin(sinTheta);
129 | }
130 |
131 | this.applyForce(inputs.thrustVector.rotate(this.heading));
132 |
133 | if (lastShot > 0) {
134 | lastShot--;
135 | } else if (inputs.laser) {
136 | var temp = this.colors[0];
137 | world.addEndFrameTask(function(world) {
138 | world.createEntity(Laser, {
139 | pos: scope.front.copy().add(createVector(20, 0)).rotate(scope.heading).add(scope.pos),
140 | heading: scope.heading,
141 | c: scope.colors[scope.colorIndex],
142 | initialVel: scope.vel,
143 | owner: scope.owner
144 | });
145 | scope.colorIndex++;
146 | scope.colorIndex %= scope.colors.length;
147 | });
148 | lastShot = rateOfFire;
149 | }
150 |
151 | this.velMu = stabToggle && (inputs.thrustVector.x === 0 && inputs.thrustVector.y === 0) ? this.coefficients.velMu : 0;
152 | if (Entity.prototype.update.call(this)) {
153 | return true;
154 | }
155 |
156 | if (this.shields > 0) {
157 | this.shields--;
158 | }
159 | }
160 | }
161 |
162 | this.render = function() {
163 | push();
164 | translate(this.pos.x, this.pos.y);
165 | rotate(this.heading);
166 | colorMode(RGB);
167 | noFill();
168 | strokeWeight(3);
169 | if (!this.canCollide) {
170 | strokeCap(ROUND);
171 | stroke(255, 255, 255, this.shape.fade());
172 | if (!this.shape.draw()) {
173 | this.reset();
174 | if (this.lives === 0) this.dead = true;
175 | }
176 | } else {
177 | var shieldCol = random(map(this.shields, 0, shieldDuration, 255, 0), 255);
178 | stroke(shieldCol, shieldCol, 255);
179 | this.shape.draw();
180 | if (inputs.thrust) {
181 | translate(-this.r, 0);
182 | rotate(random(PI / 4, 3 * PI / 4));
183 | stroke(fireColors[floor(random(fireColors.length))]);
184 | line(0, 0, 0, 10);
185 | }
186 | }
187 | pop();
188 | }
189 |
190 | this.reset = function() {
191 | this.canCollide = true;
192 | this.regenShields();
193 | this.pos.set(resetPos.x, resetPos.y);
194 | this.vel.set(0, 0);
195 | this.lastShot = 0;
196 | }
197 |
198 | this.globalVertices = function() {
199 | return this.shape.globalVertices(this.pos, this.heading);
200 | }
201 |
202 | this.toString = function() {
203 | return "[object Ship]";
204 | }
205 | }
206 |
207 | Ship.prototype = Object.create(Entity.prototype);
208 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Community Asteroids! - Coding Rainbow
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/input.js:
--------------------------------------------------------------------------------
1 | var input = {
2 | listeners: {
3 | key: {},
4 | mouse: {}
5 | },
6 |
7 | registerMouseListener: function(id, index, callback) {
8 | if (this.listeners.mouse[index] == undefined) {
9 | this.listeners.mouse[index] = {};
10 | }
11 | if (this.listeners.mouse[index][id] == undefined) {
12 | this.listeners.mouse[index][id] = [];
13 | }
14 |
15 | this.listeners.mouse[index][id].push(callback);
16 | },
17 |
18 | deregisterMouseListener: function(id, index) {
19 | delete this.listeners.mouse[index][id];
20 | },
21 |
22 | handleMouseEvent: function(button, press) {
23 | if (this.listeners.mouse[button] != undefined) {
24 | for (var i in this.listeners.mouse[button]) {
25 | for (var j = 0; j < this.listeners.mouse[button][i].length; j++) {
26 | this.listeners.mouse[button][i][j](button, press);
27 | }
28 | }
29 | }
30 | },
31 |
32 | registerKeyListener: function(id, index, callback) {
33 | if (this.listeners.key[index] == undefined) {
34 | this.listeners.key[index] = {};
35 | }
36 | if (this.listeners.key[index][id] == undefined) {
37 | this.listeners.key[index][id] = [];
38 | }
39 |
40 | this.listeners.key[index][id].push(callback);
41 | },
42 |
43 | deregisterKeyListener: function(id, index) {
44 | delete this.listeners.key[index][id];
45 | },
46 |
47 | handleKeyEvent: function(char, code, press) {
48 | if (this.listeners.key[code] != undefined) {
49 | for (var i in this.listeners.key[code]) {
50 | for (var j = 0; j < this.listeners.key[code][i].length; j++) {
51 | this.listeners.key[code][i][j](char, code, press);
52 | }
53 | }
54 | }
55 | }
56 | };
57 |
58 | function keyReleased() {
59 | input.handleKeyEvent(key, keyCode, false);
60 | }
61 |
62 | function keyPressed() {
63 | input.handleKeyEvent(key, keyCode, true);
64 | }
65 |
66 | function mousePressed() {
67 | input.handleMouseEvent(mouseButton, true);
68 | }
69 |
70 | function mouseReleased() {
71 | input.handleMouseEvent(mouseButton, false);
72 | }
73 |
--------------------------------------------------------------------------------
/libraries/p5.dom.js:
--------------------------------------------------------------------------------
1 | /*! p5.dom.js v0.2.13 Oct 1, 2016 */
2 | /**
3 | * The web is much more than just canvas and p5.dom makes it easy to interact
4 | * with other HTML5 objects, including text, hyperlink, image, input, video,
5 | * audio, and webcam.
6 | * There is a set of creation methods, DOM manipulation methods, and
7 | * an extended p5.Element that supports a range of HTML elements. See the
8 | *
9 | * beyond the canvas tutorial for a full overview of how this addon works.
10 | *
11 | *
Methods and properties shown in black are part of the p5.js core, items in
12 | * blue are part of the p5.dom library. You will need to include an extra file
13 | * in order to access the blue functions. See the
14 | * using a library
15 | * section for information on how to include this library. p5.dom comes with
16 | * p5 complete or you can download the single file
17 | *
18 | * here.
19 | * See tutorial: beyond the canvas
20 | * for more info on how to use this libary.
21 | *
22 | * @module p5.dom
23 | * @submodule p5.dom
24 | * @for p5.dom
25 | * @main
26 | */
27 |
28 | (function (root, factory) {
29 | if (typeof define === 'function' && define.amd)
30 | define('p5.dom', ['p5'], function (p5) { (factory(p5));});
31 | else if (typeof exports === 'object')
32 | factory(require('../p5'));
33 | else
34 | factory(root['p5']);
35 | }(this, function (p5) {
36 | // =============================================================================
37 | // p5 additions
38 | // =============================================================================
39 |
40 | /**
41 | * Searches the page for an element with the given ID, class, or tag name (using the '#' or '.'
42 | * prefixes to specify an ID or class respectively, and none for a tag) and returns it as
43 | * a p5.Element. If a class or tag name is given with more than 1 element,
44 | * only the first element will be returned.
45 | * The DOM node itself can be accessed with .elt.
46 | * Returns null if none found. You can also specify a container to search within.
47 | *
48 | * @method select
49 | * @param {String} name id, class, or tag name of element to search for
50 | * @param {String} [container] id, p5.Element, or HTML element to search within
51 | * @return {Object/p5.Element|Null} p5.Element containing node found
52 | * @example
53 | *
54 | * function setup() {
55 | * createCanvas(100,100);
56 | * //translates canvas 50px down
57 | * select('canvas').position(100, 100);
58 | * }
59 | *
60 | *
61 | * // these are all valid calls to select()
62 | * var a = select('#moo');
63 | * var b = select('#blah', '#myContainer');
64 | * var c = select('#foo', b);
65 | * var d = document.getElementById('beep');
66 | * var e = select('p', d);
67 | *
68 | *
69 | */
70 | p5.prototype.select = function (e, p) {
71 | var res = null;
72 | var container = getContainer(p);
73 | if (e[0] === '.'){
74 | e = e.slice(1);
75 | res = container.getElementsByClassName(e);
76 | if (res.length) {
77 | res = res[0];
78 | } else {
79 | res = null;
80 | }
81 | }else if (e[0] === '#'){
82 | e = e.slice(1);
83 | res = container.getElementById(e);
84 | }else {
85 | res = container.getElementsByTagName(e);
86 | if (res.length) {
87 | res = res[0];
88 | } else {
89 | res = null;
90 | }
91 | }
92 | if (res) {
93 | return wrapElement(res);
94 | } else {
95 | return null;
96 | }
97 | };
98 |
99 | /**
100 | * Searches the page for elements with the given class or tag name (using the '.' prefix
101 | * to specify a class and no prefix for a tag) and returns them as p5.Elements
102 | * in an array.
103 | * The DOM node itself can be accessed with .elt.
104 | * Returns an empty array if none found.
105 | * You can also specify a container to search within.
106 | *
107 | * @method selectAll
108 | * @param {String} name class or tag name of elements to search for
109 | * @param {String} [container] id, p5.Element, or HTML element to search within
110 | * @return {Array} Array of p5.Elements containing nodes found
111 | * @example
112 | *
113 | * function setup() {
114 | * createButton('btn');
115 | * createButton('2nd btn');
116 | * createButton('3rd btn');
117 | * var buttons = selectAll('button');
118 | *
119 | * for (var i = 0; i < buttons.length; i++){
120 | * buttons[i].size(100,100);
121 | * }
122 | * }
123 | *
124 | *
125 | * // these are all valid calls to selectAll()
126 | * var a = selectAll('.moo');
127 | * var b = selectAll('div');
128 | * var c = selectAll('button', '#myContainer');
129 | * var d = select('#container');
130 | * var e = selectAll('p', d);
131 | * var f = document.getElementById('beep');
132 | * var g = select('.blah', f);
133 | *
134 | *
135 | */
136 | p5.prototype.selectAll = function (e, p) {
137 | var arr = [];
138 | var res;
139 | var container = getContainer(p);
140 | if (e[0] === '.'){
141 | e = e.slice(1);
142 | res = container.getElementsByClassName(e);
143 | } else {
144 | res = container.getElementsByTagName(e);
145 | }
146 | if (res) {
147 | for (var j = 0; j < res.length; j++) {
148 | var obj = wrapElement(res[j]);
149 | arr.push(obj);
150 | }
151 | }
152 | return arr;
153 | };
154 |
155 | /**
156 | * Helper function for select and selectAll
157 | */
158 | function getContainer(p) {
159 | var container = document;
160 | if (typeof p === 'string' && p[0] === '#'){
161 | p = p.slice(1);
162 | container = document.getElementById(p) || document;
163 | } else if (p instanceof p5.Element){
164 | container = p.elt;
165 | } else if (p instanceof HTMLElement){
166 | container = p;
167 | }
168 | return container;
169 | }
170 |
171 | /**
172 | * Helper function for getElement and getElements.
173 | */
174 | function wrapElement(elt) {
175 | if(elt.tagName === "INPUT" && elt.type === "checkbox") {
176 | var converted = new p5.Element(elt);
177 | converted.checked = function(){
178 | if (arguments.length === 0){
179 | return this.elt.checked;
180 | } else if(arguments[0]) {
181 | this.elt.checked = true;
182 | } else {
183 | this.elt.checked = false;
184 | }
185 | return this;
186 | };
187 | return converted;
188 | } else if (elt.tagName === "VIDEO" || elt.tagName === "AUDIO") {
189 | return new p5.MediaElement(elt);
190 | } else {
191 | return new p5.Element(elt);
192 | }
193 | }
194 |
195 | /**
196 | * Removes all elements created by p5, except any canvas / graphics
197 | * elements created by createCanvas or createGraphics.
198 | * Event handlers are removed, and element is removed from the DOM.
199 | * @method removeElements
200 | * @example
201 | *
202 | * function setup() {
203 | * createCanvas(100, 100);
204 | * createDiv('this is some text');
205 | * createP('this is a paragraph');
206 | * }
207 | * function mousePressed() {
208 | * removeElements(); // this will remove the div and p, not canvas
209 | * }
210 | *
211 | *
212 | */
213 | p5.prototype.removeElements = function (e) {
214 | for (var i=0; i
242 | * var myDiv;
243 | * function setup() {
244 | * myDiv = createDiv('this is some text');
245 | * }
246 | *
247 | */
248 |
249 | /**
250 | * Creates a <p></p> element in the DOM with given inner HTML. Used
251 | * for paragraph length text.
252 | * Appends to the container node if one is specified, otherwise
253 | * appends to body.
254 | *
255 | * @method createP
256 | * @param {String} html inner HTML for element created
257 | * @return {Object/p5.Element} pointer to p5.Element holding created node
258 | * @example
259 | *
260 | * var myP;
261 | * function setup() {
262 | * myP = createP('this is some text');
263 | * }
264 | *
265 | */
266 |
267 | /**
268 | * Creates a <span></span> element in the DOM with given inner HTML.
269 | * Appends to the container node if one is specified, otherwise
270 | * appends to body.
271 | *
272 | * @method createSpan
273 | * @param {String} html inner HTML for element created
274 | * @return {Object/p5.Element} pointer to p5.Element holding created node
275 | * @example
276 | *
277 | * var mySpan;
278 | * function setup() {
279 | * mySpan = createSpan('this is some text');
280 | * }
281 | *
282 | */
283 | var tags = ['div', 'p', 'span'];
284 | tags.forEach(function(tag) {
285 | var method = 'create' + tag.charAt(0).toUpperCase() + tag.slice(1);
286 | p5.prototype[method] = function(html) {
287 | var elt = document.createElement(tag);
288 | elt.innerHTML = typeof html === undefined ? "" : html;
289 | return addElement(elt, this);
290 | }
291 | });
292 |
293 | /**
294 | * Creates an <img /> element in the DOM with given src and
295 | * alternate text.
296 | * Appends to the container node if one is specified, otherwise
297 | * appends to body.
298 | *
299 | * @method createImg
300 | * @param {String} src src path or url for image
301 | * @param {String} [alt] alternate text to be used if image does not load
302 | * @param {Function} [successCallback] callback to be called once image data is loaded
303 | * @return {Object/p5.Element} pointer to p5.Element holding created node
304 | * @example
305 | *
306 | * var img;
307 | * function setup() {
308 | * img = createImg('http://p5js.org/img/asterisk-01.png');
309 | * }
310 | *
311 | */
312 | p5.prototype.createImg = function() {
313 | var elt = document.createElement('img');
314 | var args = arguments;
315 | var self;
316 | var setAttrs = function(){
317 | self.width = elt.offsetWidth;
318 | self.height = elt.offsetHeight;
319 | if (args.length > 1 && typeof args[1] === 'function'){
320 | self.fn = args[1];
321 | self.fn();
322 | }else if (args.length > 1 && typeof args[2] === 'function'){
323 | self.fn = args[2];
324 | self.fn();
325 | }
326 | };
327 | elt.src = args[0];
328 | if (args.length > 1 && typeof args[1] === 'string'){
329 | elt.alt = args[1];
330 | }
331 | elt.onload = function(){
332 | setAttrs();
333 | }
334 | self = addElement(elt, this);
335 | return self;
336 | };
337 |
338 | /**
339 | * Creates an <a></a> element in the DOM for including a hyperlink.
340 | * Appends to the container node if one is specified, otherwise
341 | * appends to body.
342 | *
343 | * @method createA
344 | * @param {String} href url of page to link to
345 | * @param {String} html inner html of link element to display
346 | * @param {String} [target] target where new link should open,
347 | * could be _blank, _self, _parent, _top.
348 | * @return {Object/p5.Element} pointer to p5.Element holding created node
349 | * @example
350 | *
351 | * var myLink;
352 | * function setup() {
353 | * myLink = createA('http://p5js.org/', 'this is a link');
354 | * }
355 | *
356 | */
357 | p5.prototype.createA = function(href, html, target) {
358 | var elt = document.createElement('a');
359 | elt.href = href;
360 | elt.innerHTML = html;
361 | if (target) elt.target = target;
362 | return addElement(elt, this);
363 | };
364 |
365 | /** INPUT **/
366 |
367 |
368 | /**
369 | * Creates a slider <input></input> element in the DOM.
370 | * Use .size() to set the display length of the slider.
371 | * Appends to the container node if one is specified, otherwise
372 | * appends to body.
373 | *
374 | * @method createSlider
375 | * @param {Number} min minimum value of the slider
376 | * @param {Number} max maximum value of the slider
377 | * @param {Number} [value] default value of the slider
378 | * @param {Number} [step] step size for each tick of the slider (if step is set to 0, the slider will move continuously from the minimum to the maximum value)
379 | * @return {Object/p5.Element} pointer to p5.Element holding created node
380 | * @example
381 | *
382 | * var slider;
383 | * function setup() {
384 | * slider = createSlider(0, 255, 100);
385 | * slider.position(10, 10);
386 | * slider.style('width', '80px');
387 | * }
388 | *
389 | * function draw() {
390 | * var val = slider.value();
391 | * background(val);
392 | * }
393 | *
394 | *
395 | *
396 | * var slider;
397 | * function setup() {
398 | * colorMode(HSB);
399 | * slider = createSlider(0, 360, 60, 40);
400 | * slider.position(10, 10);
401 | * slider.style('width', '80px');
402 | * }
403 | *
404 | * function draw() {
405 | * var val = slider.value();
406 | * background(val, 100, 100, 1);
407 | * }
408 | *
409 | */
410 | p5.prototype.createSlider = function(min, max, value, step) {
411 | var elt = document.createElement('input');
412 | elt.type = 'range';
413 | elt.min = min;
414 | elt.max = max;
415 | if (step === 0) {
416 | elt.step = .000000000000000001; // smallest valid step
417 | } else if (step) {
418 | elt.step = step;
419 | }
420 | if (typeof(value) === "number") elt.value = value;
421 | return addElement(elt, this);
422 | };
423 |
424 | /**
425 | * Creates a <button></button> element in the DOM.
426 | * Use .size() to set the display size of the button.
427 | * Use .mousePressed() to specify behavior on press.
428 | * Appends to the container node if one is specified, otherwise
429 | * appends to body.
430 | *
431 | * @method createButton
432 | * @param {String} label label displayed on the button
433 | * @param {String} [value] value of the button
434 | * @return {Object/p5.Element} pointer to p5.Element holding created node
435 | * @example
436 | *
437 | * var button;
438 | * function setup() {
439 | * createCanvas(100, 100);
440 | * background(0);
441 | * button = createButton('click me');
442 | * button.position(19, 19);
443 | * button.mousePressed(changeBG);
444 | * }
445 | *
446 | * function changeBG() {
447 | * var val = random(255);
448 | * background(val);
449 | * }
450 | *
451 | */
452 | p5.prototype.createButton = function(label, value) {
453 | var elt = document.createElement('button');
454 | elt.innerHTML = label;
455 | elt.value = value;
456 | if (value) elt.value = value;
457 | return addElement(elt, this);
458 | };
459 |
460 | /**
461 | * Creates a checkbox <input></input> element in the DOM.
462 | * Calling .checked() on a checkbox returns if it is checked or not
463 | *
464 | * @method createCheckbox
465 | * @param {String} [label] label displayed after checkbox
466 | * @param {boolean} [value] value of the checkbox; checked is true, unchecked is false.Unchecked if no value given
467 | * @return {Object/p5.Element} pointer to p5.Element holding created node
468 | * @example
469 | *
470 | * var checkbox;
471 | *
472 | * function setup() {
473 | * checkbox = createCheckbox('label', false);
474 | * checkbox.changed(myCheckedEvent);
475 | * }
476 | *
477 | * function myCheckedEvent() {
478 | * if (this.checked()) {
479 | * console.log("Checking!");
480 | * } else {
481 | * console.log("Unchecking!");
482 | * }
483 | * }
484 | *
485 | */
486 | p5.prototype.createCheckbox = function() {
487 | var elt = document.createElement('div');
488 | var checkbox = document.createElement('input');
489 | checkbox.type = 'checkbox';
490 | elt.appendChild(checkbox);
491 | //checkbox must be wrapped in p5.Element before label so that label appears after
492 | var self = addElement(elt, this);
493 | self.checked = function(){
494 | var cb = self.elt.getElementsByTagName('input')[0];
495 | if (cb) {
496 | if (arguments.length === 0){
497 | return cb.checked;
498 | }else if(arguments[0]){
499 | cb.checked = true;
500 | }else{
501 | cb.checked = false;
502 | }
503 | }
504 | return self;
505 | };
506 | this.value = function(val){
507 | self.value = val;
508 | return this;
509 | };
510 | if (arguments[0]){
511 | var ran = Math.random().toString(36).slice(2);
512 | var label = document.createElement('label');
513 | checkbox.setAttribute('id', ran);
514 | label.htmlFor = ran;
515 | self.value(arguments[0]);
516 | label.appendChild(document.createTextNode(arguments[0]));
517 | elt.appendChild(label);
518 | }
519 | if (arguments[1]){
520 | checkbox.checked = true;
521 | }
522 | return self;
523 | };
524 |
525 | /**
526 | * Creates a dropdown menu <select></select> element in the DOM.
527 | * @method createSelect
528 | * @param {boolean} [multiple] [true if dropdown should support multiple selections]
529 | * @return {Object/p5.Element} pointer to p5.Element holding created node
530 | * @example
531 | *
532 | * var sel;
533 | *
534 | * function setup() {
535 | * textAlign(CENTER);
536 | * background(200);
537 | * sel = createSelect();
538 | * sel.position(10, 10);
539 | * sel.option('pear');
540 | * sel.option('kiwi');
541 | * sel.option('grape');
542 | * sel.changed(mySelectEvent);
543 | * }
544 | *
545 | * function mySelectEvent() {
546 | * var item = sel.value();
547 | * background(200);
548 | * text("it's a "+item+"!", 50, 50);
549 | * }
550 | *
551 | */
552 | p5.prototype.createSelect = function(mult) {
553 | var elt = document.createElement('select');
554 | if (mult){
555 | elt.setAttribute('multiple', 'true');
556 | }
557 | var self = addElement(elt, this);
558 | self.option = function(name, value){
559 | var opt = document.createElement('option');
560 | opt.innerHTML = name;
561 | if (arguments.length > 1)
562 | opt.value = value;
563 | else
564 | opt.value = name;
565 | elt.appendChild(opt);
566 | };
567 | self.selected = function(value){
568 | var arr = [];
569 | if (arguments.length > 0){
570 | for (var i = 0; i < this.elt.length; i++){
571 | if (value.toString() === this.elt[i].value){
572 | this.elt.selectedIndex = i;
573 | }
574 | }
575 | return this;
576 | }else{
577 | if (mult){
578 | for (var i = 0; i < this.elt.selectedOptions.length; i++){
579 | arr.push(this.elt.selectedOptions[i].value);
580 | }
581 | return arr;
582 | }else{
583 | return this.elt.value;
584 | }
585 | }
586 | };
587 | return self;
588 | };
589 |
590 | /**
591 | * Creates a radio button <input></input> element in the DOM.
592 | * The .option() method can be used to set options for the radio after it is
593 | * created. The .value() method will return the currently selected option.
594 | *
595 | * @method createRadio
596 | * @param {String} [divId] the id and name of the created div and input field respectively
597 | * @return {Object/p5.Element} pointer to p5.Element holding created node
598 | * @example
599 | *
600 | * var radio;
601 | *
602 | * function setup() {
603 | * radio = createRadio();
604 | * radio.option("black");
605 | * radio.option("white");
606 | * radio.option("gray");
607 | * radio.style('width', '60px');
608 | * textAlign(CENTER);
609 | * fill(255, 0, 0);
610 | * }
611 | *
612 | * function draw() {
613 | * var val = radio.value();
614 | * background(val);
615 | * text(val, width/2, height/2);
616 | * }
617 | *
618 | *
619 | * var radio;
620 | *
621 | * function setup() {
622 | * radio = createRadio();
623 | * radio.option('apple', 1);
624 | * radio.option('bread', 2);
625 | * radio.option('juice', 3);
626 | * radio.style('width', '60px');
627 | * textAlign(CENTER);
628 | * }
629 | *
630 | * function draw() {
631 | * background(200);
632 | * var val = radio.value();
633 | * if (val) {
634 | * text('item cost is $'+val, width/2, height/2);
635 | * }
636 | * }
637 | *
638 | */
639 | p5.prototype.createRadio = function() {
640 | var radios = document.querySelectorAll("input[type=radio]");
641 | var count = 0;
642 | if(radios.length > 1){
643 | var length = radios.length;
644 | var prev=radios[0].name;
645 | var current = radios[1].name;
646 | count = 1;
647 | for(var i = 1; i < length; i++) {
648 | current = radios[i].name;
649 | if(prev != current){
650 | count++;
651 | }
652 | prev = current;
653 | }
654 | }
655 | else if (radios.length == 1){
656 | count = 1;
657 | }
658 | var elt = document.createElement('div');
659 | var self = addElement(elt, this);
660 | var times = -1;
661 | self.option = function(name, value){
662 | var opt = document.createElement('input');
663 | opt.type = 'radio';
664 | opt.innerHTML = name;
665 | if (arguments.length > 1)
666 | opt.value = value;
667 | else
668 | opt.value = name;
669 | opt.setAttribute('name',"defaultradio"+count);
670 | elt.appendChild(opt);
671 | if (name){
672 | times++;
673 | var ran = Math.random().toString(36).slice(2);
674 | var label = document.createElement('label');
675 | opt.setAttribute('id', "defaultradio"+count+"-"+times);
676 | label.htmlFor = "defaultradio"+count+"-"+times;
677 | label.appendChild(document.createTextNode(name));
678 | elt.appendChild(label);
679 | }
680 | return opt;
681 | };
682 | self.selected = function(){
683 | var length = this.elt.childNodes.length;
684 | if(arguments.length == 1) {
685 | for (var i = 0; i < length; i+=2){
686 | if(this.elt.childNodes[i].value == arguments[0])
687 | this.elt.childNodes[i].checked = true;
688 | }
689 | return this;
690 | } else {
691 | for (var i = 0; i < length; i+=2){
692 | if(this.elt.childNodes[i].checked == true)
693 | return this.elt.childNodes[i].value;
694 | }
695 | }
696 | };
697 | self.value = function(){
698 | var length = this.elt.childNodes.length;
699 | if(arguments.length == 1) {
700 | for (var i = 0; i < length; i+=2){
701 | if(this.elt.childNodes[i].value == arguments[0])
702 | this.elt.childNodes[i].checked = true;
703 | }
704 | return this;
705 | } else {
706 | for (var i = 0; i < length; i+=2){
707 | if(this.elt.childNodes[i].checked == true)
708 | return this.elt.childNodes[i].value;
709 | }
710 | return "";
711 | }
712 | };
713 | return self
714 | };
715 |
716 | /**
717 | * Creates an <input></input> element in the DOM for text input.
718 | * Use .size() to set the display length of the box.
719 | * Appends to the container node if one is specified, otherwise
720 | * appends to body.
721 | *
722 | * @method createInput
723 | * @param {Number} [value] default value of the input box
724 | * @return {Object/p5.Element} pointer to p5.Element holding created node
725 | * @example
726 | *
727 | * function setup(){
728 | * var inp = createInput('');
729 | * inp.input(myInputEvent);
730 | * }
731 | *
732 | * function myInputEvent(){
733 | * console.log('you are typing: ', this.value());
734 | * }
735 | *
736 | *
737 | */
738 | p5.prototype.createInput = function(value) {
739 | var elt = document.createElement('input');
740 | elt.type = 'text';
741 | if (value) elt.value = value;
742 | return addElement(elt, this);
743 | };
744 |
745 | /**
746 | * Creates an <input></input> element in the DOM of type 'file'.
747 | * This allows users to select local files for use in a sketch.
748 | *
749 | * @method createFileInput
750 | * @param {Function} [callback] callback function for when a file loaded
751 | * @param {String} [multiple] optional to allow multiple files selected
752 | * @return {Object/p5.Element} pointer to p5.Element holding created DOM element
753 | */
754 | p5.prototype.createFileInput = function(callback, multiple) {
755 |
756 | // Is the file stuff supported?
757 | if (window.File && window.FileReader && window.FileList && window.Blob) {
758 | // Yup, we're ok and make an input file selector
759 | var elt = document.createElement('input');
760 | elt.type = 'file';
761 |
762 | // If we get a second argument that evaluates to true
763 | // then we are looking for multiple files
764 | if (multiple) {
765 | // Anything gets the job done
766 | elt.multiple = 'multiple';
767 | }
768 |
769 | // Function to handle when a file is selected
770 | // We're simplifying life and assuming that we always
771 | // want to load every selected file
772 | function handleFileSelect(evt) {
773 | // These are the files
774 | var files = evt.target.files;
775 | // Load each one and trigger a callback
776 | for (var i = 0; i < files.length; i++) {
777 | var f = files[i];
778 | var reader = new FileReader();
779 | function makeLoader(theFile) {
780 | // Making a p5.File object
781 | var p5file = new p5.File(theFile);
782 | return function(e) {
783 | p5file.data = e.target.result;
784 | callback(p5file);
785 | };
786 | };
787 | reader.onload = makeLoader(f);
788 |
789 | // Text or data?
790 | // This should likely be improved
791 | if (f.type.indexOf('text') > -1) {
792 | reader.readAsText(f);
793 | } else {
794 | reader.readAsDataURL(f);
795 | }
796 | }
797 | }
798 |
799 | // Now let's handle when a file was selected
800 | elt.addEventListener('change', handleFileSelect, false);
801 | return addElement(elt, this);
802 | } else {
803 | console.log('The File APIs are not fully supported in this browser. Cannot create element.');
804 | }
805 | };
806 |
807 |
808 | /** VIDEO STUFF **/
809 |
810 | function createMedia(pInst, type, src, callback) {
811 | var elt = document.createElement(type);
812 |
813 | // allow src to be empty
814 | var src = src || '';
815 | if (typeof src === 'string') {
816 | src = [src];
817 | }
818 | for (var i=0; ithis
854 | * page for further information about supported formats.
855 | *
856 | * @method createVideo
857 | * @param {String|Array} src path to a video file, or array of paths for
858 | * supporting different browsers
859 | * @param {Object} [callback] callback function to be called upon
860 | * 'canplaythrough' event fire, that is, when the
861 | * browser can play the media, and estimates that
862 | * enough data has been loaded to play the media
863 | * up to its end without having to stop for
864 | * further buffering of content
865 | * @return {Object/p5.Element} pointer to video p5.Element
866 | */
867 | p5.prototype.createVideo = function(src, callback) {
868 | return createMedia(this, 'video', src, callback);
869 | };
870 |
871 | /** AUDIO STUFF **/
872 |
873 | /**
874 | * Creates a hidden HTML5 <audio> element in the DOM for simple audio
875 | * playback. Appends to the container node if one is specified,
876 | * otherwise appends to body. The first parameter
877 | * can be either a single string path to a audio file, or an array of string
878 | * paths to different formats of the same audio. This is useful for ensuring
879 | * that your audio can play across different browsers, as each supports
880 | * different formats. See this
881 | * page for further information about supported formats.
882 | *
883 | * @method createAudio
884 | * @param {String|Array} src path to an audio file, or array of paths for
885 | * supporting different browsers
886 | * @param {Object} [callback] callback function to be called upon
887 | * 'canplaythrough' event fire, that is, when the
888 | * browser can play the media, and estimates that
889 | * enough data has been loaded to play the media
890 | * up to its end without having to stop for
891 | * further buffering of content
892 | * @return {Object/p5.Element} pointer to audio p5.Element
893 | */
894 | p5.prototype.createAudio = function(src, callback) {
895 | return createMedia(this, 'audio', src, callback);
896 | };
897 |
898 |
899 | /** CAMERA STUFF **/
900 |
901 | p5.prototype.VIDEO = 'video';
902 | p5.prototype.AUDIO = 'audio';
903 |
904 | navigator.getUserMedia = navigator.getUserMedia ||
905 | navigator.webkitGetUserMedia ||
906 | navigator.mozGetUserMedia ||
907 | navigator.msGetUserMedia;
908 |
909 | /**
910 | * Creates a new <video> element that contains the audio/video feed
911 | * from a webcam. This can be drawn onto the canvas using video().
912 | * More specific properties of the feed can be passing in a Constraints object.
913 | * See the
914 | * W3C
915 | * spec for possible properties. Note that not all of these are supported
916 | * by all browsers.
917 | * Security note: A new browser security specification requires that getUserMedia,
918 | * which is behind createCapture(), only works when you're running the code locally,
919 | * or on HTTPS. Learn more here
920 | * and here.
921 | *
922 | * @method createCapture
923 | * @param {String|Constant|Object} type type of capture, either VIDEO or
924 | * AUDIO if none specified, default both,
925 | * or a Constraints object
926 | * @param {Function} callback function to be called once
927 | * stream has loaded
928 | * @return {Object/p5.Element} capture video p5.Element
929 | * @example
930 | *
931 | * var capture;
932 | *
933 | * function setup() {
934 | * createCanvas(480, 120);
935 | * capture = createCapture(VIDEO);
936 | * }
937 | *
938 | * function draw() {
939 | * image(capture, 0, 0, width, width*capture.height/capture.width);
940 | * filter(INVERT);
941 | * }
942 | *
943 | *
944 | * function setup() {
945 | * createCanvas(480, 120);
946 | * var constraints = {
947 | * video: {
948 | * mandatory: {
949 | * minWidth: 1280,
950 | * minHeight: 720
951 | * },
952 | * optional: [
953 | * { maxFrameRate: 10 }
954 | * ]
955 | * },
956 | * audio: true
957 | * };
958 | * createCapture(constraints, function(stream) {
959 | * console.log(stream);
960 | * });
961 | * }
962 | *
963 | */
964 | p5.prototype.createCapture = function() {
965 | var useVideo = true;
966 | var useAudio = true;
967 | var constraints;
968 | var cb;
969 | for (var i=0; i
1020 | * var h2 = createElement('h2','im an h2 p5.element!');
1021 | *
1022 | */
1023 | p5.prototype.createElement = function(tag, content) {
1024 | var elt = document.createElement(tag);
1025 | if (typeof content !== 'undefined') {
1026 | elt.innerHTML = content;
1027 | }
1028 | return addElement(elt, this);
1029 | };
1030 |
1031 |
1032 | // =============================================================================
1033 | // p5.Element additions
1034 | // =============================================================================
1035 | /**
1036 | *
1037 | * Adds specified class to the element.
1038 | *
1039 | * @for p5.Element
1040 | * @method addClass
1041 | * @param {String} class name of class to add
1042 | * @return {Object/p5.Element}
1043 | * @example
1044 | *
1045 | * var div = createDiv('div');
1046 | * div.addClass('myClass');
1047 | *
1048 | */
1049 | p5.Element.prototype.addClass = function(c) {
1050 | if (this.elt.className) {
1051 | // PEND don't add class more than once
1052 | //var regex = new RegExp('[^a-zA-Z\d:]?'+c+'[^a-zA-Z\d:]?');
1053 | //if (this.elt.className.search(/[^a-zA-Z\d:]?hi[^a-zA-Z\d:]?/) === -1) {
1054 | this.elt.className = this.elt.className+' '+c;
1055 | //}
1056 | } else {
1057 | this.elt.className = c;
1058 | }
1059 | return this;
1060 | }
1061 |
1062 | /**
1063 | *
1064 | * Removes specified class from the element.
1065 | *
1066 | * @method removeClass
1067 | * @param {String} class name of class to remove
1068 | * @return {Object/p5.Element}
1069 | */
1070 | p5.Element.prototype.removeClass = function(c) {
1071 | var regex = new RegExp('(?:^|\\s)'+c+'(?!\\S)');
1072 | this.elt.className = this.elt.className.replace(regex, '');
1073 | this.elt.className = this.elt.className.replace(/^\s+|\s+$/g, ""); //prettify (optional)
1074 | return this;
1075 | }
1076 |
1077 | /**
1078 | *
1079 | * Attaches the element as a child to the parent specified.
1080 | * Accepts either a string ID, DOM node, or p5.Element.
1081 | * If no argument is specified, an array of children DOM nodes is returned.
1082 | *
1083 | * @method child
1084 | * @param {String|Object|p5.Element} [child] the ID, DOM node, or p5.Element
1085 | * to add to the current element
1086 | * @return {p5.Element}
1087 | * @example
1088 | *
1089 | * var div0 = createDiv('this is the parent');
1090 | * var div1 = createDiv('this is the child');
1091 | * div0.child(div1); // use p5.Element
1092 | *
1093 | *
1094 | * var div0 = createDiv('this is the parent');
1095 | * var div1 = createDiv('this is the child');
1096 | * div1.id('apples');
1097 | * div0.child('apples'); // use id
1098 | *
1099 | *
1100 | * var div0 = createDiv('this is the parent');
1101 | * var elt = document.getElementById('myChildDiv');
1102 | * div0.child(elt); // use element from page
1103 | *
1104 | */
1105 | p5.Element.prototype.child = function(c) {
1106 | if (c === null){
1107 | return this.elt.childNodes
1108 | }
1109 | if (typeof c === 'string') {
1110 | if (c[0] === '#') {
1111 | c = c.substring(1);
1112 | }
1113 | c = document.getElementById(c);
1114 | } else if (c instanceof p5.Element) {
1115 | c = c.elt;
1116 | }
1117 | this.elt.appendChild(c);
1118 | return this;
1119 | };
1120 |
1121 | /**
1122 | * Centers a p5 Element either vertically, horizontally,
1123 | * or both, relative to its parent or according to
1124 | * the body if the Element has no parent. If no argument is passed
1125 | * the Element is aligned both vertically and horizontally.
1126 | *
1127 | * @param {String} align passing 'vertical', 'horizontal' aligns element accordingly
1128 | * @return {Object/p5.Element} pointer to p5.Element
1129 | * @example
1130 | *
1131 | * function setup() {
1132 | * var div = createDiv('').size(10,10);
1133 | * div.style('background-color','orange');
1134 | * div.center();
1135 | *
1136 | * }
1137 | *
1138 | */
1139 | p5.Element.prototype.center = function(align) {
1140 | var style = this.elt.style.display;
1141 | var hidden = this.elt.style.display === 'none';
1142 | var parentHidden = this.parent().style.display === 'none';
1143 | var pos = { x : this.elt.offsetLeft, y : this.elt.offsetTop };
1144 |
1145 | if (hidden) this.show();
1146 |
1147 | this.elt.style.display = 'block';
1148 | this.position(0,0);
1149 |
1150 | if (parentHidden) this.parent().style.display = 'block';
1151 |
1152 | var wOffset = Math.abs(this.parent().offsetWidth - this.elt.offsetWidth);
1153 | var hOffset = Math.abs(this.parent().offsetHeight - this.elt.offsetHeight);
1154 | var y = pos.y;
1155 | var x = pos.x;
1156 |
1157 | if (align === 'both' || align === undefined){
1158 | this.position(wOffset/2, hOffset/2);
1159 | }else if (align === 'horizontal'){
1160 | this.position(wOffset/2, y);
1161 | }else if (align === 'vertical'){
1162 | this.position(x, hOffset/2);
1163 | }
1164 |
1165 | this.style('display', style);
1166 |
1167 | if (hidden) this.hide();
1168 |
1169 | if (parentHidden) this.parent().style.display = 'none';
1170 |
1171 | return this;
1172 | };
1173 |
1174 | /**
1175 | *
1176 | * If an argument is given, sets the inner HTML of the element,
1177 | * replacing any existing html. If no arguments are given, returns
1178 | * the inner HTML of the element.
1179 | *
1180 | * @for p5.Element
1181 | * @method html
1182 | * @param {String} [html] the HTML to be placed inside the element
1183 | * @return {Object/p5.Element|String}
1184 | * @example
1185 | *
1186 | * var div = createDiv('').size(100,100);
1187 | * div.style('background-color','orange');
1188 | * div.html('hi');
1189 | *
1190 | */
1191 | p5.Element.prototype.html = function(html) {
1192 | if (typeof html !== 'undefined') {
1193 | this.elt.innerHTML = html;
1194 | return this;
1195 | } else {
1196 | return this.elt.innerHTML;
1197 | }
1198 | };
1199 |
1200 | /**
1201 | *
1202 | * Sets the position of the element relative to (0, 0) of the
1203 | * window. Essentially, sets position:absolute and left and top
1204 | * properties of style. If no arguments given returns the x and y position
1205 | * of the element in an object.
1206 | *
1207 | * @method position
1208 | * @param {Number} [x] x-position relative to upper left of window
1209 | * @param {Number} [y] y-position relative to upper left of window
1210 | * @return {Object/p5.Element}
1211 | * @example
1212 | *
1213 | * function setup() {
1214 | * var cnv = createCanvas(100, 100);
1215 | * // positions canvas 50px to the right and 100px
1216 | * // below upper left corner of the window
1217 | * cnv.position(50, 100);
1218 | * }
1219 | *
1220 | */
1221 | p5.Element.prototype.position = function() {
1222 | if (arguments.length === 0){
1223 | return { 'x' : this.elt.offsetLeft , 'y' : this.elt.offsetTop };
1224 | }else{
1225 | this.elt.style.position = 'absolute';
1226 | this.elt.style.left = arguments[0]+'px';
1227 | this.elt.style.top = arguments[1]+'px';
1228 | this.x = arguments[0];
1229 | this.y = arguments[1];
1230 | return this;
1231 | }
1232 | };
1233 |
1234 | /* Helper method called by p5.Element.style() */
1235 | p5.Element.prototype._translate = function(){
1236 | this.elt.style.position = 'absolute';
1237 | // save out initial non-translate transform styling
1238 | var transform = '';
1239 | if (this.elt.style.transform) {
1240 | transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
1241 | transform = transform.replace(/translate[X-Z]?\(.*\)/g, '');
1242 | }
1243 | if (arguments.length === 2) {
1244 | this.elt.style.transform = 'translate('+arguments[0]+'px, '+arguments[1]+'px)';
1245 | } else if (arguments.length > 2) {
1246 | this.elt.style.transform = 'translate3d('+arguments[0]+'px,'+arguments[1]+'px,'+arguments[2]+'px)';
1247 | if (arguments.length === 3) {
1248 | this.elt.parentElement.style.perspective = '1000px';
1249 | } else {
1250 | this.elt.parentElement.style.perspective = arguments[3]+'px';
1251 | }
1252 | }
1253 | // add any extra transform styling back on end
1254 | this.elt.style.transform += transform;
1255 | return this;
1256 | };
1257 |
1258 | /* Helper method called by p5.Element.style() */
1259 | p5.Element.prototype._rotate = function(){
1260 | // save out initial non-rotate transform styling
1261 | var transform = '';
1262 | if (this.elt.style.transform) {
1263 | var transform = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
1264 | transform = transform.replace(/rotate[X-Z]?\(.*\)/g, '');
1265 | }
1266 |
1267 | if (arguments.length === 1){
1268 | this.elt.style.transform = 'rotate('+arguments[0]+'deg)';
1269 | }else if (arguments.length === 2){
1270 | this.elt.style.transform = 'rotate('+arguments[0]+'deg, '+arguments[1]+'deg)';
1271 | }else if (arguments.length === 3){
1272 | this.elt.style.transform = 'rotateX('+arguments[0]+'deg)';
1273 | this.elt.style.transform += 'rotateY('+arguments[1]+'deg)';
1274 | this.elt.style.transform += 'rotateZ('+arguments[2]+'deg)';
1275 | }
1276 | // add remaining transform back on
1277 | this.elt.style.transform += transform;
1278 | return this;
1279 | };
1280 |
1281 | /**
1282 | * Sets the given style (css) property (1st arg) of the element with the
1283 | * given value (2nd arg). If a single argument is given, .style()
1284 | * returns the value of the given property; however, if the single argument
1285 | * is given in css syntax ('text-align:center'), .style() sets the css
1286 | * appropriatly. .style() also handles 2d and 3d css transforms. If
1287 | * the 1st arg is 'rotate', 'translate', or 'position', the following arguments
1288 | * accept Numbers as values. ('translate', 10, 100, 50);
1289 | *
1290 | * @method style
1291 | * @param {String} property property to be set
1292 | * @param {String|Number|p5.Color} [value] value to assign to property
1293 | * @param {String|Number} [value] value to assign to property (rotate/translate)
1294 | * @param {String|Number} [value] value to assign to property (rotate/translate)
1295 | * @param {String|Number} [value] value to assign to property (translate)
1296 | * @return {String|Object/p5.Element} value of property, if no value is specified
1297 | * or p5.Element
1298 | * @example
1299 | *
1300 | * var myDiv = createDiv("I like pandas.");
1301 | * myDiv.style("font-size", "18px");
1302 | * myDiv.style("color", "#ff0000");
1303 | *
1304 | *
1305 | * var col = color(25,23,200,50);
1306 | * var button = createButton("button");
1307 | * button.style("background-color", col);
1308 | * button.position(10, 10);
1309 | *
1310 | *
1311 | * var myDiv = createDiv("I like lizards.");
1312 | * myDiv.style("position", 20, 20);
1313 | * myDiv.style("rotate", 45);
1314 | *
1315 | *
1316 | * var myDiv;
1317 | * function setup() {
1318 | * background(200);
1319 | * myDiv = createDiv("I like gray.");
1320 | * myDiv.position(20, 20);
1321 | * }
1322 | *
1323 | * function draw() {
1324 | * myDiv.style("font-size", mouseX+"px");
1325 | * }
1326 | *
1327 | */
1328 | p5.Element.prototype.style = function(prop, val) {
1329 | var self = this;
1330 |
1331 | if (val instanceof p5.Color) {
1332 | val = 'rgba(' + val.levels[0] + ',' + val.levels[1] + ',' + val.levels[2] + ',' + val.levels[3]/255 + ')'
1333 | }
1334 |
1335 | if (typeof val === 'undefined') {
1336 | if (prop.indexOf(':') === -1) {
1337 | var styles = window.getComputedStyle(self.elt);
1338 | var style = styles.getPropertyValue(prop);
1339 | return style;
1340 | } else {
1341 | var attrs = prop.split(';');
1342 | for (var i = 0; i < attrs.length; i++) {
1343 | var parts = attrs[i].split(':');
1344 | if (parts[0] && parts[1]) {
1345 | this.elt.style[parts[0].trim()] = parts[1].trim();
1346 | }
1347 | }
1348 | }
1349 | } else {
1350 | if (prop === 'rotate' || prop === 'translate' || prop === 'position'){
1351 | var trans = Array.prototype.shift.apply(arguments);
1352 | var f = this[trans] || this['_'+trans];
1353 | f.apply(this, arguments);
1354 | } else {
1355 | this.elt.style[prop] = val;
1356 | if (prop === 'width' || prop === 'height' || prop === 'left' || prop === 'top') {
1357 | var numVal = val.replace(/\D+/g, '');
1358 | this[prop] = parseInt(numVal, 10); // pend: is this necessary?
1359 | }
1360 | }
1361 | }
1362 | return this;
1363 | };
1364 |
1365 |
1366 | /**
1367 | *
1368 | * Adds a new attribute or changes the value of an existing attribute
1369 | * on the specified element. If no value is specified, returns the
1370 | * value of the given attribute, or null if attribute is not set.
1371 | *
1372 | * @method attribute
1373 | * @param {String} attr attribute to set
1374 | * @param {String} [value] value to assign to attribute
1375 | * @return {String|Object/p5.Element} value of attribute, if no value is
1376 | * specified or p5.Element
1377 | * @example
1378 | *
1379 | * var myDiv = createDiv("I like pandas.");
1380 | * myDiv.attribute("align", "center");
1381 | *
1382 | */
1383 | p5.Element.prototype.attribute = function(attr, value) {
1384 | if (typeof value === 'undefined') {
1385 | return this.elt.getAttribute(attr);
1386 | } else {
1387 | this.elt.setAttribute(attr, value);
1388 | return this;
1389 | }
1390 | };
1391 |
1392 |
1393 | /**
1394 | *
1395 | * Removes an attribute on the specified element.
1396 | *
1397 | * @method removeAttribute
1398 | * @param {String} attr attribute to remove
1399 | * @return {Object/p5.Element}
1400 | *
1401 | * @example
1402 | *
1403 | * var button;
1404 | * var checkbox;
1405 | *
1406 | * function setup() {
1407 | * checkbox = createCheckbox('enable', true);
1408 | * checkbox.changed(enableButton);
1409 | * button = createButton('button');
1410 | * button.position(10, 10);
1411 | * }
1412 | *
1413 | * function enableButton() {
1414 | * if( this.checked() ) {
1415 | * // Re-enable the button
1416 | * button.removeAttribute('disabled');
1417 | * } else {
1418 | * // Disable the button
1419 | * button.attribute('disabled','');
1420 | * }
1421 | * }
1422 | *
1423 | */
1424 | p5.Element.prototype.removeAttribute = function(attr) {
1425 | this.elt.removeAttribute(attr);
1426 | return this;
1427 | };
1428 |
1429 |
1430 | /**
1431 | * Either returns the value of the element if no arguments
1432 | * given, or sets the value of the element.
1433 | *
1434 | * @method value
1435 | * @param {String|Number} [value]
1436 | * @return {String|Object/p5.Element} value of element if no value is specified or p5.Element
1437 | * @example
1438 | *
1439 | * // gets the value
1440 | * var inp;
1441 | * function setup() {
1442 | * inp = createInput('');
1443 | * }
1444 | *
1445 | * function mousePressed() {
1446 | * print(inp.value());
1447 | * }
1448 | *
1449 | *
1450 | * // sets the value
1451 | * var inp;
1452 | * function setup() {
1453 | * inp = createInput('myValue');
1454 | * }
1455 | *
1456 | * function mousePressed() {
1457 | * inp.value("myValue");
1458 | * }
1459 | *
1460 | */
1461 | p5.Element.prototype.value = function() {
1462 | if (arguments.length > 0) {
1463 | this.elt.value = arguments[0];
1464 | return this;
1465 | } else {
1466 | if (this.elt.type === 'range') {
1467 | return parseFloat(this.elt.value);
1468 | }
1469 | else return this.elt.value;
1470 | }
1471 | };
1472 |
1473 | /**
1474 | *
1475 | * Shows the current element. Essentially, setting display:block for the style.
1476 | *
1477 | * @method show
1478 | * @return {Object/p5.Element}
1479 | * @example
1480 | *
1481 | * var div = createDiv('div');
1482 | * div.style("display", "none");
1483 | * div.show(); // turns display to block
1484 | *
1485 | */
1486 | p5.Element.prototype.show = function() {
1487 | this.elt.style.display = 'block';
1488 | return this;
1489 | };
1490 |
1491 | /**
1492 | * Hides the current element. Essentially, setting display:none for the style.
1493 | *
1494 | * @method hide
1495 | * @return {Object/p5.Element}
1496 | * @example
1497 | *
1498 | * var div = createDiv('this is a div');
1499 | * div.hide();
1500 | *
1501 | */
1502 | p5.Element.prototype.hide = function() {
1503 | this.elt.style.display = 'none';
1504 | return this;
1505 | };
1506 |
1507 | /**
1508 | *
1509 | * Sets the width and height of the element. AUTO can be used to
1510 | * only adjust one dimension. If no arguments given returns the width and height
1511 | * of the element in an object.
1512 | *
1513 | * @method size
1514 | * @param {Number} [w] width of the element
1515 | * @param {Number} [h] height of the element
1516 | * @return {Object/p5.Element}
1517 | * @example
1518 | *
1519 | * var div = createDiv('this is a div');
1520 | * div.size(100, 100);
1521 | *
1522 | */
1523 | p5.Element.prototype.size = function(w, h) {
1524 | if (arguments.length === 0){
1525 | return { 'width' : this.elt.offsetWidth , 'height' : this.elt.offsetHeight };
1526 | }else{
1527 | var aW = w;
1528 | var aH = h;
1529 | var AUTO = p5.prototype.AUTO;
1530 | if (aW !== AUTO || aH !== AUTO) {
1531 | if (aW === AUTO) {
1532 | aW = h * this.width / this.height;
1533 | } else if (aH === AUTO) {
1534 | aH = w * this.height / this.width;
1535 | }
1536 | // set diff for cnv vs normal div
1537 | if (this.elt instanceof HTMLCanvasElement) {
1538 | var j = {};
1539 | var k = this.elt.getContext('2d');
1540 | for (var prop in k) {
1541 | j[prop] = k[prop];
1542 | }
1543 | this.elt.setAttribute('width', aW * this._pInst._pixelDensity);
1544 | this.elt.setAttribute('height', aH * this._pInst._pixelDensity);
1545 | this.elt.setAttribute('style', 'width:' + aW + 'px; height:' + aH + 'px');
1546 | this._pInst.scale(this._pInst._pixelDensity, this._pInst._pixelDensity);
1547 | for (var prop in j) {
1548 | this.elt.getContext('2d')[prop] = j[prop];
1549 | }
1550 | } else {
1551 | this.elt.style.width = aW+'px';
1552 | this.elt.style.height = aH+'px';
1553 | this.elt.width = aW;
1554 | this.elt.height = aH;
1555 | this.width = aW;
1556 | this.height = aH;
1557 | }
1558 |
1559 | this.width = this.elt.offsetWidth;
1560 | this.height = this.elt.offsetHeight;
1561 |
1562 | if (this._pInst) { // main canvas associated with p5 instance
1563 | if (this._pInst._curElement.elt === this.elt) {
1564 | this._pInst._setProperty('width', this.elt.offsetWidth);
1565 | this._pInst._setProperty('height', this.elt.offsetHeight);
1566 | }
1567 | }
1568 | }
1569 | return this;
1570 | }
1571 | };
1572 |
1573 | /**
1574 | * Removes the element and deregisters all listeners.
1575 | * @method remove
1576 | * @example
1577 | *
1578 | * var myDiv = createDiv('this is some text');
1579 | * myDiv.remove();
1580 | *
1581 | */
1582 | p5.Element.prototype.remove = function() {
1583 | // deregister events
1584 | for (var ev in this._events) {
1585 | this.elt.removeEventListener(ev, this._events[ev]);
1586 | }
1587 | if (this.elt.parentNode) {
1588 | this.elt.parentNode.removeChild(this.elt);
1589 | }
1590 | delete(this);
1591 | };
1592 |
1593 |
1594 |
1595 | // =============================================================================
1596 | // p5.MediaElement additions
1597 | // =============================================================================
1598 |
1599 |
1600 | /**
1601 | * Extends p5.Element to handle audio and video. In addition to the methods
1602 | * of p5.Element, it also contains methods for controlling media. It is not
1603 | * called directly, but p5.MediaElements are created by calling createVideo,
1604 | * createAudio, and createCapture.
1605 | *
1606 | * @class p5.MediaElement
1607 | * @constructor
1608 | * @param {String} elt DOM node that is wrapped
1609 | * @param {Object} [pInst] pointer to p5 instance
1610 | */
1611 | p5.MediaElement = function(elt, pInst) {
1612 | p5.Element.call(this, elt, pInst);
1613 |
1614 | var self = this;
1615 | this.elt.crossOrigin = 'anonymous';
1616 |
1617 | this._prevTime = 0;
1618 | this._cueIDCounter = 0;
1619 | this._cues = [];
1620 | this._pixelDensity = 1;
1621 |
1622 | /**
1623 | * Path to the media element source.
1624 | *
1625 | * @property src
1626 | * @return {String} src
1627 | */
1628 | Object.defineProperty(self, 'src', {
1629 | get: function() {
1630 | var firstChildSrc = self.elt.children[0].src;
1631 | var srcVal = self.elt.src === window.location.href ? '' : self.elt.src;
1632 | var ret = firstChildSrc === window.location.href ? srcVal : firstChildSrc;
1633 | return ret;
1634 | },
1635 | set: function(newValue) {
1636 | for (var i = 0; i < self.elt.children.length; i++) {
1637 | self.elt.removeChild(self.elt.children[i]);
1638 | }
1639 | var source = document.createElement('source');
1640 | source.src = newValue;
1641 | elt.appendChild(source);
1642 | self.elt.src = newValue;
1643 | },
1644 | });
1645 |
1646 | // private _onended callback, set by the method: onended(callback)
1647 | self._onended = function() {};
1648 | self.elt.onended = function() {
1649 | self._onended(self);
1650 | }
1651 | };
1652 | p5.MediaElement.prototype = Object.create(p5.Element.prototype);
1653 |
1654 |
1655 |
1656 |
1657 | /**
1658 | * Play an HTML5 media element.
1659 | *
1660 | * @method play
1661 | * @return {Object/p5.Element}
1662 | */
1663 | p5.MediaElement.prototype.play = function() {
1664 | if (this.elt.currentTime === this.elt.duration) {
1665 | this.elt.currentTime = 0;
1666 | }
1667 |
1668 | if (this.elt.readyState > 1) {
1669 | this.elt.play();
1670 | } else {
1671 | // in Chrome, playback cannot resume after being stopped and must reload
1672 | this.elt.load();
1673 | this.elt.play();
1674 | }
1675 | return this;
1676 | };
1677 |
1678 | /**
1679 | * Stops an HTML5 media element (sets current time to zero).
1680 | *
1681 | * @method stop
1682 | * @return {Object/p5.Element}
1683 | */
1684 | p5.MediaElement.prototype.stop = function() {
1685 | this.elt.pause();
1686 | this.elt.currentTime = 0;
1687 | return this;
1688 | };
1689 |
1690 | /**
1691 | * Pauses an HTML5 media element.
1692 | *
1693 | * @method pause
1694 | * @return {Object/p5.Element}
1695 | */
1696 | p5.MediaElement.prototype.pause = function() {
1697 | this.elt.pause();
1698 | return this;
1699 | };
1700 |
1701 | /**
1702 | * Set 'loop' to true for an HTML5 media element, and starts playing.
1703 | *
1704 | * @method loop
1705 | * @return {Object/p5.Element}
1706 | */
1707 | p5.MediaElement.prototype.loop = function() {
1708 | this.elt.setAttribute('loop', true);
1709 | this.play();
1710 | return this;
1711 | };
1712 | /**
1713 | * Set 'loop' to false for an HTML5 media element. Element will stop
1714 | * when it reaches the end.
1715 | *
1716 | * @method noLoop
1717 | * @return {Object/p5.Element}
1718 | */
1719 | p5.MediaElement.prototype.noLoop = function() {
1720 | this.elt.setAttribute('loop', false);
1721 | return this;
1722 | };
1723 |
1724 |
1725 | /**
1726 | * Set HTML5 media element to autoplay or not.
1727 | *
1728 | * @method autoplay
1729 | * @param {Boolean} autoplay whether the element should autoplay
1730 | * @return {Object/p5.Element}
1731 | */
1732 | p5.MediaElement.prototype.autoplay = function(val) {
1733 | this.elt.setAttribute('autoplay', val);
1734 | return this;
1735 | };
1736 |
1737 | /**
1738 | * Sets volume for this HTML5 media element. If no argument is given,
1739 | * returns the current volume.
1740 | *
1741 | * @param {Number} [val] volume between 0.0 and 1.0
1742 | * @return {Number|p5.MediaElement} current volume or p5.MediaElement
1743 | * @method volume
1744 | */
1745 | p5.MediaElement.prototype.volume = function(val) {
1746 | if (typeof val === 'undefined') {
1747 | return this.elt.volume;
1748 | } else {
1749 | this.elt.volume = val;
1750 | }
1751 | };
1752 |
1753 | /**
1754 | * If no arguments are given, returns the current playback speed of the
1755 | * element. The speed parameter sets the speed where 2.0 will play the
1756 | * element twice as fast, 0.5 will play at half the speed, and -1 will play
1757 | * the element in normal speed in reverse.(Note that not all browsers support
1758 | * backward playback and even if they do, playback might not be smooth.)
1759 | *
1760 | * @method speed
1761 | * @param {Number} [speed] speed multiplier for element playback
1762 | * @return {Number|Object/p5.MediaElement} current playback speed or p5.MediaElement
1763 | */
1764 | p5.MediaElement.prototype.speed = function(val) {
1765 | if (typeof val === 'undefined') {
1766 | return this.elt.playbackRate;
1767 | } else {
1768 | this.elt.playbackRate = val;
1769 | }
1770 | };
1771 |
1772 | /**
1773 | * If no arguments are given, returns the current time of the element.
1774 | * If an argument is given the current time of the element is set to it.
1775 | *
1776 | * @method time
1777 | * @param {Number} [time] time to jump to (in seconds)
1778 | * @return {Number|Object/p5.MediaElement} current time (in seconds)
1779 | * or p5.MediaElement
1780 | */
1781 | p5.MediaElement.prototype.time = function(val) {
1782 | if (typeof val === 'undefined') {
1783 | return this.elt.currentTime;
1784 | } else {
1785 | this.elt.currentTime = val;
1786 | }
1787 | };
1788 |
1789 | /**
1790 | * Returns the duration of the HTML5 media element.
1791 | *
1792 | * @method duration
1793 | * @return {Number} duration
1794 | */
1795 | p5.MediaElement.prototype.duration = function() {
1796 | return this.elt.duration;
1797 | };
1798 | p5.MediaElement.prototype.pixels = [];
1799 | p5.MediaElement.prototype.loadPixels = function() {
1800 | if (!this.canvas) {
1801 | this.canvas = document.createElement('canvas');
1802 | this.drawingContext = this.canvas.getContext('2d');
1803 | }
1804 | if (this.loadedmetadata) { // wait for metadata for w/h
1805 | if (this.canvas.width !== this.elt.width) {
1806 | this.canvas.width = this.elt.width;
1807 | this.canvas.height = this.elt.height;
1808 | this.width = this.canvas.width;
1809 | this.height = this.canvas.height;
1810 | }
1811 | this.drawingContext.drawImage(this.elt, 0, 0, this.canvas.width, this.canvas.height);
1812 | p5.Renderer2D.prototype.loadPixels.call(this);
1813 | }
1814 | return this;
1815 | }
1816 | p5.MediaElement.prototype.updatePixels = function(x, y, w, h){
1817 | if (this.loadedmetadata) { // wait for metadata
1818 | p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
1819 | }
1820 | return this;
1821 | }
1822 | p5.MediaElement.prototype.get = function(x, y, w, h){
1823 | if (this.loadedmetadata) { // wait for metadata
1824 | return p5.Renderer2D.prototype.get.call(this, x, y, w, h);
1825 | } else if (!x) {
1826 | return new p5.Image(1, 1);
1827 | } else {
1828 | return [0, 0, 0, 255];
1829 | }
1830 | };
1831 | p5.MediaElement.prototype.set = function(x, y, imgOrCol){
1832 | if (this.loadedmetadata) { // wait for metadata
1833 | p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
1834 | }
1835 | };
1836 | p5.MediaElement.prototype.copy = function(){
1837 | p5.Renderer2D.prototype.copy.apply(this, arguments);
1838 | };
1839 | p5.MediaElement.prototype.mask = function(){
1840 | this.loadPixels();
1841 | p5.Image.prototype.mask.apply(this, arguments);
1842 | };
1843 | /**
1844 | * Schedule an event to be called when the audio or video
1845 | * element reaches the end. If the element is looping,
1846 | * this will not be called. The element is passed in
1847 | * as the argument to the onended callback.
1848 | *
1849 | * @method onended
1850 | * @param {Function} callback function to call when the
1851 | * soundfile has ended. The
1852 | * media element will be passed
1853 | * in as the argument to the
1854 | * callback.
1855 | * @return {Object/p5.MediaElement}
1856 | * @example
1857 | *
1858 | * function setup() {
1859 | * audioEl = createAudio('assets/beat.mp3');
1860 | * audioEl.showControls(true);
1861 | * audioEl.onended(sayDone);
1862 | * }
1863 | *
1864 | * function sayDone(elt) {
1865 | * alert('done playing ' + elt.src );
1866 | * }
1867 | *
1868 | */
1869 | p5.MediaElement.prototype.onended = function(callback) {
1870 | this._onended = callback;
1871 | return this;
1872 | };
1873 |
1874 |
1875 | /*** CONNECT TO WEB AUDIO API / p5.sound.js ***/
1876 |
1877 | /**
1878 | * Send the audio output of this element to a specified audioNode or
1879 | * p5.sound object. If no element is provided, connects to p5's master
1880 | * output. That connection is established when this method is first called.
1881 | * All connections are removed by the .disconnect() method.
1882 | *
1883 | * This method is meant to be used with the p5.sound.js addon library.
1884 | *
1885 | * @method connect
1886 | * @param {AudioNode|p5.sound object} audioNode AudioNode from the Web Audio API,
1887 | * or an object from the p5.sound library
1888 | */
1889 | p5.MediaElement.prototype.connect = function(obj) {
1890 | var audioContext, masterOutput;
1891 |
1892 | // if p5.sound exists, same audio context
1893 | if (typeof p5.prototype.getAudioContext === 'function') {
1894 | audioContext = p5.prototype.getAudioContext();
1895 | masterOutput = p5.soundOut.input;
1896 | } else {
1897 | try {
1898 | audioContext = obj.context;
1899 | masterOutput = audioContext.destination
1900 | } catch(e) {
1901 | throw 'connect() is meant to be used with Web Audio API or p5.sound.js'
1902 | }
1903 | }
1904 |
1905 | // create a Web Audio MediaElementAudioSourceNode if none already exists
1906 | if (!this.audioSourceNode) {
1907 | this.audioSourceNode = audioContext.createMediaElementSource(this.elt);
1908 |
1909 | // connect to master output when this method is first called
1910 | this.audioSourceNode.connect(masterOutput);
1911 | }
1912 |
1913 | // connect to object if provided
1914 | if (obj) {
1915 | if (obj.input) {
1916 | this.audioSourceNode.connect(obj.input);
1917 | } else {
1918 | this.audioSourceNode.connect(obj);
1919 | }
1920 | }
1921 |
1922 | // otherwise connect to master output of p5.sound / AudioContext
1923 | else {
1924 | this.audioSourceNode.connect(masterOutput);
1925 | }
1926 |
1927 | };
1928 |
1929 | /**
1930 | * Disconnect all Web Audio routing, including to master output.
1931 | * This is useful if you want to re-route the output through
1932 | * audio effects, for example.
1933 | *
1934 | * @method disconnect
1935 | */
1936 | p5.MediaElement.prototype.disconnect = function() {
1937 | if (this.audioSourceNode) {
1938 | this.audioSourceNode.disconnect();
1939 | } else {
1940 | throw 'nothing to disconnect';
1941 | }
1942 | };
1943 |
1944 |
1945 | /*** SHOW / HIDE CONTROLS ***/
1946 |
1947 | /**
1948 | * Show the default MediaElement controls, as determined by the web browser.
1949 | *
1950 | * @method showControls
1951 | */
1952 | p5.MediaElement.prototype.showControls = function() {
1953 | // must set style for the element to show on the page
1954 | this.elt.style['text-align'] = 'inherit';
1955 | this.elt.controls = true;
1956 | };
1957 |
1958 | /**
1959 | * Hide the default mediaElement controls.
1960 | *
1961 | * @method hideControls
1962 | */
1963 | p5.MediaElement.prototype.hideControls = function() {
1964 | this.elt.controls = false;
1965 | };
1966 |
1967 | /*** SCHEDULE EVENTS ***/
1968 |
1969 | /**
1970 | * Schedule events to trigger every time a MediaElement
1971 | * (audio/video) reaches a playback cue point.
1972 | *
1973 | * Accepts a callback function, a time (in seconds) at which to trigger
1974 | * the callback, and an optional parameter for the callback.
1975 | *
1976 | * Time will be passed as the first parameter to the callback function,
1977 | * and param will be the second parameter.
1978 | *
1979 | *
1980 | * @method addCue
1981 | * @param {Number} time Time in seconds, relative to this media
1982 | * element's playback. For example, to trigger
1983 | * an event every time playback reaches two
1984 | * seconds, pass in the number 2. This will be
1985 | * passed as the first parameter to
1986 | * the callback function.
1987 | * @param {Function} callback Name of a function that will be
1988 | * called at the given time. The callback will
1989 | * receive time and (optionally) param as its
1990 | * two parameters.
1991 | * @param {Object} [value] An object to be passed as the
1992 | * second parameter to the
1993 | * callback function.
1994 | * @return {Number} id ID of this cue,
1995 | * useful for removeCue(id)
1996 | * @example
1997 | *
1998 | * function setup() {
1999 | * background(255,255,255);
2000 | *
2001 | * audioEl = createAudio('assets/beat.mp3');
2002 | * audioEl.showControls();
2003 | *
2004 | * // schedule three calls to changeBackground
2005 | * audioEl.addCue(0.5, changeBackground, color(255,0,0) );
2006 | * audioEl.addCue(1.0, changeBackground, color(0,255,0) );
2007 | * audioEl.addCue(2.5, changeBackground, color(0,0,255) );
2008 | * audioEl.addCue(3.0, changeBackground, color(0,255,255) );
2009 | * audioEl.addCue(4.2, changeBackground, color(255,255,0) );
2010 | * audioEl.addCue(5.0, changeBackground, color(255,255,0) );
2011 | * }
2012 | *
2013 | * function changeBackground(val) {
2014 | * background(val);
2015 | * }
2016 | *
2017 | */
2018 | p5.MediaElement.prototype.addCue = function(time, callback, val) {
2019 | var id = this._cueIDCounter++;
2020 |
2021 | var cue = new Cue(callback, time, id, val);
2022 | this._cues.push(cue);
2023 |
2024 | if (!this.elt.ontimeupdate) {
2025 | this.elt.ontimeupdate = this._onTimeUpdate.bind(this);
2026 | }
2027 |
2028 | return id;
2029 | };
2030 |
2031 | /**
2032 | * Remove a callback based on its ID. The ID is returned by the
2033 | * addCue method.
2034 | *
2035 | * @method removeCue
2036 | * @param {Number} id ID of the cue, as returned by addCue
2037 | */
2038 | p5.MediaElement.prototype.removeCue = function(id) {
2039 | for (var i = 0; i < this._cues.length; i++) {
2040 | var cue = this._cues[i];
2041 | if (cue.id === id) {
2042 | this.cues.splice(i, 1);
2043 | }
2044 | }
2045 |
2046 | if (this._cues.length === 0) {
2047 | this.elt.ontimeupdate = null
2048 | }
2049 | };
2050 |
2051 | /**
2052 | * Remove all of the callbacks that had originally been scheduled
2053 | * via the addCue method.
2054 | *
2055 | * @method clearCues
2056 | */
2057 | p5.MediaElement.prototype.clearCues = function() {
2058 | this._cues = [];
2059 | this.elt.ontimeupdate = null;
2060 | };
2061 |
2062 | // private method that checks for cues to be fired if events
2063 | // have been scheduled using addCue(callback, time).
2064 | p5.MediaElement.prototype._onTimeUpdate = function() {
2065 | var playbackTime = this.time();
2066 |
2067 | for (var i = 0 ; i < this._cues.length; i++) {
2068 | var callbackTime = this._cues[i].time;
2069 | var val = this._cues[i].val;
2070 |
2071 |
2072 | if (this._prevTime < callbackTime && callbackTime <= playbackTime) {
2073 |
2074 | // pass the scheduled callbackTime as parameter to the callback
2075 | this._cues[i].callback(val);
2076 | }
2077 |
2078 | }
2079 |
2080 | this._prevTime = playbackTime;
2081 | };
2082 |
2083 |
2084 | // Cue inspired by JavaScript setTimeout, and the
2085 | // Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org
2086 | var Cue = function(callback, time, id, val) {
2087 | this.callback = callback;
2088 | this.time = time;
2089 | this.id = id;
2090 | this.val = val;
2091 | };
2092 |
2093 | // =============================================================================
2094 | // p5.File
2095 | // =============================================================================
2096 |
2097 |
2098 | /**
2099 | * Base class for a file
2100 | * Using this for createFileInput
2101 | *
2102 | * @class p5.File
2103 | * @constructor
2104 | * @param {File} file File that is wrapped
2105 | * @param {Object} [pInst] pointer to p5 instance
2106 | */
2107 | p5.File = function(file, pInst) {
2108 | /**
2109 | * Underlying File object. All normal File methods can be called on this.
2110 | *
2111 | * @property file
2112 | */
2113 | this.file = file;
2114 |
2115 | this._pInst = pInst;
2116 |
2117 | // Splitting out the file type into two components
2118 | // This makes determining if image or text etc simpler
2119 | var typeList = file.type.split('/');
2120 | /**
2121 | * File type (image, text, etc.)
2122 | *
2123 | * @property type
2124 | */
2125 | this.type = typeList[0];
2126 | /**
2127 | * File subtype (usually the file extension jpg, png, xml, etc.)
2128 | *
2129 | * @property subtype
2130 | */
2131 | this.subtype = typeList[1];
2132 | /**
2133 | * File name
2134 | *
2135 | * @property name
2136 | */
2137 | this.name = file.name;
2138 | /**
2139 | * File size
2140 | *
2141 | * @property size
2142 | */
2143 | this.size = file.size;
2144 |
2145 | /**
2146 | * URL string containing image data.
2147 | *
2148 | * @property data
2149 | */
2150 | this.data = undefined;
2151 | };
2152 |
2153 | }));
2154 |
--------------------------------------------------------------------------------
/managers/entitymanager.js:
--------------------------------------------------------------------------------
1 | function EntityManager() {
2 | var entities = {};
3 | var nextId = 0;
4 |
5 | this.add = function(entity) {
6 | if (entity.id === -1) {
7 | entity.registerId(nextId++);
8 | }
9 |
10 | entities[entity.id] = entity;
11 | }
12 |
13 | this.update = function() {
14 | for (var index in entities) {
15 | if (entities[index].update()) {
16 | // Deletes the index property from the entities object.
17 | delete entities[index];
18 | }
19 | }
20 | }
21 |
22 | this.render = function() {
23 | for (var index in entities) {
24 | var entity = entities[index];
25 | var playerPos = world.getLocalPlayer().getEntity().pos;
26 | var relPos = p5.Vector.sub(entity.pos, playerPos);
27 | var halfWinWid = windowWidth / 2;
28 | var halfWinHig = windowHeight / 2;
29 |
30 | var shouldRender = !(
31 | (relPos.x - entity.r > halfWinWid) ||
32 | (relPos.x + entity.r < -halfWinWid) ||
33 | (relPos.y - entity.r > halfWinHig) ||
34 | (relPos.y + entity.r < -halfWinHig)
35 | );
36 |
37 | push();
38 | if (shouldRender && !entity.dead) {
39 | entity.render();
40 | }
41 | pop();
42 | }
43 | }
44 |
45 | this.checkCollisions = function() {
46 | var x = 1;
47 | for (var i in entities) {
48 | var y = x;
49 | for (var j in entities) {
50 | // Skip all the collisions we have already done.
51 | if (y !== 0) {
52 | y--;
53 | continue;
54 | }
55 |
56 | if (entities[j].collides(entities[i]) || entities[i].collides(entities[j])) {
57 | entities[i].collision(entities[j]);
58 | entities[j].collision(entities[i]);
59 | }
60 | }
61 | x++;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/managers/levelmanager.js:
--------------------------------------------------------------------------------
1 | function LevelManager(world, level) {
2 | var asteroids = 0;
3 | var level = 0;
4 | var score = 0;
5 | var scope = this;
6 |
7 | this.recordKill = function(killerId, destroyedArea) {
8 | asteroids--;
9 | if (killerId !== -1) {
10 | world.getPlayer(killerId).score += max(0, floor(destroyedArea / 100));
11 | }
12 | }
13 |
14 | this.recordAsteroidCreation = function() {
15 | asteroids++;
16 | }
17 |
18 | this.update = function(players) {
19 | if (asteroids === 0) {
20 | level++;
21 | for (var i = 0; i < players.length; i++) {
22 | if (players[i].dead) {
23 | return;
24 | }
25 |
26 | players[i].getEntity().regenShields();
27 | }
28 |
29 | for (var i = 0; i < level + 5; i++) {
30 | world.addEndFrameTask(function(world) {
31 | world.createEntity(Asteroid, {
32 | levelmanager: scope
33 | });
34 | });
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/managers/uimanager.js:
--------------------------------------------------------------------------------
1 | function UIManager(world, viewSize) {
2 | var uiElements = [];
3 |
4 | this.create = function(constructor, params) {
5 | uiElements.push(new constructor(uiElements.length, params));
6 | }
7 |
8 | this.update = function() {
9 | var entity = world.getLocalPlayer().getEntity();
10 | camera(entity.pos.x - windowWidth / 2, entity.pos.y - windowHeight / 2, 0);
11 | for (var i = 0; i < uiElements.length; i++) {
12 | uiElements[i].update(world);
13 | }
14 | }
15 |
16 | this.render = function() {
17 | push();
18 | var entity = world.getLocalPlayer().getEntity();
19 | translate(entity.pos.x - windowWidth / 2, entity.pos.y - windowHeight / 2, 0);
20 | for (var i = 0; i < uiElements.length; i++) {
21 | uiElements[i].render(world);
22 | }
23 | pop();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/player.js:
--------------------------------------------------------------------------------
1 | function Player(id, name, world) {
2 | this.id = id;
3 | var ship = new Ship(world, {
4 | pos: createVector(0, 0),
5 | r: 20,
6 | shieldDuration: 180
7 | });
8 | ship.owner = id;
9 | this.score = 0;
10 | this.dead = false;
11 |
12 | this.getEntity = function() {
13 | return ship;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/shape.js:
--------------------------------------------------------------------------------
1 | function Shape(vertices) {
2 |
3 | if (!vertices) vertices = [];
4 |
5 | this.vertices = vertices;
6 | this.frames = 100;
7 | this.frame = -1;
8 | this.speed = 0.5;
9 | this.spin = 0.1;
10 | this.seed = random(millis());
11 | this.area = Shape.calculateArea(vertices);
12 | this.centroid = Shape.trueCenterpoint(vertices, this.area);
13 | this.r = Shape.calculateRadius(vertices, this.centroid);
14 |
15 | this.breakAnime = function(frames, speed, spin) {
16 | if (frames && frames > 0) this.frames = frames;
17 | if (speed) this.speed = speed;
18 | if (spin) this.spin = spin;
19 | this.frame = 0;
20 | }
21 |
22 | this.fade = function() {
23 | if (this.frame < 0) return 255;
24 | else return 255 * (1 - this.frame / this.frames);
25 | }
26 |
27 | this.globalVertices = function(pos, heading) {
28 | var glob_vertices = [];
29 | for (var i = 0; i < this.vertices.length; i++) {
30 | var v = this.vertices[i].copy();
31 | v.rotate(heading);
32 | v.add(pos);
33 | glob_vertices.push(v);
34 | }
35 | return glob_vertices;
36 | }
37 |
38 | this.recenter = function() {
39 | for (var i = 0; i < this.vertices.length; i++) {
40 | this.vertices[i].sub(this.centroid);
41 | }
42 | this.centroid = createVector(0, 0);
43 | }
44 |
45 | this.perimeter = function() {
46 | var per = 0;
47 | for (var i = 0, j = this.vertices.length - 1; i < this.vertices.length; j = i++) {
48 | var vIJ = p5.Vector.sub(this.vertices[j], this.vertices[i]);
49 | per += vIJ.mag();
50 | }
51 | return per;
52 | }
53 |
54 | this.intersections = function(pos, angle, indlist) {
55 | if (indlist === undefined) {
56 | indlist = [];
57 | }
58 | intersections = [];
59 | for (var i = 0; i < this.vertices.length; i++) {
60 | if (lineIntersect2(pos, angle, this.vertices[i], this.vertices[(i + 1) % this.vertices.length])) {
61 | intersections.push(lineIntersectionPoint(pos, p5.Vector.add(pos, p5.Vector.fromAngle(angle)), this.vertices[i], this.vertices[(i + 1) % this.vertices.length]));
62 | indlist.push(i);
63 | }
64 | }
65 | //sorting
66 | for (var n = intersections.length; n > 1; n--) {
67 | for (i = 0; i < n - 1; i++) {
68 | if (dist(intersections[i].x, intersections[i].y, pos.x, pos.y) > dist(intersections[i + 1].x, intersections[i + 1].y, pos.x, pos.y)) {
69 | var temp = intersections[i];
70 | intersections[i] = intersections[i + 1];
71 | intersections[i + 1] = temp;
72 |
73 | var temp2 = indlist[i];
74 | indlist[i] = indlist[i + 1];
75 | indlist[i + 1] = temp2;
76 | }
77 | }
78 | }
79 | return intersections;
80 | }
81 |
82 | this.splitAtWeakestPoint = function() {
83 | var weakestAngle;
84 | var intersectiondist = -1;
85 | var center = this.centroid;
86 | var indList = [];
87 | var splitIntersections = [];
88 | for (angle = random(PI / 15); angle < TWO_PI; angle += PI / 15) {
89 | var list = [];
90 | var intersections = this.intersections(center, angle, list);
91 | if (intersections.length >= 2) {
92 | var dist_ = 0;
93 | for (var i = 0; i < intersections.length; i++) {
94 | dist_ += dist(intersections[i].x, intersections[i].y, center.x, center.y);
95 | }
96 | if (intersectiondist == -1 || dist_ < intersectiondist) {
97 | intersectiondist = dist_;
98 | weakestAngle = angle;
99 | indList = list;
100 | splitIntersections = intersections;
101 | }
102 | }
103 | }
104 |
105 | if (splitIntersections.length > 1) {
106 | var intersectionedge = p5.Vector.sub(splitIntersections[1], splitIntersections[0]);
107 | var crackVertice1 = p5.Vector.add(splitIntersections[0], p5.Vector.div(intersectionedge, 3).rotate(random(-PI / 5, PI / 5)));
108 | var crackVertice2 = p5.Vector.add(splitIntersections[1], p5.Vector.div(intersectionedge, -3).rotate(random(-PI / 5, PI / 5)));
109 |
110 | var newVertices1 = [];
111 | newVertices1.push(splitIntersections[0].copy());
112 | for (i = (indList[0] + 1) % this.vertices.length; i != (indList[1] + 1) % this.vertices.length; i = (i + 1) % this.vertices.length) {
113 | newVertices1.push(this.vertices[i].copy());
114 | }
115 | newVertices1.push(splitIntersections[1].copy());
116 | if (Shape.contains(this.vertices, crackVertice2)) {
117 | newVertices1.push(crackVertice2.copy());
118 | }
119 | if (Shape.contains(this.vertices, crackVertice1)) {
120 | newVertices1.push(crackVertice1.copy());
121 | }
122 |
123 | var newVertices2 = [];
124 | newVertices2.push(splitIntersections[1].copy());
125 | for (i = (indList[1] + 1) % this.vertices.length; i != (indList[0] + 1) % this.vertices.length; i = (i + 1) % this.vertices.length) {
126 | newVertices2.push(this.vertices[i].copy());
127 | }
128 | newVertices2.push(splitIntersections[0].copy());
129 | if (Shape.contains(this.vertices, crackVertice1)) {
130 | newVertices2.push(crackVertice1.copy());
131 | }
132 | if (Shape.contains(this.vertices, crackVertice2)) {
133 | newVertices2.push(crackVertice2.copy());
134 | }
135 |
136 | return [new Shape(newVertices1), new Shape(newVertices2)];
137 | } else {
138 | return [this];
139 | }
140 | }
141 |
142 | this.sub = function(pos1, heading1, pos2, heading2, shape) {
143 | debris = [];
144 |
145 | intersections = [];
146 | stencilVertices = shape.globalVertices(pos2, heading2);
147 | for (var i = 0; i < stencilVertices.length; i++) {
148 | stencilVertices[i].sub(pos1);
149 | stencilVertices[i].rotate(-heading1);
150 | }
151 |
152 | for (i = 0; i < stencilVertices.length; i++) {
153 | for (var j = 0; j < this.vertices.length; j++) {
154 | if (lineIntersect(stencilVertices[i], stencilVertices[(i + 1) % stencilVertices.length], this.vertices[j], this.vertices[(j + 1) % this.vertices.length])) {
155 | var intersection = lineIntersectionPoint(stencilVertices[i], stencilVertices[(i + 1) % stencilVertices.length], this.vertices[j], this.vertices[(j + 1) % this.vertices.length]);
156 | if (intersection !== undefined) {
157 | intersections.push(intersection);
158 | this.vertices.splice((++j) % this.vertices.length, 0, intersection);
159 | stencilVertices.splice((++i) % stencilVertices.length, 0, intersection);
160 | }
161 | }
162 | }
163 | }
164 |
165 | var d = [];
166 | if (intersections.length > 1) {
167 | d.push(intersections[1].copy());
168 | }
169 | for (i = 0; i < this.vertices.length; i++) {
170 | if (Shape.contains(stencilVertices, this.vertices[i]) && intersections.indexOf(this.vertices[i]) === -1) {
171 | d.push(this.vertices.splice(i--, 1)[0].copy());
172 | }
173 | }
174 | if (intersections.length > 1) {
175 | d.push(intersections[0].copy());
176 | }
177 | debris.push(new Shape(d));
178 |
179 | for (i = 0; i < this.vertices.length; i++) {
180 | if (intersections.indexOf(this.vertices[i]) !== -1 && intersections.indexOf(this.vertices[(i + 1) % this.vertices.length]) !== -1) {
181 | var intersection_start = this.vertices[i];
182 | var intersection_end = this.vertices[(i + 1) % this.vertices.length];
183 | for (j = stencilVertices.indexOf(intersection_start); stencilVertices[j] !== intersection_end; j = mod((j - 1), stencilVertices.length)) {
184 | if (intersections.indexOf(stencilVertices[j]) === -1) {
185 | this.vertices.splice(++i, 0, stencilVertices[j].copy());
186 | }
187 | }
188 | }
189 | }
190 |
191 | for (i = 0; i < this.vertices.length - 1; i++) {
192 | for (j = i + 1; j < this.vertices.length; j++) {
193 | if (this.vertices[i].x == this.vertices[j].x && this.vertices[i].y == this.vertices[j].y) {
194 | this.vertices.splice(i--, 1);
195 | this.vertices.splice(j--, 1);
196 | }
197 | }
198 | }
199 |
200 | intersections = [];
201 | var addTo = [];
202 | for (i = 0; i < this.vertices.length - 2; i++) {
203 | for (j = i + 2; j < this.vertices.length - (i === 0 ? 1 : 0); j++) {
204 | if (lineIntersect(this.vertices[i], this.vertices[i + 1], this.vertices[j], this.vertices[(j + 1) % this.vertices.length])) {
205 | intersection = lineIntersectionPoint(this.vertices[i], this.vertices[i + 1], this.vertices[j], this.vertices[(j + 1) % this.vertices.length]);
206 | if (intersection !== undefined) {
207 | intersections.push(intersection);
208 | addTo[intersections.length - 1] = [i + 1, j + 1];
209 | }
210 | }
211 | }
212 | }
213 |
214 | if (intersections.length < 1) {
215 | return [
216 | [this], debris
217 | ];
218 | }
219 |
220 | for (i = 0; i < addTo.length; i++) {
221 | for (j = 0; j < addTo[i].length; j++) {
222 | var index = addTo[i][j];
223 | this.vertices.splice(index, 0, intersections[i]);
224 |
225 | for (k = i; k < addTo.length; k++) {
226 | for (l = j; l < addTo[k].length; l++) {
227 | if (addTo[k][l] > index) {
228 | addTo[k][l]++;
229 | }
230 | }
231 | }
232 | }
233 | }
234 |
235 | var outcomePolygons = new Array(intersections.length + 1);
236 | var pushTo = 0;
237 | var order = 1;
238 |
239 | for (i = 0; i < this.vertices.length; i++) {
240 | if (outcomePolygons[pushTo] === undefined) {
241 | outcomePolygons[pushTo] = [];
242 | }
243 | if (intersections.indexOf(this.vertices[i]) !== -1) {
244 | if (order == 1) {
245 | outcomePolygons[pushTo].push(this.vertices[i].copy());
246 | }
247 | pushTo += order;
248 | if (pushTo == outcomePolygons.length - 1) {
249 | order = -1;
250 | }
251 | } else {
252 | outcomePolygons[pushTo].push(this.vertices[i].copy());
253 | }
254 | }
255 |
256 | for (i = 0; i < outcomePolygons.length; i++) {
257 | if (Shape.inverted(outcomePolygons[i])) {
258 | outcomePolygons.splice(i--, 1);
259 | }
260 | }
261 |
262 | if (outcomePolygons.length > 0) {
263 | var shapes = [];
264 | for (i = 0; i < outcomePolygons.length; i++) {
265 | shapes.push(new Shape(outcomePolygons[i]));
266 | }
267 | return [shapes, debris];
268 | } else {
269 | return [
270 | [this], debris
271 | ];
272 | }
273 | }
274 |
275 | this.draw = function() {
276 |
277 | if (this.frame < 0) {
278 |
279 | beginShape();
280 | for (var i = 0; i < this.vertices.length; i++) {
281 | vertex(this.vertices[i].x, this.vertices[i].y);
282 | }
283 | endShape(CLOSE);
284 |
285 | } else if (this.frame < this.frames) {
286 |
287 | var hRng = this.speed * 0.5;
288 | randomSeed(this.seed);
289 |
290 | for (var i = 0; i < this.vertices.length; i++) {
291 | var vertA = this.vertices[i];
292 | var vertB = this.vertices[(i + 1) % this.vertices.length];
293 |
294 | var rSpeed = this.speed + random(-hRng, hRng);
295 | var vAB = p5.Vector.sub(vertB, vertA);
296 | var cAB = p5.Vector.add(vertA, vertB);
297 | cAB.div(2);
298 | var trans = cAB.copy();
299 | trans.normalize();
300 | trans.mult(this.frame * rSpeed);
301 | trans.add(cAB);
302 |
303 | push();
304 |
305 | translate(trans.x, trans.y);
306 | rotate(this.frame * random(-this.spin / 2, this.spin / 2));
307 | line(-vAB.x / 2, -vAB.y / 2, vAB.x / 2, vAB.y / 2);
308 |
309 | pop();
310 |
311 | }
312 |
313 | this.frame++;
314 |
315 | } else {
316 | this.frame = -1;
317 | return false;
318 | }
319 | return true;
320 |
321 | }
322 | }
323 |
324 |
325 | Shape.calculateRadius = function(vertices, centroid) {
326 | var center = centroid;
327 | var max_r = -1;
328 | for (var i = 0; i < vertices.length; i++) {
329 | var dist_ = dist(center.x, center.y, vertices[i].x, vertices[i].y);
330 | if (dist_ > max_r) {
331 | max_r = dist_;
332 | }
333 | }
334 | return max_r;
335 | }
336 |
337 | //calculates the area within a shape
338 | Shape.calculateArea = function(vertices) {
339 | var area = 0;
340 | for (var i = 0; i < vertices.length - 1; i++) {
341 | area += vertices[i].x * vertices[i + 1].y - vertices[i].y * vertices[i + 1].x;
342 | }
343 | return abs(area / 2);
344 | }
345 |
346 | Shape.trueCenterpoint = function(vertices, area) {
347 | var px = 0,
348 | py = 0;
349 | for (var i = 0; i < vertices.length; i++) {
350 | px += (vertices[i].x + vertices[(i + 1) % vertices.length].x) * cross(vertices[i], vertices[(i + 1) % vertices.length]);
351 | py += (vertices[i].y + vertices[(i + 1) % vertices.length].y) * cross(vertices[i], vertices[(i + 1) % vertices.length]);
352 | }
353 | px /= 6 * area;
354 | py /= 6 * area;
355 | return new p5.Vector(px, py);
356 | }
357 |
358 | Shape.makeAsteroidSized = function(shapes) {
359 | for (var i = 0; i < shapes[0].length; i++) {
360 | var area = shapes[0][i].area;
361 | var r = shapes[0][i].r
362 | var error = area / (PI * r * r);
363 | if (area < 750) {
364 | shapes[1].push(shapes[0].splice(i--, 1)[0]);
365 | } else if (error < 0.3) {
366 | var newShapes = shapes[0][i].splitAtWeakestPoint();
367 | shapes[0] = shapes[0].concat(newShapes);
368 | shapes[0].splice(i--, 1);
369 | }
370 | }
371 | return shapes;
372 | }
373 |
374 | Shape.contains = function(vertices, pos) {
375 | var c = false;
376 | for (var i = 0, j = vertices.length - 1; i < vertices.length; j = i++) {
377 | if (((vertices[i].y > pos.y) != (vertices[j].y > pos.y)) &&
378 | (pos.x < (vertices[j].x - vertices[i].x) * (pos.y - vertices[i].y) / (vertices[j].y - vertices[i].y) + vertices[i].x))
379 | c = !c;
380 | }
381 | return c;
382 | }
383 |
384 | Shape.inverted = function(vertices) {
385 | var sum = 0;
386 | for (var i = 0; i < vertices.length; i++) {
387 | sum += (vertices[(i + 1) % vertices.length].x - vertices[i].x) * (vertices[i].y + vertices[(i + 1) % vertices.length].y);
388 | }
389 | return sum >= 0;
390 | }
391 |
392 | Shape.smooth = function(vertices, loop_) {
393 | if (loop_ === undefined) {
394 | loop_ = true;
395 | }
396 | for (var i = 0; i < vertices.length - (loop_ ? 0 : 1); i += 2) {
397 | var v_ = createVector((vertices[i].x + vertices[(i + 1) % vertices.length].x) * 0.5 + random(10), (vertices[i].y + vertices[(i + 1) % vertices.length].y) * 0.5 + random(10));
398 | vertices.splice(i + 1, 0, v_);
399 | }
400 | }
--------------------------------------------------------------------------------
/sketch.js:
--------------------------------------------------------------------------------
1 | // Daniel Shiffman
2 | // http://codingrainbow.com
3 | // http://patreon.com/codingrainbow
4 | // Code for: https://youtu.be/hacZU523FyM
5 |
6 |
7 | var laserSoundEffect = [];
8 | var explosionSoundEffects = [];
9 | var world;
10 | var uimanager;
11 | var localplayerentity;
12 |
13 | function playSoundEffect(sound) {
14 | if (!sound.isPlaying()) {
15 | sound.play();
16 | }
17 | }
18 |
19 | function preload() {
20 | for (var i = 0; i < 3; i++) {
21 | laserSoundEffect[i] = loadSound('audio/pew-' + i + '.mp3');
22 | }
23 | for (var i = 0; i < 3; i++) {
24 | explosionSoundEffects[i] = loadSound('audio/explosion-' + i + '.mp3');
25 | }
26 | }
27 |
28 | function setup() {
29 | createCanvas(windowWidth, windowHeight);
30 | world = new World(2000, 2000, {
31 | left: -640,
32 | right: 640,
33 | bottom: -360,
34 | top: 360,
35 | near: 0,
36 | far: 500
37 | });
38 | uimanager = new UIManager(world, {
39 | left: 160,
40 | right: 160,
41 | bottom: 90,
42 | top: 90,
43 | near: 0,
44 | far: 500
45 | });
46 | world.initialize();
47 |
48 | localplayerentity = world.getLocalPlayer().getEntity();
49 | uimanager.create(Hud);
50 | uimanager.create(PlayerControls);
51 | }
52 |
53 | function draw() {
54 | push();
55 | background(0);
56 | world.update();
57 | uimanager.update();
58 |
59 | world.render();
60 | uimanager.render();
61 | pop();
62 | }
63 |
--------------------------------------------------------------------------------
/ui/hud.js:
--------------------------------------------------------------------------------
1 | function Hud(id) {
2 | UIElement.call(this, id, {
3 | pos: createVector(0, 0)
4 | });
5 | var size = 20;
6 | var padding = 10;
7 | var lifeWidth = 20;
8 | var player;
9 |
10 | /*
11 | --0--
12 | 1 2
13 | --3--
14 | 4 5
15 | --6--
16 | */
17 | var digitMaps = [
18 | //return a digit map
19 | [true, true, true, false, true, true, true], //0
20 | [false, false, true, false, false, true, false], //1
21 | [true, false, true, true, true, false, true], //2
22 | [true, false, true, true, false, true, true], //3
23 | [false, true, true, true, false, true, false], //4
24 | [true, true, false, true, false, true, true], //5
25 | [true, true, false, true, true, true, true], //6
26 | [true, false, true, false, false, true, false], //7
27 | [true, true, true, true, true, true, true], //8
28 | [true, true, true, true, false, true, true] //9
29 |
30 | ];
31 |
32 | this.render = function (world) {
33 | player = world.getLocalPlayer();
34 | var scoreString = "" + player.score;
35 | var digitPos = createVector((width / 2 - (scoreString.length * (size + padding) - padding) / 2), padding);
36 | for (var i = 0; i < scoreString.length; i++) {
37 | var dmap = digitMaps[scoreString.charAt(i)];
38 | drawDigit(dmap, i, digitPos);
39 | digitPos.x += size + padding;
40 | }
41 | drawLives();
42 | if (player.dead) {
43 | push();
44 | textSize(32);
45 | fill(255);
46 | textAlign(CENTER);
47 | text("GAME OVER", width / 2, height / 2);
48 | pop();
49 | }
50 | }
51 |
52 | function drawLives() {
53 | var lives = player.getEntity().lives;
54 | push();
55 | stroke(255);
56 | fill(0);
57 | var top = createVector((width / 2) + (lifeWidth + padding) * (lives - 1) / 2, padding * 2 + size * 2);
58 | for (var i = 0; i < lives; i++) {
59 | triangle(top.x, top.y, top.x - lifeWidth / 2, top.y + 25, top.x + lifeWidth / 2, top.y + 25);
60 | top.x -= lifeWidth + padding;
61 | }
62 | pop();
63 | }
64 |
65 | //draws the digit based on the digit map
66 | function drawDigit(digitMap, index, pos) {
67 | push();
68 | stroke(255);
69 | for (var i = 0; i < digitMap.length; i++) {
70 | if (digitMap[i] === true) {
71 | drawLine(i, pos);
72 | }
73 | }
74 | pop();
75 | }
76 |
77 | //draws a line based on the line map
78 | function drawLine(lineMap, pos) {
79 | switch (lineMap) {
80 | case 0:
81 | line(pos.x, pos.y, pos.x + size, pos.y);
82 | break;
83 | case 1:
84 | line(pos.x, pos.y, pos.x, pos.y + size);
85 | break;
86 | case 2:
87 | line(pos.x + size, pos.y, pos.x + size, pos.y + size);
88 | break;
89 | case 3:
90 | line(pos.x, pos.y + size, pos.x + size, pos.y + size);
91 | break;
92 | case 4:
93 | line(pos.x, pos.y + size, pos.x, pos.y + 2 * size);
94 | break;
95 | case 5:
96 | line(pos.x + size, pos.y + size, pos.x + size, pos.y + 2 * size);
97 | break;
98 | case 6:
99 | line(pos.x, pos.y + size * 2, pos.x + size, pos.y + 2 * size);
100 | break;
101 | default:
102 | console.log("line map is invalid");
103 | break;
104 | }
105 | }
106 | }
107 |
108 | Hud.prototype = Object.create(UIElement.prototype);
109 |
--------------------------------------------------------------------------------
/ui/playercontrols.js:
--------------------------------------------------------------------------------
1 | function PlayerControls(id) {
2 | UIElement.call(this, id, {
3 | pos: createVector(0, 0)
4 | });
5 | var keys = {
6 | right: false,
7 | left: false,
8 | up: false,
9 | down: false,
10 | f: false,
11 | space: false,
12 | spacerepeat: false,
13 | leftmouse: false,
14 | leftmouserepeat: false
15 | };
16 |
17 | this.update = function(world) {
18 | world.getLocalPlayer().getEntity().setInputs(
19 | createVector(mouseX - windowWidth / 2, mouseY - windowHeight / 2),
20 | keys.up,
21 | keys.down,
22 | keys.left,
23 | keys.right,
24 | keys.f,
25 | keys.space || keys.spacerepeat || keys.leftmouse || keys.leftmouserepeat
26 | );
27 | keys.space = false;
28 | keys.leftmouse = false;
29 | keys.f = false;
30 | }
31 |
32 | this.render = function(world) {
33 | push();
34 | textSize(32);
35 | textAlign(LEFT);
36 | fill(255);
37 | if (world.getLocalPlayer().getEntity().velMu > 0) {
38 | text("(FPS: " + floor(frameRate()) + ") Stabilizers ON", 10, windowHeight - 10);
39 | } else {
40 | text("(FPS: " + floor(frameRate()) + ") Stabilizers OFF", 10, windowHeight - 10);
41 | }
42 | }
43 |
44 | var scope = this;
45 | world.registerKeyListener(id, " ".charCodeAt(0), function(char, code, press) {
46 | keys.spacerepeat = press;
47 | if (press) {
48 | keys.space = true;
49 | }
50 | });
51 | world.registerKeyListener(id, "F".charCodeAt(0), function(char, code, press) {
52 | keys.f = press;
53 | });
54 | world.registerKeyListener(id, "D".charCodeAt(0), function(char, code, press) {
55 | keys.right = press;
56 | });
57 | world.registerKeyListener(id, "A".charCodeAt(0), function(char, code, press) {
58 | keys.left = press;
59 | });
60 | world.registerKeyListener(id, "W".charCodeAt(0), function(char, code, press) {
61 | keys.up = press;
62 | });
63 | world.registerKeyListener(id, "S".charCodeAt(0), function(char, code, press) {
64 | keys.down = press;
65 | });
66 | world.registerMouseListener(id, LEFT, function(button, press) {
67 | keys.leftmouserepeat = press;
68 | if (press) {
69 | keys.leftmouse = true;
70 | }
71 | });
72 | }
73 |
74 | PlayerControls.prototype = Object.create(UIElement.prototype);
75 |
--------------------------------------------------------------------------------
/ui/uielement.js:
--------------------------------------------------------------------------------
1 | function UIElement(id, params) {
2 | this.pos = params.pos !== undefined ? params.pos : createVector(0, 0);
3 | }
4 |
5 | UIElement.prototype.update = function(world) {};
6 | UIElement.prototype.render = function(world) {};
7 |
--------------------------------------------------------------------------------
/world.js:
--------------------------------------------------------------------------------
1 | function World(width, height, viewSize) {
2 | this.width = width;
3 | this.height = height;
4 | this.halfwidth = width / 2;
5 | this.halfheight = height / 2;
6 | this.seed = millis();
7 | this.time = 0;
8 |
9 |
10 | var hud;
11 | var levelmanager;
12 | var entitymanager = new EntityManager();
13 | var endFrameTasks = [];
14 | var players = [];
15 |
16 | // Returns the player playing on this machine.
17 | // TODO: Currently returns the first player as we only have one.
18 | this.getLocalPlayer = function() {
19 | return players[0];
20 | }
21 |
22 | // Returns the player with the specific id.
23 | this.getPlayer = function(id) {
24 | return players[id];
25 | }
26 |
27 | // Adds a function to a stack, will be called at the end of the frame once
28 | // all the other logic is completed.
29 | this.addEndFrameTask = function(callback) {
30 | endFrameTasks.push(callback);
31 | }
32 |
33 | // Adds a callback for the specified entity when the specified keycode is hit.
34 | this.registerKeyListener = function(entity, code, callback) {
35 | input.registerKeyListener(entity.id, code, callback);
36 | }
37 |
38 | // Removes all callbacks for the specified entity and keycode.
39 | this.degisterKeyListener = function(entity, code) {
40 | input.degisterKeyListener(entity.id, code);
41 | }
42 |
43 | // Adds a callback for the specified entity when the specified mouse button is hit.
44 | this.registerMouseListener = function(entity, button, callback) {
45 | input.registerMouseListener(entity.id, button, callback);
46 | }
47 |
48 | // Removes all callbacks for the specified entity and mouse button.
49 | this.degisterMouseListener = function(entity, button) {
50 | input.degisterMouseListener(entity.id, button);
51 | }
52 |
53 | // Creates a new entity from the given constructor and params object
54 | // and adds it to the world.
55 | this.createEntity = function(entity, params) {
56 | var entity = new entity(world, params);
57 | entitymanager.add(entity);
58 | return entity;
59 | }
60 |
61 | // Initializes the world.
62 | this.initialize = function() {
63 | players[0] = new Player(players.length, "SomeRandomName", this);
64 | entitymanager.add(players[0].getEntity());
65 | levelmanager = new LevelManager(this, players[0].getEntity(), 0);
66 | hud = new Hud(this, levelmanager, players[0].getEntity());
67 | }
68 |
69 | // Does all the update logic for this frame.
70 | this.update = function() {
71 | entitymanager.update();
72 | entitymanager.checkCollisions();
73 | levelmanager.update(players);
74 | for (var i = 0; i < endFrameTasks.length; i++) {
75 | endFrameTasks[i](this);
76 | }
77 |
78 | endFrameTasks.length = 0;
79 | }
80 |
81 | // Does all the rendering for this frame.
82 | this.render = function() {
83 | push();
84 | randomSeed(this.seed);
85 | push();
86 | for (var i = 0; i < 500; i++) {
87 | strokeWeight(0.1 * random(20) + 2);
88 | stroke(255 * pow(sin(random(0, PI) + this.time / 80), 2));
89 | var star = createVector(
90 | random(-this.halfwidth, this.halfwidth),
91 | random(-this.halfheight, this.halfheight)
92 | );
93 | var playerPos = this.getLocalPlayer().getEntity().pos;
94 | var relPos = p5.Vector.sub(star, playerPos);
95 | if (relPos.x > windowWidth / 2) star.x -= this.width;
96 | else if (relPos.x < -windowWidth / 2) star.x += this.width;
97 | if (relPos.y > windowHeight / 2) star.y -= this.height;
98 | else if (relPos.y < -windowHeight / 2) star.y += this.height;
99 | point(star.x, star.y);
100 | }
101 | //push();
102 | //stroke(255);
103 | //strokeWeight(3);
104 | //noFill();
105 | //rect(-world.halfwidth, -world.halfheight, world.width, world.height);
106 | //pop();
107 | pop();
108 | this.time++;
109 | randomSeed(millis());
110 | entitymanager.render();
111 | pop();
112 | }
113 | }
114 |
--------------------------------------------------------------------------------