├── 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 | 19 |
AD keys to move and click to shoot
20 |
21 |
22 |
Congratulations you saved the world!
23 |
Made with A-FRAME
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 | }); --------------------------------------------------------------------------------