├── README.md
├── css
└── style.css
├── index.html
└── js
├── aframe-core.js
└── components
├── collider.js
├── explode.js
├── laser-behavior.js
├── spawner.js
└── wasd-controls.js
/README.md:
--------------------------------------------------------------------------------
1 | # A-INVADERS
2 |
3 | [DEMO](http://diegomarcos.com/a-invaders/) made with [A-FRAME](http://www.aframevr.io)
4 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: black;
3 | }
4 |
5 | .screen {
6 | position: relative;
7 | background-color: black;
8 | width: 100%;
9 | height: 100%;
10 | z-index: 99999;
11 | text-align: center;
12 | padding-top: 15%;
13 | color: white;
14 | font-family: monospace;
15 | }
16 |
17 | .end-screen {
18 | display: none;
19 | }
20 |
21 | .title {
22 | font-weight: bold;
23 | font-size: 110px;
24 | text-shadow: 1px 1px #ef2d5e,
25 | 2px 2px #ef2d5e,
26 | 3px 3px #ef2d5e;
27 | }
28 |
29 | .start, .end {
30 | font-weight: bold;
31 | margin-top: 45px;
32 | font-size: 35px;
33 | }
34 |
35 | .instructions {
36 | margin-top: 45px;
37 | font-size: 20px;
38 | }
39 |
40 | .blink {
41 | animation: blink 2.5s linear infinite;
42 | }
43 |
44 | @keyframes blink {
45 | 0% { opacity: 0.0; }
46 | 50% { opacity: 1.0; }
47 | 100% { opacity: 0.0; }
48 | }
49 |
50 | .score {
51 | color: white;
52 | position: fixed;
53 | top: 10px;
54 | font-family: monospace;
55 | font-size: 20px;
56 | z-index: 99999;
57 | }
58 |
59 | .end a {
60 | color: #ef2d5e;
61 | text-decoration: none;
62 | }
63 |
64 | .end a::hover {
65 | text-decoration: underline;
66 | }
67 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | A-INVADERS
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Score: 0
16 |
17 |
A-INVADERS
18 |
Click to Start
19 |
AD keys to move and click to shoot
20 |
21 |
22 |
Congratulations you saved the world!
23 |
24 |
25 |
26 |
27 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
56 |
57 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/js/components/collider.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerComponent('collider', {
2 | schema: {},
3 |
4 | init: function() {
5 | this.el.sceneEl.addBehavior(this);
6 | },
7 |
8 | update: function () {
9 | var sceneEl = this.el.sceneEl;
10 | var mesh = this.el.getObject3D('mesh');
11 | var object3D = this.el.object3D
12 | var originPoint = this.el.object3D.position.clone();
13 | for (var vertexIndex = 0; vertexIndex < mesh.geometry.vertices.length; vertexIndex++) {
14 | var localVertex = mesh.geometry.vertices[vertexIndex].clone();
15 | var globalVertex = localVertex.applyMatrix4( object3D.matrix );
16 | var directionVector = globalVertex.sub( object3D.position );
17 |
18 | var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
19 | var collisionResults = ray.intersectObjects( sceneEl.object3D.children, true );
20 | collisionResults.forEach(hit);
21 | }
22 | function hit(collision) {
23 | if (collision.object === object3D) {
24 | return;
25 | }
26 | if (collision.distance < directionVector.length()) {
27 | if (!collision.object.el) { return; }
28 | collision.object.el.emit('hit');
29 | }
30 | }
31 | }
32 | });
--------------------------------------------------------------------------------
/js/components/explode.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerComponent('explode', {
2 | schema: { on: { default: ''} },
3 |
4 | update: function (previousData) {
5 | var el = this.el;
6 | var explode = this.handler = this.explode.bind(this);
7 | if (previousData) {
8 | el.removeEventListener(previousData.on, explode);
9 | }
10 | el.addEventListener(this.data.on, explode);
11 | },
12 |
13 | explode: function () {
14 | var object3D = this.el.getObject3D('mesh');
15 | var scene = this.el.sceneEl.object3D;
16 | var duration = 8000;
17 |
18 | // explode geometry into objects
19 | var pieces = explode(object3D.geometry, object3D.material);
20 |
21 | object3D.material.visible = false;
22 |
23 | // animate objects
24 | for ( var i = 0; i < pieces.children.length; i ++ ) {
25 |
26 | var object = pieces.children[ i ];
27 |
28 | object.geometry.computeFaceNormals();
29 | var normal = object.geometry.faces[0].normal.clone();
30 | var targetPosition = object.position.clone().add(normal.multiplyScalar(300));
31 | //removeBoxFromList( object3D );
32 | new TWEEN.Tween( object.position )
33 | .to( targetPosition, duration )
34 | .onComplete( deleteBox )
35 | .start();
36 |
37 | object.material.opacity = 0;
38 | new TWEEN.Tween( object.material )
39 | .to( { opacity: 1 }, duration )
40 | .start();
41 |
42 | var rotation = 2 * Math.PI;
43 | var targetRotation = { x: rotation, y: rotation, z:rotation };
44 | new TWEEN.Tween( object.rotation )
45 | .to( targetRotation, duration )
46 | .start();
47 |
48 | }
49 |
50 | function deleteBox() {
51 | object3D.remove( pieces )
52 | scene.remove( object3D );
53 | }
54 |
55 | object3D.add(pieces);
56 | this.el.removeEventListener(this.data.on, this.handler);
57 |
58 | function explode( geometry, material ) {
59 | var pieces = new THREE.Group();
60 | var material = material.clone();
61 | material.side = THREE.DoubleSide;
62 |
63 | for (var i = 0; i < geometry.faces.length; i ++) {
64 | var face = geometry.faces[ i ];
65 |
66 | var vertexA = geometry.vertices[ face.a ].clone();
67 | var vertexB = geometry.vertices[ face.b ].clone();
68 | var vertexC = geometry.vertices[ face.c ].clone();
69 |
70 | var geometry2 = new THREE.Geometry();
71 | geometry2.vertices.push( vertexA, vertexB, vertexC );
72 | geometry2.faces.push( new THREE.Face3( 0, 1, 2 ) );
73 |
74 | var mesh = new THREE.Mesh( geometry2, material );
75 | mesh.position.sub( geometry2.center() );
76 | pieces.add( mesh );
77 | }
78 | //sort the pieces
79 | pieces.children.sort( function ( a, b ) {
80 | return a.position.z - b.position.z;
81 | //return a.position.x - b.position.x; // sort x
82 | //return b.position.y - a.position.y; // sort y
83 | } );
84 | pieces.rotation.set( 0, 0, 0 )
85 | return pieces;
86 | }
87 |
88 | function removeBoxFromList( box ) {
89 | var objects = scene.children;
90 | for (var i = 0; i < objects.length; i++) {
91 | if (objects[i] === box) {
92 | objects.splice(i, 1);
93 | return;
94 | }
95 | }
96 | }
97 |
98 | }
99 | });
--------------------------------------------------------------------------------
/js/components/laser-behavior.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerComponent('laser-behavior', {
2 | schema: {
3 | speed: { default: 1 }
4 | },
5 |
6 | init: function () {
7 | this.el.sceneEl.addBehavior(this);
8 | },
9 |
10 | update: function () {
11 | var object3D = this.el.object3D;
12 | object3D.translateY(this.data.speed);
13 | }
14 | });
--------------------------------------------------------------------------------
/js/components/spawner.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerComponent('spawner', {
2 | schema: {
3 | on: { default: 'click' },
4 | mixin: { default: '' }
5 | },
6 |
7 | update: function () {
8 | var el = this.el;
9 | var spawn = this.spawn.bind(this);
10 | if (this.on === this.data.on) { return; }
11 | el.removeEventListener(this.on, spawn);
12 | el.addEventListener(this.data.on, spawn);
13 | this.on = this.data.on;
14 | },
15 |
16 | spawn: function () {
17 | var el = this.el;
18 | var matrixWorld = el.object3D.matrixWorld;
19 | var position = new THREE.Vector3();
20 | position.setFromMatrixPosition(matrixWorld);
21 | var entity = document.createElement('a-entity');
22 | entity.setAttribute('position', position);
23 | entity.setAttribute('mixin', this.data.mixin);
24 | el.sceneEl.appendChild(entity);
25 | }
26 | });
--------------------------------------------------------------------------------
/js/components/wasd-controls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WASD component.
3 | *
4 | * Control your entities with the WASD keys.
5 | *
6 | * @namespace wasd-controls
7 | * @param {number} [easing=20] - How fast the movement decelerates. If you hold the
8 | * keys the entity moves and if you release it will stop. Easing simulates friction.
9 | * @param {number} [acceleration=65] - Determines the acceleration given
10 | * to the entity when pressing the keys.
11 | * @param {bool} [enabled=true] - To completely enable or disable the controls
12 | * @param {bool} [fly=false] - Determines if the direction of the movement sticks
13 | * to the plane where the entity started off or if there are 6 degrees of
14 | * freedom as a diver underwater or a plane flying.
15 | * @param {string} [wsAxis='z'] - The axis that the W and S keys operate on
16 | * @param {string} [adAxis='x'] - The axis that the A and D keys operate on
17 | * @param {bool} [wsInverted=false] - WS Axis is inverted
18 | * @param {bool} [adInverted=false] - AD Axis is inverted
19 | */
20 | AFRAME.registerComponent('wasd-controls', {
21 | schema: {
22 | easing: { default: 20 },
23 | acceleration: { default: 65 },
24 | enabled: { default: true },
25 | fly: { default: false },
26 | wsAxis: { default: 'z', oneOf: [ 'x', 'y', 'z' ] },
27 | adAxis: { default: 'x', oneOf: [ 'x', 'y', 'z' ] },
28 | wsInverted: { default: false },
29 | wsEnabled: { default: true },
30 | adInverted: { default: false },
31 | adEnabled: { default: true }
32 | },
33 |
34 | init: function () {
35 | this.velocity = new THREE.Vector3();
36 | // To keep track of the pressed keys
37 | this.keys = {};
38 | this.onKeyDown = this.onKeyDown.bind(this);
39 | this.onKeyUp = this.onKeyUp.bind(this);
40 | },
41 |
42 | play: function () {
43 | var scene = this.el.sceneEl;
44 | this.attachEventListeners();
45 | scene.addBehavior(this);
46 | },
47 |
48 | pause: function () {
49 | var scene = this.el.sceneEl;
50 | this.removeEventListeners();
51 | scene.removeBehavior(this);
52 | },
53 |
54 | remove: function () {
55 | this.pause();
56 | },
57 |
58 | update: function (previousData) {
59 | var data = this.data;
60 | var acceleration = data.acceleration;
61 | var easing = data.easing;
62 | var velocity = this.velocity;
63 | var prevTime = this.prevTime = this.prevTime || Date.now();
64 | var time = window.performance.now();
65 | var delta = (time - prevTime) / 1000;
66 | var keys = this.keys;
67 | var movementVector;
68 | var adAxis = data.adAxis;
69 | var wsAxis = data.wsAxis;
70 | var adSign = data.adInverted ? -1 : 1;
71 | var wsSign = data.wsInverted ? -1 : 1;
72 | var el = this.el;
73 | this.prevTime = time;
74 |
75 | // If data has changed or FPS is too low
76 | // we reset the velocity
77 | if (previousData || delta > MAX_DELTA) {
78 | velocity[adAxis] = 0;
79 | velocity[wsAxis] = 0;
80 | return;
81 | }
82 |
83 | velocity[adAxis] -= velocity[adAxis] * easing * delta;
84 | velocity[wsAxis] -= velocity[wsAxis] * easing * delta;
85 |
86 | var position = el.getComputedAttribute('position');
87 |
88 | if (data.enabled) {
89 | if (data.adEnabled) {
90 | if (keys[65]) { velocity[adAxis] -= adSign * acceleration * delta; } // Left
91 | if (keys[68]) { velocity[adAxis] += adSign * acceleration * delta; } // Right
92 | }
93 | if (data.wsEnabled) {
94 | if (keys[87]) { velocity[wsAxis] -= wsSign * acceleration * delta; } // Up
95 | if (keys[83]) { velocity[wsAxis] += wsSign * acceleration * delta; } // Down
96 | }
97 | }
98 |
99 | movementVector = this.getMovementVector(delta);
100 | el.object3D.translateX(movementVector.x);
101 | el.object3D.translateY(movementVector.y);
102 | el.object3D.translateZ(movementVector.z);
103 |
104 | el.setAttribute('position', {
105 | x: position.x + movementVector.x,
106 | y: position.y + movementVector.y,
107 | z: position.z + movementVector.z
108 | });
109 | },
110 |
111 | attachEventListeners: function () {
112 | // Keyboard events
113 | window.addEventListener('keydown', this.onKeyDown, false);
114 | window.addEventListener('keyup', this.onKeyUp, false);
115 | },
116 |
117 | removeEventListeners: function () {
118 | // Keyboard events
119 | window.removeEventListener('keydown', this.onKeyDown);
120 | window.removeEventListener('keyup', this.onKeyUp);
121 | },
122 |
123 | onKeyDown: function (event) {
124 | this.keys[event.keyCode] = true;
125 | },
126 |
127 | onKeyUp: function (event) {
128 | this.keys[event.keyCode] = false;
129 | },
130 |
131 | getMovementVector: (function (delta) {
132 | var direction = new THREE.Vector3(0, 0, 0);
133 | var rotation = new THREE.Euler(0, 0, 0, 'YXZ');
134 | return function (delta) {
135 | var velocity = this.velocity;
136 | var elRotation = this.el.getAttribute('rotation');
137 | direction.copy(velocity);
138 | direction.multiplyScalar(delta);
139 | if (!elRotation) { return direction; }
140 | if (!this.data.fly) { elRotation.x = 0; }
141 | rotation.set(THREE.Math.degToRad(elRotation.x),
142 | THREE.Math.degToRad(elRotation.y), 0);
143 | direction.applyEuler(rotation);
144 | return direction;
145 | };
146 | })()
147 | });
--------------------------------------------------------------------------------