├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── data │ └── waves.js ├── fonts │ ├── mozillavr.fnt │ └── mozillavr.png ├── images │ ├── damage.png │ ├── decal0.png │ ├── decal1.png │ ├── decal2.png │ ├── enemy_diff.jpg │ ├── enemy_emissive0.jpg │ ├── enemy_emissive1.jpg │ ├── enemy_emissive2.jpg │ ├── enemy_emissive3.jpg │ ├── enemy_emissive_template.jpg │ ├── enemy_spec.jpg │ ├── face_guide.png │ ├── font.png │ ├── fx1.png │ ├── fx2.png │ ├── fx3.png │ ├── fx4.png │ ├── fx5.png │ ├── fx6.png │ ├── fx7.png │ ├── fx8.png │ ├── grad.png │ ├── gun_diff.jpg │ ├── gun_normal.png │ ├── gun_spec.jpg │ ├── icon-128.png │ ├── icon-256.png │ ├── icon.png │ ├── menu.png │ ├── set_diff.jpg │ └── sky.jpg ├── models │ ├── border.json │ ├── enemy-bullet.json │ ├── enemy0.json │ ├── enemy1.json │ ├── enemy2.json │ ├── enemy3.json │ ├── gameover.json │ ├── gun.json │ ├── logo.json │ ├── player-bullet.json │ ├── restart.json │ ├── ring.json │ └── welldone.json ├── rawfiles │ ├── images │ │ ├── decals.psd │ │ ├── enemy.psd │ │ ├── env.psd │ │ ├── face.psd │ │ ├── favicon.ico │ │ ├── font.kra │ │ ├── font.psd │ │ ├── fx.psd │ │ ├── icon-128.png │ │ ├── icon-256.png │ │ ├── icon.png │ │ ├── menu.psd │ │ └── sky.psd │ ├── models │ │ ├── background.blend │ │ ├── border.blend │ │ ├── border_lighting.blend │ │ ├── bullet.blend │ │ ├── deco.blend │ │ ├── decos.blend │ │ ├── enemies.blend │ │ ├── enemy0.blend │ │ ├── enemy1.blend │ │ ├── enemy2.blend │ │ ├── enemy3.blend │ │ ├── exportWaves.py │ │ ├── gameover.blend │ │ ├── gun.blend │ │ ├── gun_anim.blend │ │ ├── logo.blend │ │ ├── platform.blend │ │ ├── player-bullet.blend │ │ ├── restart.blend │ │ ├── testsphere.blend │ │ ├── waves.blend │ │ └── welldone.blend │ └── sounds │ │ └── sfx │ │ ├── .bitwig-project │ │ ├── enemy0shoot.bwproject │ │ ├── explosion0.bwproject │ │ ├── explosion1.bwproject │ │ ├── explosion2.bwproject │ │ ├── explosion3.bwproject │ │ ├── hitbullet.bwproject │ │ ├── hitwall.bwproject │ │ ├── hurt.bwproject │ │ ├── playershoot.bwproject │ │ ├── sfx.bwproject │ │ └── sounds.aup ├── readme │ ├── a-blast-10s.gif │ ├── a-blast-1s.gif │ ├── a-blast-22s.gif │ ├── a-blast-3s.gif │ ├── gun.png │ ├── mainmenu.png │ └── mainmenu2.png └── sounds │ ├── enemy0shoot.ogg │ ├── enemy1shoot.ogg │ ├── enemy2shoot.ogg │ ├── enemy3shoot.ogg │ ├── explosion0.ogg │ ├── explosion1.ogg │ ├── explosion2.ogg │ ├── explosion3.ogg │ ├── gun.ogg │ ├── hitbullet.ogg │ ├── hitwall.ogg │ ├── hurt.ogg │ ├── intro.ogg │ └── music.ogg ├── build └── build.js ├── css └── main.css ├── favicon.ico ├── index.html ├── manifest.webmanifest ├── package-lock.json ├── package.json ├── src ├── a-asset-image.js ├── bullets │ ├── enemy-fast.js │ ├── enemy-fat.js │ ├── enemy-medium.js │ ├── enemy-slow.js │ └── player.js ├── components │ ├── animate-message.js │ ├── bullet.js │ ├── collision-helper.js │ ├── countdown.js │ ├── curve-movement.js │ ├── decals.js │ ├── enemy.js │ ├── explosion.js │ ├── gamestate-debug.js │ ├── gamestate-visuals.js │ ├── gamestate.js │ ├── gun.js │ ├── headset.js │ ├── highscores.js │ ├── json-model.js │ ├── lifes-counter.js │ ├── points-counter.js │ ├── proxy_event.js │ ├── restrict-position.js │ ├── shoot-controls.js │ ├── sound-fade.js │ ├── spline-line.js │ ├── timer-counter.js │ ├── vr-analytics.js │ └── weapon.js ├── enemies │ ├── enemy0.js │ ├── enemy1.js │ ├── enemy2.js │ ├── enemy3.js │ └── enemy_start.js ├── index.js ├── lib │ ├── letterpanel.js │ ├── poolhelper.js │ └── utils.js └── systems │ ├── bullet.js │ ├── enemy.js │ └── explosion.js ├── vendor ├── aframe-bmfont-component.js ├── aframe-bmfont-component.min.js ├── aframe-bmfont-text-component.min.js ├── aframe-master.min.js └── aframe-master.min.js.map └── webpack.config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # FIX CRLF always when developer has not set 2 | # Linux/Mac: git config --global core.autocrlf input 3 | # Windows: git config --global core.autocrlf true 4 | # Auto detect text files and perform LF normalization 5 | * text=auto 6 | * eol=lf 7 | 8 | *.js text 9 | *.html text 10 | *.npmignore text 11 | *.md text 12 | .ackrc text 13 | .gitattributes text 14 | .gitignore text 15 | .jshintrc text 16 | .nojekyll text 17 | .travis.yml text 18 | LICENSE text 19 | 20 | # Avoid creation of unnecessary big commit objects 21 | # For these files we do not want to see text diff for 22 | 23 | *.min.* binary minified 24 | *js.map* binary 25 | *.svg binary 26 | *.mtl binary 27 | *.obj binary 28 | *.jpg binary 29 | *.jpeg binary 30 | *.png binary 31 | *.wav binary 32 | *.ogg binary 33 | *.gif binary 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache 3 | gh-pages/ 4 | node_modules 5 | npm-debug.log* 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2015-2016 A-Frame authors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A-Blast 2 | 3 | A WebVR FPS mini-game demo using [A-Frame](http://aframe.io) by [Mozilla VR](http://mozvr.com). 4 | 5 | [Read the introductory blog post.](https://blog.mozvr.com/a-blast/) 6 | 7 | [![Screenshot of A-Blast main menu](assets/readme/mainmenu2.png)](https://aframe.io/a-blast/) 8 | 9 | ## Usage 10 | 11 | - Grab a [WebVR-enabled browser](https://webvr.info/). 12 | - Head to [https://aframe.io/a-blast/](https://aframe.io/a-blast/) and start shooting. See the [blog post](https://blog.mozvr.com/a-blast/) for more information. 13 | - You can also play in desktop using the spacebar and the mouse, and on your mobile phone too (with or without Cardboard). 14 | 15 |

16 | Recording of A-Blast gameplay 17 |

18 | 19 | ## Local Development 20 | 21 | ```bash 22 | git clone git@github.com:aframevr/a-blast && cd a-blast 23 | npm install 24 | npm start 25 | ``` 26 | 27 | Then, load [`http://localhost:8080`](http://localhost:8080) in your browser. 28 | 29 | We are opened to new ideas and contributions, feel free to send a pull request with your A-Blast improvements. 30 | 31 |

32 | Laser Gun 33 |

34 | 35 | 36 | Soundtrack by José Manuel Pérez Paredes AKA [JosSs](https://soundcloud.com/josss-1/tracks) 37 | -------------------------------------------------------------------------------- /assets/data/waves.js: -------------------------------------------------------------------------------- 1 | var WAVES = [ 2 | { 3 | name: "WAVE 1", 4 | sequences: [ 5 | { 6 | subwave: "2", 7 | enemies:[ 8 | { 9 | type: "enemy0", 10 | points: [[-2.779,-3.466,-8.405],[-2.717,3.011,-7.228]], 11 | movement: "single", 12 | }, 13 | { 14 | type: "enemy0", 15 | points: [[1.874,-7.677,-8.328],[3.490,6.398,-7.157]], 16 | movement: "single", 17 | } 18 | ] 19 | }, 20 | { 21 | subwave: "3", 22 | enemies:[ 23 | { 24 | type: "enemy0", 25 | points: [[-0.000,-8.839,-10.754],[-0.000,6.865,-9.169]], 26 | movement: "single", 27 | }, 28 | { 29 | type: "enemy0", 30 | points: [[-4.720,-11.803,-9.161],[-6.315,5.616,-7.575]], 31 | movement: "single", 32 | }, 33 | { 34 | type: "enemy0", 35 | points: [[4.324,-14.076,-9.161],[6.315,5.616,-7.575]], 36 | movement: "single", 37 | }, 38 | { 39 | type: "enemy1", 40 | points: [[-0.000,-4.957,-9.048],[0.000,2.312,-7.866]], 41 | movement: "single", 42 | } 43 | ] 44 | }, 45 | { 46 | subwave: "4", 47 | enemies:[ 48 | { 49 | type: "enemy0", 50 | points: [[2.532,-10.411,-9.161],[3.697,6.535,-7.575]], 51 | movement: "single", 52 | }, 53 | { 54 | type: "enemy0", 55 | points: [[-2.532,-10.095,-9.161],[-3.697,6.535,-7.575]], 56 | movement: "single", 57 | }, 58 | { 59 | type: "enemy0", 60 | points: [[-0.000,-4.957,-9.048],[0.000,2.312,-7.866]], 61 | movement: "single", 62 | }, 63 | { 64 | type: "enemy1", 65 | points: [[5.802,-13.645,-6.388],[4.858,0.887,-5.830],[2.253,3.765,-4.991],[-2.570,3.796,-4.924],[-4.115,2.613,-5.326],[-5.146,1.339,-5.770],[-5.333,1.050,-5.870]], 66 | movement: "pingpong", 67 | }, 68 | { 69 | type: "enemy1", 70 | points: [[-5.802,-21.383,-7.624],[-4.858,0.593,-7.067],[-2.253,3.471,-6.228],[2.595,3.502,-6.161],[4.080,2.453,-6.569],[5.178,1.071,-6.960],[5.333,0.757,-7.107]], 71 | movement: "pingpong", 72 | } 73 | ] 74 | }, 75 | { 76 | subwave: "5", 77 | enemies:[ 78 | { 79 | type: "enemy0", 80 | points: [[6.423,-4.604,-11.022],[5.502,2.845,-8.485]], 81 | movement: "single", 82 | }, 83 | { 84 | type: "enemy0", 85 | points: [[-6.423,-4.604,-11.022],[-5.502,2.845,-8.485]], 86 | movement: "single", 87 | }, 88 | { 89 | type: "enemy1", 90 | points: [[-3.143,-16.484,-7.948],[-2.804,1.664,-6.817],[-2.624,4.543,-6.542]], 91 | movement: "pingpong", 92 | }, 93 | { 94 | type: "enemy0", 95 | points: [[0.102,-12.302,-8.235],[0.140,0.816,-9.515],[0.159,6.137,-10.021]], 96 | movement: "pingpong", 97 | }, 98 | { 99 | type: "enemy1", 100 | points: [[3.143,-16.484,-7.948],[2.804,1.664,-6.817],[2.624,4.543,-6.542]], 101 | movement: "pingpong", 102 | } 103 | ] 104 | }, 105 | { 106 | subwave: "6", 107 | enemies:[ 108 | { 109 | type: "enemy0", 110 | points: [[7.419,-2.617,-4.583],[6.251,1.269,-5.546],[3.603,3.048,-6.824],[-4.661,2.923,-6.788],[-6.749,5.301,-5.183]], 111 | movement: "pingpong", 112 | }, 113 | { 114 | type: "enemy0", 115 | points: [[-7.419,-6.477,-4.873],[-6.251,1.269,-6.389],[-3.603,3.048,-8.657],[4.661,2.923,-8.159],[6.749,5.301,-5.473]], 116 | movement: "pingpong", 117 | }, 118 | { 119 | type: ["enemy1","enemy0","enemy1"], 120 | points: [[-3.570,-10.350,-9.308],[-1.735,1.652,-10.408],[2.206,2.076,-10.369],[2.617,5.974,-10.166],[-2.128,5.852,-10.231]], 121 | movement: "loop", 122 | enemyTimeOffset: -1000, 123 | } 124 | ] 125 | }, 126 | { 127 | subwave: "7", 128 | enemies:[ 129 | { 130 | type: "enemy1", 131 | points: [[-0.117,-2.922,-9.989],[-6.728,3.366,-7.252],[5.960,3.435,-7.280]], 132 | movement: "pingpong", 133 | }, 134 | { 135 | type: "enemy1", 136 | points: [[0.051,-3.250,-10.318],[4.455,2.252,-7.790],[-3.950,2.320,-7.818]], 137 | movement: "pingpong", 138 | }, 139 | { 140 | type: "enemy1", 141 | points: [[0.058,-2.013,-9.194],[4.455,4.694,-6.410],[-3.950,4.763,-6.438]], 142 | movement: "pingpong", 143 | }, 144 | { 145 | type: "enemy1", 146 | points: [[-0.019,-1.414,-8.712],[-2.206,5.633,-5.545],[1.956,5.702,-5.573]], 147 | movement: "pingpong", 148 | }, 149 | { 150 | type: "enemy0", 151 | points: [[-5.886,-1.106,-5.230],[-4.331,4.539,-4.951],[0.000,6.983,-4.318]], 152 | movement: "single", 153 | }, 154 | { 155 | type: "enemy0", 156 | points: [[4.955,-3.852,-5.815],[3.565,0.447,-6.539],[-0.237,1.357,-7.004]], 157 | movement: "single", 158 | } 159 | ] 160 | }, 161 | { 162 | subwave: "8", 163 | enemies:[ 164 | { 165 | type: "enemy1", 166 | points: [[-5.898,-3.573,-4.946],[-5.920,1.011,-4.932],[-5.901,3.772,-4.958]], 167 | movement: "pingpong", 168 | }, 169 | { 170 | type: "enemy1", 171 | points: [[-4.267,-4.889,-6.345],[-4.289,1.815,-6.331],[-4.270,4.556,-6.357]], 172 | movement: "pingpong", 173 | }, 174 | { 175 | type: "enemy1", 176 | points: [[-2.281,-6.165,-7.163],[-2.304,2.565,-7.150],[-2.284,5.285,-7.176]], 177 | movement: "pingpong", 178 | }, 179 | { 180 | type: "enemy1", 181 | points: [[-0.043,-7.572,-7.469],[-0.066,1.799,-7.455],[-0.046,4.560,-7.481]], 182 | movement: "pingpong", 183 | }, 184 | { 185 | type: "enemy1", 186 | points: [[2.119,-9.295,-7.249],[2.097,1.367,-7.236],[2.116,4.108,-7.262]], 187 | movement: "pingpong", 188 | }, 189 | { 190 | type: "enemy1", 191 | points: [[4.068,-11.333,-6.409],[4.046,0.575,-6.395],[4.065,3.295,-6.422]], 192 | movement: "pingpong", 193 | }, 194 | { 195 | type: "enemy1", 196 | points: [[5.715,-14.491,-5.093],[5.693,1.654,-5.079],[5.712,4.500,-5.105]], 197 | movement: "pingpong", 198 | } 199 | ] 200 | }, 201 | { 202 | subwave: "9", 203 | enemies:[ 204 | { 205 | type: ["enemy1","enemy1","enemy1","enemy1"], 206 | points: [[-8.397,-5.214,-8.919],[-5.099,5.574,-7.011],[-0.154,2.375,-7.229],[4.780,-0.703,-7.721],[7.075,2.325,-8.069],[5.146,5.540,-8.403],[0.530,2.524,-9.188],[-4.186,-0.706,-9.842],[-6.878,2.511,-8.356]], 207 | movement: "loop", 208 | enemyTimeOffset: -800, 209 | }, 210 | { 211 | type: ["enemy1","enemy1","enemy1"], 212 | points: [[9.242,-9.088,-7.545],[5.098,5.615,-5.763],[0.153,2.416,-5.982],[-4.781,-0.661,-6.473],[-7.076,2.366,-6.821],[-5.147,5.581,-7.156],[-0.531,2.565,-7.941],[4.185,-0.665,-8.594],[6.877,2.552,-7.108]], 213 | movement: "loop", 214 | enemyTimeOffset: -800, 215 | } 216 | ] 217 | }, 218 | { 219 | subwave: "10", 220 | enemies:[ 221 | { 222 | type: "enemy0", 223 | points: [[3.269,-4.923,-7.350],[5.260,7.431,-5.765]], 224 | movement: "single", 225 | }, 226 | { 227 | type: "enemy0", 228 | points: [[-3.277,-4.923,-7.350],[-5.267,7.431,-5.765]], 229 | movement: "single", 230 | }, 231 | { 232 | type: "enemy0", 233 | points: [[-1.633,-3.221,-7.997],[-2.162,5.305,-6.815]], 234 | movement: "single", 235 | }, 236 | { 237 | type: "enemy0", 238 | points: [[1.532,-3.221,-7.997],[2.185,5.305,-6.815]], 239 | movement: "single", 240 | }, 241 | { 242 | type: ["enemy1","enemy1","enemy1","enemy1"], 243 | points: [[4.303,-1.904,-5.736],[-0.275,-2.036,-6.847],[-4.825,-1.601,-5.221],[-4.558,1.129,-5.118],[-3.569,3.022,-5.324],[-1.982,0.774,-5.739],[-0.064,2.984,-6.016],[1.848,0.764,-5.803],[3.476,2.997,-5.457],[4.700,0.738,-5.096],[5.241,-10.892,-5.080]], 244 | movement: "loop", 245 | enemyTimeOffset: -1500, 246 | } 247 | ] 248 | }, 249 | { 250 | subwave: "11", 251 | enemies:[ 252 | { 253 | type: "enemy3", 254 | points: [[0.238,-9.234,-9.310],[5.180,3.034,-9.861],[-0.018,2.720,-7.559],[-5.041,3.034,-9.861]], 255 | movement: "pingpong", 256 | } 257 | ] 258 | } 259 | ] 260 | }, 261 | { 262 | name: "WAVE 2", 263 | sequences: [ 264 | { 265 | subwave: "2001", 266 | enemies:[ 267 | { 268 | type: ["enemy1","enemy1","enemy1","enemy0"], 269 | points: [[0.102,-11.982,-9.037],[0.142,3.765,-13.133],[-1.412,2.089,-8.481],[0.152,1.404,-2.714],[0.164,5.907,-2.462],[1.545,6.252,-9.056]], 270 | movement: "loop", 271 | enemyTimeOffset: -1585, 272 | } 273 | ] 274 | }, 275 | { 276 | subwave: "2002", 277 | enemies:[ 278 | { 279 | type: ["enemy0","enemy1","enemy0"], 280 | points: [[0.102,-20.812,-7.970],[0.142,3.765,-10.804],[-1.941,1.304,-7.540],[-1.808,1.404,-3.581],[-1.796,5.907,-1.843],[-1.697,6.252,-6.858]], 281 | movement: "loop", 282 | enemyTimeOffset: -1300, 283 | loopStart: 2, 284 | }, 285 | { 286 | type: ["enemy0","enemy1","enemy0"], 287 | points: [[0.102,-11.982,-9.037],[0.142,3.765,-10.804],[1.859,1.304,-7.540],[1.992,1.404,-3.581],[2.003,5.907,-1.843],[2.102,6.252,-6.858]], 288 | movement: "loop", 289 | enemyTimeOffset: -1300, 290 | loopStart: 2, 291 | } 292 | ] 293 | }, 294 | { 295 | subwave: "2003", 296 | enemies:[ 297 | { 298 | type: ["enemy0","enemy1","enemy0"], 299 | points: [[-3.635,-12.072,-5.277],[-3.807,3.628,-8.542],[-2.358,1.958,-6.590],[-1.562,1.404,-2.714],[-3.219,4.870,-2.166],[-4.926,5.662,-7.516]], 300 | movement: "loop", 301 | enemyTimeOffset: -1585, 302 | }, 303 | { 304 | type: "enemy2", 305 | points: [[0.373,-11.356,-50.516],[0.142,3.079,-17.640],[0.142,2.955,-12.288],[0.142,2.785,-8.047],[0.142,2.458,-6.873],[0.142,2.326,-6.423]], 306 | movement: "single", 307 | }, 308 | { 309 | type: ["enemy1","enemy0","enemy1"], 310 | points: [[3.635,-12.072,-5.277],[3.807,3.628,-8.542],[2.358,1.958,-6.590],[1.562,1.404,-2.714],[3.219,4.870,-2.166],[4.926,5.662,-7.516]], 311 | movement: "loop", 312 | enemyTimeOffset: -1585, 313 | } 314 | ] 315 | }, 316 | { 317 | subwave: "2004", 318 | enemies:[ 319 | { 320 | type: "enemy2", 321 | points: [[-2.878,-7.579,-5.481],[-3.763,1.667,-4.806],[1.462,2.373,-3.978],[2.949,3.076,-1.987]], 322 | movement: "pingpong", 323 | }, 324 | { 325 | type: "enemy2", 326 | points: [[2.348,-11.066,-5.481],[3.763,1.667,-4.806],[-1.462,2.373,-3.978],[-2.949,3.076,-1.987]], 327 | movement: "pingpong", 328 | }, 329 | { 330 | type: "enemy2", 331 | points: [[4.017,-15.603,-32.074],[3.047,-0.480,-23.429],[1.549,1.924,-7.464]], 332 | movement: "single", 333 | }, 334 | { 335 | type: "enemy2", 336 | points: [[-2.487,-14.442,-19.974],[-3.672,-0.480,-16.871],[-3.028,3.002,-7.177]], 337 | movement: "single", 338 | }, 339 | { 340 | type: "enemy2", 341 | points: [[-1.403,-9.224,-24.527],[-1.992,0.174,-26.051],[0.580,3.837,-5.928]], 342 | movement: "single", 343 | }, 344 | { 345 | type: "enemy2", 346 | points: [[0.583,-12.435,-27.199],[0.294,-0.480,-21.448],[-1.835,3.868,-8.518]], 347 | movement: "single", 348 | } 349 | ] 350 | }, 351 | { 352 | subwave: "2005", 353 | enemies:[ 354 | { 355 | type: ["enemy2","enemy2","enemy2","enemy2"], 356 | points: [[4.101,-5.177,-5.577],[5.205,1.154,-4.972],[3.834,5.993,-1.522],[0.169,4.682,-3.751],[-3.488,3.536,-6.089],[-5.189,5.007,-4.362],[-3.759,6.553,-2.504],[-0.338,5.556,-5.004],[3.158,4.411,-7.553],[5.153,5.207,-4.439]], 357 | movement: "loop", 358 | enemyTimeOffset: -800, 359 | loopStart: 2, 360 | }, 361 | { 362 | type: "enemy0", 363 | points: [[-0.063,-6.742,-7.931],[-5.342,2.732,-7.897],[4.737,2.818,-7.933]], 364 | movement: "pingpong", 365 | }, 366 | { 367 | type: "enemy0", 368 | points: [[-0.525,-7.183,-9.356],[5.342,1.205,-9.389],[-4.737,1.291,-9.424]], 369 | movement: "pingpong", 370 | } 371 | ] 372 | }, 373 | { 374 | subwave: "2006", 375 | enemies:[ 376 | { 377 | type: ["enemy0","enemy0"], 378 | points: [[-4.493,-1.832,-6.394],[6.629,-1.590,-5.740],[6.279,1.103,-4.304],[4.222,3.393,-3.757],[0.087,4.573,-4.070],[-4.130,3.392,-3.916],[-6.475,0.219,-4.915],[-6.310,-16.689,-5.513]], 379 | movement: "loop", 380 | enemyTimeOffset: -1100, 381 | }, 382 | { 383 | type: ["enemy0","enemy1","enemy2"], 384 | points: [[4.541,-1.901,-6.427],[-6.699,-1.616,-5.719],[-6.636,1.504,-2.983],[-4.450,4.425,-1.980],[-0.088,5.989,-2.373],[4.322,4.430,-2.141],[6.786,0.236,-3.210],[6.295,-5.078,-5.031]], 385 | movement: "loop", 386 | enemyTimeOffset: -1100, 387 | }, 388 | { 389 | type: ["enemy1","enemy1"], 390 | points: [[4.193,-1.096,-6.432],[-6.186,-0.905,-5.777],[-5.680,1.241,-4.993],[-3.541,2.773,-5.370],[-0.081,3.559,-6.116],[3.422,2.766,-5.528],[5.716,0.546,-5.292],[6.081,-10.693,-5.641]], 391 | movement: "loop", 392 | enemyTimeOffset: -1100, 393 | }, 394 | { 395 | type: ["enemy0","enemy1","enemy2"], 396 | points: [[-3.823,-1.762,-6.705],[4.949,-1.243,-5.718],[3.229,1.704,-6.223],[0.074,2.425,-7.033],[-3.120,1.684,-6.379],[-5.116,-0.508,-5.958],[-5.312,-7.883,-5.939]], 397 | movement: "loop", 398 | enemyTimeOffset: -1100, 399 | } 400 | ] 401 | }, 402 | { 403 | subwave: "2007", 404 | enemies:[ 405 | { 406 | type: ["enemy3","enemy3"], 407 | points: [[0.000,-11.755,-24.508],[-0.154,0.110,-19.769],[-7.344,0.112,-3.727],[-7.629,-5.338,-2.696],[-4.647,-6.852,-19.380],[-3.776,4.304,-7.531],[-1.544,5.107,-3.990]], 408 | movement: "loop", 409 | enemyTimeOffset: -3000, 410 | loopStart: 2, 411 | }, 412 | { 413 | type: ["enemy3","enemy3"], 414 | points: [[-0.000,-11.993,-25.733],[0.154,0.110,-23.081],[7.344,0.112,-3.727],[7.629,-5.338,-2.696],[4.647,-6.852,-19.287],[3.776,4.304,-7.531],[1.544,5.107,-3.990]], 415 | movement: "loop", 416 | enemyTimeOffset: -3000, 417 | loopStart: 2, 418 | } 419 | ] 420 | } 421 | ] 422 | } 423 | ]; 424 | -------------------------------------------------------------------------------- /assets/fonts/mozillavr.fnt: -------------------------------------------------------------------------------- 1 | info face="Y:\mozvr\work\ashooter\font\font" size=32 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 2 | common lineHeight=90 base=26 scaleW=1024 scaleH=512 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0 3 | page id=0 file="Y:\mozvr\work\ashooter\font\font.png" 4 | chars count=86 5 | char id=97 x=376 y=51 width=42 height=54 xoffset=0 yoffset=38 xadvance=47 page=0 chnl=0 letter="a" 6 | char id=98 x=376 y=106 width=42 height=71 xoffset=0 yoffset=18 xadvance=47 page=0 chnl=0 letter="b" 7 | char id=99 x=419 y=0 width=39 height=52 xoffset=0 yoffset=38 xadvance=44 page=0 chnl=0 letter="c" 8 | char id=100 x=289 y=0 width=45 height=73 xoffset=0 yoffset=16 xadvance=50 page=0 chnl=0 letter="d" 9 | char id=101 x=376 y=302 width=42 height=52 xoffset=0 yoffset=37 xadvance=47 page=0 chnl=0 letter="e" 10 | char id=102 x=419 y=53 width=38 height=72 xoffset=0 yoffset=16 xadvance=43 page=0 chnl=0 letter="f" 11 | char id=103 x=126 y=278 width=54 height=72 xoffset=0 yoffset=35 xadvance=59 page=0 chnl=0 letter="g" 12 | char id=104 x=376 y=178 width=42 height=71 xoffset=0 yoffset=17 xadvance=47 page=0 chnl=0 letter="h" 13 | char id=105 x=510 y=175 width=18 height=70 xoffset=0 yoffset=18 xadvance=23 page=0 chnl=0 letter="i" 14 | char id=106 x=487 y=266 width=27 height=92 xoffset=0 yoffset=18 xadvance=32 page=0 chnl=0 letter="j" 15 | char id=107 x=285 y=182 width=45 height=76 xoffset=0 yoffset=14 xadvance=50 page=0 chnl=0 letter="k" 16 | char id=108 x=459 y=435 width=22 height=75 xoffset=0 yoffset=15 xadvance=27 page=0 chnl=0 letter="l" 17 | char id=109 x=66 y=333 width=59 height=51 xoffset=0 yoffset=36 xadvance=64 page=0 chnl=0 letter="m" 18 | char id=110 x=335 y=0 width=43 height=50 xoffset=0 yoffset=38 xadvance=48 page=0 chnl=0 letter="n" 19 | char id=111 x=332 y=237 width=43 height=52 xoffset=0 yoffset=38 xadvance=48 page=0 chnl=0 letter="o" 20 | char id=112 x=331 y=401 width=44 height=73 xoffset=0 yoffset=39 xadvance=49 page=0 chnl=0 letter="p" 21 | char id=113 x=332 y=74 width=43 height=71 xoffset=0 yoffset=39 xadvance=48 page=0 chnl=0 letter="q" 22 | char id=114 x=379 y=0 width=30 height=50 xoffset=0 yoffset=39 xadvance=35 page=0 chnl=0 letter="r" 23 | char id=115 x=376 y=250 width=42 height=51 xoffset=0 yoffset=39 xadvance=47 page=0 chnl=0 letter="s" 24 | char id=116 x=457 y=126 width=32 height=65 xoffset=0 yoffset=25 xadvance=37 page=0 chnl=0 letter="t" 25 | char id=117 x=376 y=422 width=41 height=53 xoffset=0 yoffset=38 xadvance=46 page=0 chnl=0 letter="u" 26 | char id=118 x=69 y=280 width=47 height=51 xoffset=0 yoffset=38 xadvance=52 page=0 chnl=0 letter="v" 27 | char id=119 x=0 y=280 width=68 height=52 xoffset=0 yoffset=38 xadvance=73 page=0 chnl=0 letter="w" 28 | char id=120 x=125 y=449 width=53 height=55 xoffset=0 yoffset=37 xadvance=58 page=0 chnl=0 letter="x" 29 | char id=121 x=234 y=204 width=50 height=74 xoffset=0 yoffset=37 xadvance=55 page=0 chnl=0 letter="y" 30 | char id=122 x=418 y=422 width=40 height=51 xoffset=0 yoffset=38 xadvance=45 page=0 chnl=0 letter="z" 31 | char id=48 x=286 y=74 width=45 height=51 xoffset=0 yoffset=40 xadvance=50 page=0 chnl=0 letter="0" 32 | char id=49 x=453 y=288 width=33 height=51 xoffset=0 yoffset=39 xadvance=38 page=0 chnl=0 letter="1" 33 | char id=50 x=285 y=130 width=46 height=51 xoffset=0 yoffset=39 xadvance=51 page=0 chnl=0 letter="2" 34 | char id=51 x=183 y=206 width=47 height=55 xoffset=0 yoffset=37 xadvance=52 page=0 chnl=0 letter="3" 35 | char id=52 x=284 y=447 width=46 height=55 xoffset=0 yoffset=35 xadvance=51 page=0 chnl=0 letter="4" 36 | char id=53 x=331 y=182 width=44 height=54 xoffset=0 yoffset=35 xadvance=49 page=0 chnl=0 letter="5" 37 | char id=54 x=284 y=335 width=46 height=56 xoffset=0 yoffset=33 xadvance=51 page=0 chnl=0 letter="6" 38 | char id=55 x=241 y=0 width=47 height=59 xoffset=0 yoffset=36 xadvance=52 page=0 chnl=0 letter="7" 39 | char id=56 x=284 y=392 width=46 height=54 xoffset=0 yoffset=34 xadvance=51 page=0 chnl=0 letter="8" 40 | char id=57 x=284 y=279 width=47 height=55 xoffset=0 yoffset=35 xadvance=52 page=0 chnl=0 letter="9" 41 | char id=65 x=71 y=141 width=57 height=64 xoffset=0 yoffset=26 xadvance=62 page=0 chnl=0 letter="A" 42 | char id=66 x=191 y=0 width=49 height=63 xoffset=0 yoffset=26 xadvance=54 page=0 chnl=0 letter="B" 43 | char id=67 x=234 y=438 width=49 height=66 xoffset=0 yoffset=24 xadvance=54 page=0 chnl=0 letter="C" 44 | char id=68 x=138 y=0 width=52 height=63 xoffset=0 yoffset=26 xadvance=57 page=0 chnl=0 letter="D" 45 | char id=69 x=331 y=335 width=44 height=65 xoffset=0 yoffset=25 xadvance=49 page=0 chnl=0 letter="E" 46 | char id=70 x=418 y=355 width=40 height=66 xoffset=0 yoffset=25 xadvance=45 page=0 chnl=0 letter="F" 47 | char id=71 x=132 y=70 width=52 height=65 xoffset=0 yoffset=25 xadvance=57 page=0 chnl=0 letter="G" 48 | char id=72 x=181 y=267 width=52 height=67 xoffset=0 yoffset=23 xadvance=57 page=0 chnl=0 letter="H" 49 | char id=73 x=490 y=175 width=19 height=65 xoffset=0 yoffset=24 xadvance=24 page=0 chnl=0 letter="I" 50 | char id=74 x=459 y=0 width=30 height=79 xoffset=0 yoffset=24 xadvance=35 page=0 chnl=0 letter="J" 51 | char id=75 x=74 y=70 width=57 height=65 xoffset=0 yoffset=25 xadvance=62 page=0 chnl=0 letter="K" 52 | char id=76 x=376 y=355 width=41 height=66 xoffset=0 yoffset=24 xadvance=46 page=0 chnl=0 letter="L" 53 | char id=77 x=0 y=211 width=69 height=68 xoffset=0 yoffset=24 xadvance=74 page=0 chnl=0 letter="M" 54 | char id=78 x=185 y=64 width=51 height=67 xoffset=0 yoffset=24 xadvance=56 page=0 chnl=0 letter="N" 55 | char id=79 x=125 y=385 width=56 height=63 xoffset=0 yoffset=26 xadvance=61 page=0 chnl=0 letter="O" 56 | char id=80 x=236 y=132 width=48 height=65 xoffset=0 yoffset=26 xadvance=53 page=0 chnl=0 letter="P" 57 | char id=81 x=63 y=400 width=61 height=75 xoffset=0 yoffset=25 xadvance=66 page=0 chnl=0 letter="Q" 58 | char id=82 x=182 y=404 width=51 height=65 xoffset=0 yoffset=26 xadvance=56 page=0 chnl=0 letter="R" 59 | char id=83 x=70 y=211 width=58 height=66 xoffset=0 yoffset=24 xadvance=63 page=0 chnl=0 letter="S" 60 | char id=84 x=182 y=335 width=51 height=68 xoffset=0 yoffset=23 xadvance=56 page=0 chnl=0 letter="T" 61 | char id=85 x=184 y=136 width=51 height=67 xoffset=0 yoffset=23 xadvance=56 page=0 chnl=0 letter="U" 62 | char id=86 x=80 y=0 width=57 height=69 xoffset=0 yoffset=22 xadvance=62 page=0 chnl=0 letter="V" 63 | char id=87 x=0 y=0 width=79 height=69 xoffset=0 yoffset=26 xadvance=84 page=0 chnl=0 letter="W" 64 | char id=88 x=0 y=400 width=62 height=67 xoffset=0 yoffset=26 xadvance=67 page=0 chnl=0 letter="X" 65 | char id=89 x=129 y=136 width=54 height=69 xoffset=0 yoffset=26 xadvance=59 page=0 chnl=0 letter="Y" 66 | char id=90 x=237 y=64 width=48 height=65 xoffset=0 yoffset=28 xadvance=53 page=0 chnl=0 letter="Z" 67 | char id=46 x=94 y=476 width=16 height=19 xoffset=0 yoffset=71 xadvance=21 page=0 chnl=0 letter="." 68 | char id=44 x=75 y=476 width=18 height=29 xoffset=0 yoffset=72 xadvance=23 page=0 chnl=0 letter="," 69 | char id=58 x=482 y=454 width=20 height=46 xoffset=0 yoffset=44 xadvance=25 page=0 chnl=0 letter=":" 70 | char id=64 x=0 y=141 width=70 height=69 xoffset=0 yoffset=19 xadvance=75 page=0 chnl=0 letter="@" 71 | char id=35 x=234 y=279 width=49 height=70 xoffset=0 yoffset=20 xadvance=54 page=0 chnl=0 letter="#" 72 | char id=60 x=419 y=288 width=33 height=65 xoffset=0 yoffset=27 xadvance=38 page=0 chnl=0 letter="<" 73 | char id=62 x=456 y=198 width=33 height=67 xoffset=0 yoffset=25 xadvance=38 page=0 chnl=0 letter=">" 74 | char id=40 x=487 y=359 width=26 height=94 xoffset=0 yoffset=13 xadvance=31 page=0 chnl=0 letter="(" 75 | char id=41 x=459 y=340 width=27 height=94 xoffset=0 yoffset=12 xadvance=32 page=0 chnl=0 letter=")" 76 | char id=91 x=490 y=89 width=23 height=85 xoffset=0 yoffset=17 xadvance=28 page=0 chnl=0 letter="[" 77 | char id=93 x=490 y=0 width=23 height=88 xoffset=0 yoffset=16 xadvance=28 page=0 chnl=0 letter="]" 78 | char id=63 x=419 y=126 width=37 height=71 xoffset=0 yoffset=20 xadvance=42 page=0 chnl=0 letter="?" 79 | char id=33 x=514 y=0 width=17 height=75 xoffset=0 yoffset=15 xadvance=22 page=0 chnl=0 letter="!" 80 | char id=45 x=47 y=480 width=27 height=18 xoffset=0 yoffset=53 xadvance=32 page=0 chnl=0 letter="-" 81 | char id=95 x=0 y=468 width=53 height=11 xoffset=0 yoffset=90 xadvance=58 page=0 chnl=0 letter="_" 82 | char id=126 x=0 y=480 width=46 height=24 xoffset=0 yoffset=46 xadvance=51 page=0 chnl=0 letter="~" 83 | char id=36 x=234 y=350 width=49 height=87 xoffset=0 yoffset=12 xadvance=54 page=0 chnl=0 letter="$" 84 | char id=37 x=0 y=70 width=73 height=70 xoffset=0 yoffset=20 xadvance=78 page=0 chnl=0 letter="%" 85 | char id=38 x=0 y=333 width=65 height=66 xoffset=0 yoffset=26 xadvance=70 page=0 chnl=0 letter="&" 86 | char id=47 x=419 y=198 width=36 height=89 xoffset=0 yoffset=14 xadvance=41 page=0 chnl=0 letter="/" 87 | char id=42 x=332 y=290 width=41 height=43 xoffset=0 yoffset=20 xadvance=46 page=0 chnl=0 letter="*" 88 | char id=43 x=129 y=206 width=53 height=60 xoffset=0 yoffset=29 xadvance=58 page=0 chnl=0 letter="+" 89 | char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=30 page=0 chnl=0 letter=" " 90 | char id=9 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=240 page=0 chnl=0 letter=" " 91 | 92 | kernings count=0 93 | -------------------------------------------------------------------------------- /assets/fonts/mozillavr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/fonts/mozillavr.png -------------------------------------------------------------------------------- /assets/images/damage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/damage.png -------------------------------------------------------------------------------- /assets/images/decal0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/decal0.png -------------------------------------------------------------------------------- /assets/images/decal1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/decal1.png -------------------------------------------------------------------------------- /assets/images/decal2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/decal2.png -------------------------------------------------------------------------------- /assets/images/enemy_diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/enemy_diff.jpg -------------------------------------------------------------------------------- /assets/images/enemy_emissive0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/enemy_emissive0.jpg -------------------------------------------------------------------------------- /assets/images/enemy_emissive1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/enemy_emissive1.jpg -------------------------------------------------------------------------------- /assets/images/enemy_emissive2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/enemy_emissive2.jpg -------------------------------------------------------------------------------- /assets/images/enemy_emissive3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/enemy_emissive3.jpg -------------------------------------------------------------------------------- /assets/images/enemy_emissive_template.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/enemy_emissive_template.jpg -------------------------------------------------------------------------------- /assets/images/enemy_spec.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/enemy_spec.jpg -------------------------------------------------------------------------------- /assets/images/face_guide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/face_guide.png -------------------------------------------------------------------------------- /assets/images/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/font.png -------------------------------------------------------------------------------- /assets/images/fx1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/fx1.png -------------------------------------------------------------------------------- /assets/images/fx2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/fx2.png -------------------------------------------------------------------------------- /assets/images/fx3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/fx3.png -------------------------------------------------------------------------------- /assets/images/fx4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/fx4.png -------------------------------------------------------------------------------- /assets/images/fx5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/fx5.png -------------------------------------------------------------------------------- /assets/images/fx6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/fx6.png -------------------------------------------------------------------------------- /assets/images/fx7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/fx7.png -------------------------------------------------------------------------------- /assets/images/fx8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/fx8.png -------------------------------------------------------------------------------- /assets/images/grad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/grad.png -------------------------------------------------------------------------------- /assets/images/gun_diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/gun_diff.jpg -------------------------------------------------------------------------------- /assets/images/gun_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/gun_normal.png -------------------------------------------------------------------------------- /assets/images/gun_spec.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/gun_spec.jpg -------------------------------------------------------------------------------- /assets/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/icon-128.png -------------------------------------------------------------------------------- /assets/images/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/icon-256.png -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/menu.png -------------------------------------------------------------------------------- /assets/images/set_diff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/set_diff.jpg -------------------------------------------------------------------------------- /assets/images/sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/images/sky.jpg -------------------------------------------------------------------------------- /assets/models/restart.json: -------------------------------------------------------------------------------- 1 | { 2 | "geometries":[{ 3 | "data":{ 4 | "name":"bgGeometry", 5 | "vertices":[-0.7479,-0.0557,-0.2918,-0.8309,-0.0557,0.3222,-0.6788,0.0557,-0.2648,-0.7542,0.0557,0.2925,0.7783,-0.0557,-0.2697,0.8005,-0.0557,0.2392,0.7064,0.0557,-0.2447,0.7265,0.0557,0.2171,-0.7479,-0.0557,-0.2918,-0.8309,-0.0557,0.3222,-0.7542,0.0557,0.2925,-0.7542,0.0557,0.2925,-0.6788,0.0557,-0.2648,-0.6788,0.0557,-0.2648,0.7265,0.0557,0.2171,0.7265,0.0557,0.2171,0.7064,0.0557,-0.2447,0.7064,0.0557,-0.2447,0.8005,-0.0557,0.2392,0.7783,-0.0557,-0.2697], 6 | "normals":[-0.8321,-0.1125,-0.543,0,-0,-1,0.836,-0.0364,-0.5475,0.0141,-0.9738,-0.2268,0.0495,0.9729,-0.2258], 7 | "metadata":{ 8 | "generator":"io_three", 9 | "vertices":20, 10 | "version":3, 11 | "normals":5, 12 | "uvs":0, 13 | "faces":5 14 | }, 15 | "uvs":[], 16 | "faces":[33,9,10,12,8,0,0,0,0,33,11,14,16,13,1,1,1,1,33,15,18,19,17,2,2,2,2,33,0,2,6,4,3,3,3,3,33,5,7,3,1,4,4,4,4] 17 | }, 18 | "type":"Geometry", 19 | "uuid":"3C994563-C5DA-4D55-820D-E01948F9AA1C" 20 | },{ 21 | "data":{ 22 | "name":"restart.001Geometry", 23 | "vertices":[0.412,0.1127,0.05,0.5907,0.1127,0.138,0.5956,0.1127,-0.1394,0.5477,0.1127,-0.1653,0.4745,0.1127,-0.0017,0.3612,0.1127,-0.1209,0.41,0.1127,-0.1336,0.5009,0.1127,-0.0203,0.5526,0.1127,-0.0613,0.2186,0.1127,-0.1028,0.2557,0.1127,-0.1316,0.3641,0.1127,-0.0789,0.371,0.1127,0.0833,0.2293,0.1127,0.1516,0.2401,0.1127,0.1047,0.328,0.1127,0.0686,0.327,0.1127,0.0159,0.2733,0.1127,0.0334,0.285,0.1127,-0.0105,0.3251,0.1127,-0.0203,0.3231,0.1127,-0.0613,0.0657,0.1127,0.135,0.0731,0.1127,0.0881,0.1522,0.1127,0.051,0.032,0.1127,-0.0896,0.2157,0.1127,-0.0847,0.2362,0.1127,-0.0447,0.1053,0.1127,-0.0574,0.1961,0.1127,0.0588,0.0496,0.1127,0.072,0.0154,0.1127,0.05,-0.0305,0.1127,0.0647,-0.0315,0.1127,-0.1258,-0.0676,0.1127,-0.0974,-0.0627,0.1127,0.0725,-0.1428,0.1127,0.0979,-0.1672,0.1127,0.1575,-0.1228,0.1127,-0.1355,-0.091,0.1127,-0.1072,-0.1662,0.1127,0.0764,-0.2024,0.1127,0.0667,-0.2903,0.1127,-0.158,-0.1408,0.1127,-0.0916,-0.3259,0.1127,-0.0823,-0.2825,0.1127,-0.0574,-0.2932,0.1127,0.1653,-0.4438,0.1127,0.0842,-0.3821,0.1127,0.0393,-0.4505,0.1127,-0.0926,-0.3919,0.1127,-0.0799,-0.3518,0.1127,0.0207,-0.3245,0.1127,0.0002,-0.4475,0.1127,0.0544,-0.4739,0.1127,0.0315,-0.5179,0.1127,0.052,-0.5169,0.1127,-0.117,-0.5599,0.1127,-0.1433,-0.5481,0.1127,0.0618,-0.6185,0.1127,0.093,-0.6312,0.1127,0.1516,-0.1901,0.1127,0.0345,0.5547,0.1127,0.0727,0.5528,0.1127,-0.0177,0.4535,0.1127,0.0474,-0.152,0.1127,-0.0649,-0.2245,0.1127,-0.0885,-0.3962,0.1127,0.0808,-0.3238,0.1127,0.0377,-0.3254,0.1127,0.1199], 24 | "normals":[0,-0,-1,0,0,-1,-0,0,-1,-0,-0,-1,0,0,-1,-0,-0,-1,0,0,-1,0,-0.0001,-1,-0,0,-1,0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,0,0,-1,0,0,-1,-0,0,-1], 25 | "metadata":{ 26 | "generator":"io_three", 27 | "vertices":69, 28 | "version":3, 29 | "normals":16, 30 | "uvs":0, 31 | "faces":61 32 | }, 33 | "uvs":[], 34 | "faces":[32,0,1,61,0,0,0,32,19,16,11,1,1,1,32,27,24,23,2,2,2,32,36,29,34,3,3,3,32,42,65,64,4,4,4,32,47,66,67,5,5,5,32,59,52,57,3,3,3,32,64,39,38,1,1,1,32,4,63,62,6,6,6,32,68,46,45,2,2,2,32,63,0,61,7,7,7,32,39,60,40,2,2,2,32,40,65,41,1,1,1,32,19,18,16,1,1,1,32,10,9,20,1,1,1,32,11,10,20,2,2,2,32,18,17,16,1,1,1,32,11,20,19,1,1,1,32,14,13,15,4,4,4,32,15,13,12,1,1,1,32,12,11,16,8,8,8,32,16,15,12,1,1,1,32,22,21,23,1,1,1,32,27,26,25,0,0,0,32,23,21,28,1,1,1,32,27,25,24,0,0,0,32,23,28,27,0,0,0,32,30,31,29,1,1,1,32,36,34,35,3,3,3,32,34,31,33,9,9,9,32,31,34,29,4,4,4,32,33,31,32,0,0,0,32,38,37,42,2,2,2,32,42,41,65,2,2,2,32,64,38,42,3,3,3,32,44,43,51,8,8,8,32,50,49,47,2,2,2,32,67,44,51,10,10,10,32,49,48,47,8,8,8,32,67,51,50,11,11,11,32,46,66,47,1,1,1,32,50,47,67,12,12,12,32,53,54,52,1,1,1,32,59,57,58,3,3,3,32,57,55,56,0,0,0,32,54,57,52,1,1,1,32,57,54,55,4,4,4,32,64,60,39,1,1,1,32,61,1,62,0,0,0,32,2,3,8,8,8,8,32,7,6,4,2,2,2,32,1,2,62,3,3,3,32,6,5,4,1,1,1,32,62,2,8,3,3,3,32,0,63,4,13,13,13,32,7,4,62,1,1,1,32,62,8,7,2,2,2,32,45,44,67,14,14,14,32,68,66,46,2,2,2,32,45,67,68,15,15,15,32,40,60,65,9,9,9] 35 | }, 36 | "type":"Geometry", 37 | "uuid":"7FA3E1DB-BBED-44D3-ACBA-7FE2D6D58928" 38 | }], 39 | "materials":[{ 40 | "name":"mat-restart", 41 | "color":16777215, 42 | "uuid":"6CF285F3-D567-4695-8019-F97C0A4B5A9C", 43 | "depthTest":true, 44 | "blending":"NormalBlending", 45 | "type":"MeshBasicMaterial", 46 | "depthWrite":true 47 | },{ 48 | "name":"mat-restart-bg", 49 | "color":10616832, 50 | "uuid":"415B601E-B130-4152-9FBA-33A7E6B59476", 51 | "vertexColors":0, 52 | "depthTest":true, 53 | "emissive":6356992, 54 | "blending":"NormalBlending", 55 | "type":"MeshLambertMaterial", 56 | "depthWrite":true 57 | }], 58 | "textures":[], 59 | "images":[], 60 | "metadata":{ 61 | "type":"Object", 62 | "generator":"io_three", 63 | "sourceFile":"restart.blend", 64 | "version":4.4 65 | }, 66 | "object":{ 67 | "type":"Scene", 68 | "uuid":"3E5C22DE-2FF1-49AE-93EB-6C0E7F20C273", 69 | "children":[{ 70 | "name":"bg", 71 | "uuid":"95058BEF-8418-4044-A5AD-8A5EFD52155C", 72 | "matrix":[-1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1], 73 | "visible":true, 74 | "type":"Mesh", 75 | "material":"415B601E-B130-4152-9FBA-33A7E6B59476", 76 | "castShadow":true, 77 | "receiveShadow":true, 78 | "geometry":"3C994563-C5DA-4D55-820D-E01948F9AA1C" 79 | },{ 80 | "name":"restart", 81 | "uuid":"B7595896-149A-4985-ABE9-D4282F0E9A23", 82 | "matrix":[-1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1], 83 | "visible":true, 84 | "type":"Mesh", 85 | "material":"6CF285F3-D567-4695-8019-F97C0A4B5A9C", 86 | "castShadow":true, 87 | "receiveShadow":true, 88 | "geometry":"7FA3E1DB-BBED-44D3-ACBA-7FE2D6D58928" 89 | }], 90 | "matrix":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] 91 | }, 92 | "animations":[{ 93 | "name":"default", 94 | "fps":24, 95 | "tracks":[] 96 | }] 97 | } -------------------------------------------------------------------------------- /assets/models/ring.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata":{ 3 | "type":"Geometry", 4 | "materials":1, 5 | "generator":"io_three", 6 | "normals":91, 7 | "vertices":92, 8 | "faces":46, 9 | "version":3 10 | }, 11 | "materials":[{ 12 | "wireframe":false, 13 | "colorEmissive":[0.04,0.05,0.06], 14 | "depthWrite":true, 15 | "DbgColor":15658734, 16 | "blending":"NormalBlending", 17 | "shading":"lambert", 18 | "transparent":false, 19 | "colorDiffuse":[0.02,0.03,0.04], 20 | "depthTest":true, 21 | "visible":true, 22 | "DbgIndex":0, 23 | "opacity":1, 24 | "DbgName":"mat-background" 25 | }], 26 | "normals":[0.906919,0.421277,-0,0.977508,0.210761,-0,0.974914,0.22251,-0,0.89996,0.435926,-0,0.989227,-0.146214,-0,0.989196,-0.146519,-0,0.965575,-0.260079,-0,0.964873,-0.262642,-0,0.617908,0.786218,-0,0.780572,0.625019,-0,0.771355,0.636372,-0,0.608783,0.793329,-0,0.999817,0.017701,-0,0.999695,0.024323,-0,0.275216,0.961364,-0,0.445631,0.895199,-0,0.439436,0.898251,-0,0.271676,0.962371,-0,-0.149846,0.988678,-0,0.086612,0.996216,-0,0.086337,0.996246,-0,-0.145054,0.98941,-0,-0.612354,0.790551,-0,-0.411786,0.911252,-0,-0.402875,0.91522,-0,-0.605243,0.796014,-0,-0.719352,0.694601,-0,-0.708914,0.705283,-0,-0.706442,0.707755,-0,-0.720695,0.693228,-0,-0.604236,0.796777,-0,-0.68099,0.732261,-0,-0.684469,0.729026,-0,-0.610309,0.792138,-0,-0.383251,0.923612,-0,-0.498825,0.866695,-0,-0.504227,0.863552,-0,-0.386456,0.9223,-0,-0.457045,0.889431,-0,-0.3408,0.940123,-0,-0.462722,0.886471,-0,-0.833338,0.552751,-0,-0.65685,0.75399,-0,-0.667562,0.74453,-0,-0.843074,0.537736,-0,-0.990143,0.139958,-0,-0.942015,0.335551,-0,-0.946959,0.321268,-0,-0.992126,0.125034,-0,-0.964934,-0.262429,-0,-0.998535,-0.053591,-0,-0.997589,-0.069185,-0,-0.961058,-0.276254,-0,-0.745201,-0.666829,-0,-0.878964,-0.476852,-0,-0.875027,-0.484024,-0,-0.74514,-0.66689,-0,-0.398755,-0.91702,-0,-0.585284,-0.810785,-0,-0.590838,-0.806757,-0,-0.412793,-0.910794,-0,-0.049318,-0.998779,-0,-0.202673,-0.979217,-0,-0.219642,-0.975555,-0,-0.061525,-0.998077,-0,0.387829,-0.92172,-0,0.133946,-0.990966,-0,0.116123,-0.993225,-0,0.376293,-0.926481,-0,0.72927,-0.684194,-0,0.601825,-0.798608,-0,0.601947,-0.798517,-0,0.733085,-0.680105,-0,0.711173,-0.702994,-0,0.771844,-0.635792,-0,0.771111,-0.636677,-0,0.705313,-0.708884,-0,0.381909,-0.924161,-0,0.567309,-0.823481,-0,0.563707,-0.825953,-0,0.38319,-0.923643,-0,0.361034,-0.932524,-0,0.273507,-0.961852,-0,0.273934,-0.96173,-0,0.353252,-0.935514,-0,0.779534,-0.626331,-0,0.562365,-0.826868,-0,0.550707,-0.834681,-0,0.766839,-0.641804,-0,0.917142,-0.398541,-0,0.912534,-0.408948,-0], 27 | "vertices":[-49.1278,5.9901,-8.19676,-49.1278,-3.23673,-8.19676,-48.8282,-3.92736,-20.5838,-48.8282,5.29948,-20.5838,-43.2105,3.92764,-32.4147,-43.2105,-5.29919,-32.4147,-32.184,2.78468,-40.9277,-32.184,-6.44215,-40.9277,-18.7384,2.3275,-44.7869,-18.7384,-6.89934,-44.7869,-7.57594,-6.59455,-43.2456,-7.57594,2.63229,-43.2456,0.761053,3.39426,-36.7947,0.761053,-5.83258,-36.7947,9.61601,4.61282,-27.6203,9.61601,-4.61402,-27.6203,20.2215,-3.31942,-19.4946,20.2215,5.90742,-19.4946,32.0263,6.44064,-14.5686,32.0263,-2.78619,-14.5686,42.6689,5.90742,-9.0906,42.6689,-3.31942,-9.0906,49.3771,4.61342,1.37066,49.3771,-4.61342,1.37066,51.2282,-6.36321,15.3264,51.2282,2.86363,15.3264,47.5109,1.46384,28.7035,47.5109,-7.76299,28.7035,38.1583,1.46384,39.1111,38.1583,-7.76299,39.1111,26.2204,2.86363,44.4987,26.2204,-6.3632,44.4987,15.1521,-4.68584,45.1399,15.1521,4.54099,45.1399,5.25695,5.23177,40.9787,5.25695,-3.99506,40.9787,-3.66566,4.39127,31.3522,-3.66566,-4.83557,31.3522,-13.3944,3.3201,21.5639,-13.3944,-5.90674,21.5639,-26.4148,-5.75458,16.2221,-26.4148,3.47225,16.2221,-39.0371,4.38745,11.473,-39.0371,-4.83938,11.473,-46.1327,5.45847,2.88717,-46.1327,-3.76837,2.88717,-47.8591,5.85948,-2.51836,-47.8591,-3.36736,-2.51836,-49.5729,5.81517,-14.2009,-49.5729,-3.41167,-14.2009,-46.7541,4.61349,-26.8276,-46.7541,-4.61335,-26.8276,-38.3147,3.29901,-37.1723,-38.3147,-5.92782,-37.1723,-25.4486,2.44179,-43.5196,-25.4486,-6.78504,-43.5196,-12.599,2.40369,-44.7041,-12.599,-6.82314,-44.7041,-3.25921,2.97517,-40.5597,-3.25921,-6.25166,-40.5597,4.91081,3.92749,-32.3489,4.91081,-5.29934,-32.3489,14.7588,5.31716,-23.1539,14.7588,-3.90968,-23.1539,25.9839,6.30734,-16.6352,25.9839,-2.9195,-16.6352,37.7781,6.30734,-12.364,37.7781,-2.9195,-12.364,46.576,5.31731,-4.5714,46.576,-3.90953,-4.5714,50.9639,3.79558,8.18634,50.9639,-5.43125,8.18634,50.1004,2.01918,22.3219,50.1004,-7.20766,22.3219,43.5126,1.25845,34.3428,43.5126,-7.96838,34.3428,32.1576,2.01918,42.6244,32.1576,-7.20766,42.6244,20.5005,3.77748,45.1864,20.5005,-5.44936,45.1864,10.0969,5.05786,43.8929,10.0969,-4.16898,43.8929,0.660103,5.02286,36.6982,0.660103,-4.20397,36.6982,-8.19295,3.70201,25.9657,-8.19295,-5.52483,25.9657,-19.4187,3.24402,18.2737,-19.4187,-5.98282,18.2737,-33.3113,3.89125,14.3187,-33.3113,-5.33559,14.3187,-43.3812,4.92261,7.66818,-43.3812,-4.30422,7.66818], 28 | "faces":[35,50,3,2,51,0,0,1,2,3,35,1,0,46,47,0,4,5,6,7,35,52,4,5,53,0,8,9,10,11,35,2,3,48,49,0,2,1,12,13,35,54,6,7,55,0,14,15,16,17,35,5,4,50,51,0,10,9,0,3,35,56,8,9,57,0,18,19,20,21,35,7,6,52,53,0,16,15,8,11,35,58,11,10,59,0,22,23,24,25,35,9,8,54,55,0,20,19,14,17,35,60,12,13,61,0,26,27,28,29,35,10,11,56,57,0,24,23,18,21,35,62,14,15,63,0,30,31,32,33,35,13,12,58,59,0,28,27,22,25,35,64,17,16,65,0,34,35,36,37,35,15,14,60,61,0,32,31,26,29,35,66,18,19,67,0,38,39,39,40,35,16,17,62,63,0,36,35,30,33,35,68,20,21,69,0,41,42,43,44,35,19,18,64,65,0,39,39,34,37,35,70,22,23,71,0,45,46,47,48,35,21,20,66,67,0,43,42,38,40,35,72,25,24,73,0,49,50,51,52,35,23,22,68,69,0,47,46,41,44,35,74,26,27,75,0,53,54,55,56,35,24,25,70,71,0,51,50,45,48,35,76,28,29,77,0,57,58,59,60,35,27,26,72,73,0,55,54,49,52,35,78,30,31,79,0,61,62,63,64,35,29,28,74,75,0,59,58,53,56,35,80,33,32,81,0,65,66,67,68,35,31,30,76,77,0,63,62,57,60,35,82,34,35,83,0,69,70,71,72,35,32,33,78,79,0,67,66,61,64,35,84,36,37,85,0,73,74,75,76,35,35,34,80,81,0,71,70,65,68,35,86,38,39,87,0,77,78,79,80,35,37,36,82,83,0,75,74,69,72,35,88,41,40,89,0,81,82,83,84,35,39,38,84,85,0,79,78,73,76,35,90,42,43,91,0,85,86,87,88,35,40,41,86,87,0,83,82,77,80,35,46,44,45,47,0,6,89,90,7,35,43,42,88,89,0,87,86,81,84,35,45,44,90,91,0,90,89,85,88,35,48,0,1,49,0,12,5,4,13], 29 | "name":"ringGeometry.4" 30 | } -------------------------------------------------------------------------------- /assets/rawfiles/images/decals.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/decals.psd -------------------------------------------------------------------------------- /assets/rawfiles/images/enemy.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/enemy.psd -------------------------------------------------------------------------------- /assets/rawfiles/images/env.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/env.psd -------------------------------------------------------------------------------- /assets/rawfiles/images/face.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/face.psd -------------------------------------------------------------------------------- /assets/rawfiles/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/favicon.ico -------------------------------------------------------------------------------- /assets/rawfiles/images/font.kra: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/font.kra -------------------------------------------------------------------------------- /assets/rawfiles/images/font.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/font.psd -------------------------------------------------------------------------------- /assets/rawfiles/images/fx.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/fx.psd -------------------------------------------------------------------------------- /assets/rawfiles/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/icon-128.png -------------------------------------------------------------------------------- /assets/rawfiles/images/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/icon-256.png -------------------------------------------------------------------------------- /assets/rawfiles/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/icon.png -------------------------------------------------------------------------------- /assets/rawfiles/images/menu.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/menu.psd -------------------------------------------------------------------------------- /assets/rawfiles/images/sky.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/images/sky.psd -------------------------------------------------------------------------------- /assets/rawfiles/models/background.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/background.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/border.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/border.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/border_lighting.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/border_lighting.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/bullet.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/bullet.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/deco.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/deco.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/decos.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/decos.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/enemies.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/enemies.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/enemy0.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/enemy0.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/enemy1.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/enemy1.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/enemy2.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/enemy2.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/enemy3.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/enemy3.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/exportWaves.py: -------------------------------------------------------------------------------- 1 | # Exports waves.js file for Mozilla VR's A-Blast demo 2 | # - Each blender layer is a wave 3 | # - Sequences go in groups, alphabetical order 4 | # - Enemies are drawn with Bezier Curves: 5 | # + material name defines movement (single, pingpong, loop) 6 | # + custom property "type" defines enemy name 7 | 8 | import bpy 9 | 10 | 11 | def getOrderedListOfGroupsInLayer(layer): 12 | curves = [ob for ob in bpy.data.objects if ob.layers[layer] and ob.type=='CURVE'] 13 | groupset= set() 14 | for curve in curves: 15 | for group in curve.users_group: 16 | groupset.add(group.name) 17 | return sorted(list(groupset), key=int) 18 | 19 | def getCustomData(obj, dataname): 20 | if not dataname in obj.data: return '' 21 | data = obj.data[dataname] 22 | if data: 23 | return ' '+dataname+': '+str(data)+',\n' 24 | return '' 25 | 26 | out = 'var WAVES = [\n' 27 | waves = [] 28 | exportwaves = [0,1,2,3,4,5,6,7,8,9] 29 | debugmsg = '' 30 | for l in exportwaves: 31 | wave = ' {\n' 32 | wave += ' name: "WAVE '+str(l+1)+'",\n' 33 | wave += ' sequences: [\n'; 34 | groups = getOrderedListOfGroupsInLayer(l) 35 | if not groups: continue 36 | sequences = [] 37 | for group in groups: 38 | enemies = [] 39 | curves = sorted([ob.name for ob in bpy.data.groups[group].objects]) 40 | if not curves: continue 41 | seq = '' 42 | seq += ' {\n start: 0,\n random: 0,\n'; 43 | seq += ' subwave: "'+group+'",\n'; 44 | seq += ' enemies:[\n' 45 | for i in curves: 46 | curve = bpy.data.objects[i] 47 | points = [] 48 | for p in curve.data.splines[0].bezier_points: 49 | points.append('[{0:.3f},{1:.3f},{2:.3f}]'.format( -p.co.x, p.co.z, p.co.y )) 50 | 51 | enemyTimeOffset = getCustomData(curve, 'enemyTimeOffset') 52 | loopStart = getCustomData(curve, 'loopStart') 53 | type = curve.data['type'] 54 | if type.find(',') >= 0: 55 | if enemyTimeOffset == '': debugmsg += '// enemyTimeOffset not set in group ' + group + ', curve ' + curve.name + '\n' 56 | type = '["'+'","'.join(type.split(',')) + '"]' 57 | else: 58 | type = '"' + type + '"' 59 | enemies.append( 60 | ' {\n' 61 | ' type: '+type+',\n'+ 62 | ' points: ['+','.join(points)+'],\n'+ 63 | ' movement: "'+curve.material_slots[0].material.name.lower()+'",\n'+ 64 | enemyTimeOffset+ 65 | loopStart+ 66 | ' random: '+str(0)+',\n'+ 67 | ' }') 68 | seq += ',\n'.join(enemies) 69 | seq += '\n ]\n }' 70 | sequences.append(seq) 71 | wave += ',\n'.join(sequences) 72 | wave += '\n ]\n }' 73 | waves.append(wave) 74 | out += ',\n'.join(waves) 75 | out += '\n];' 76 | 77 | f = open(bpy.path.abspath("//")+'../../data/waves.js', 'w+') 78 | f.write(debugmsg + '\n') 79 | f.write(out) 80 | f.close() 81 | print(out) -------------------------------------------------------------------------------- /assets/rawfiles/models/gameover.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/gameover.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/gun.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/gun.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/gun_anim.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/gun_anim.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/logo.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/logo.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/platform.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/platform.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/player-bullet.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/player-bullet.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/restart.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/restart.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/testsphere.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/testsphere.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/waves.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/waves.blend -------------------------------------------------------------------------------- /assets/rawfiles/models/welldone.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/models/welldone.blend -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/.bitwig-project: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/.bitwig-project -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/enemy0shoot.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/enemy0shoot.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/explosion0.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/explosion0.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/explosion1.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/explosion1.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/explosion2.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/explosion2.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/explosion3.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/explosion3.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/hitbullet.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/hitbullet.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/hitwall.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/hitwall.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/hurt.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/hurt.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/playershoot.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/playershoot.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/sfx.bwproject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/rawfiles/sounds/sfx/sfx.bwproject -------------------------------------------------------------------------------- /assets/rawfiles/sounds/sfx/sounds.aup: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /assets/readme/a-blast-10s.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/readme/a-blast-10s.gif -------------------------------------------------------------------------------- /assets/readme/a-blast-1s.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/readme/a-blast-1s.gif -------------------------------------------------------------------------------- /assets/readme/a-blast-22s.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/readme/a-blast-22s.gif -------------------------------------------------------------------------------- /assets/readme/a-blast-3s.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/readme/a-blast-3s.gif -------------------------------------------------------------------------------- /assets/readme/gun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/readme/gun.png -------------------------------------------------------------------------------- /assets/readme/mainmenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/readme/mainmenu.png -------------------------------------------------------------------------------- /assets/readme/mainmenu2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/readme/mainmenu2.png -------------------------------------------------------------------------------- /assets/sounds/enemy0shoot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/enemy0shoot.ogg -------------------------------------------------------------------------------- /assets/sounds/enemy1shoot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/enemy1shoot.ogg -------------------------------------------------------------------------------- /assets/sounds/enemy2shoot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/enemy2shoot.ogg -------------------------------------------------------------------------------- /assets/sounds/enemy3shoot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/enemy3shoot.ogg -------------------------------------------------------------------------------- /assets/sounds/explosion0.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/explosion0.ogg -------------------------------------------------------------------------------- /assets/sounds/explosion1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/explosion1.ogg -------------------------------------------------------------------------------- /assets/sounds/explosion2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/explosion2.ogg -------------------------------------------------------------------------------- /assets/sounds/explosion3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/explosion3.ogg -------------------------------------------------------------------------------- /assets/sounds/gun.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/gun.ogg -------------------------------------------------------------------------------- /assets/sounds/hitbullet.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/hitbullet.ogg -------------------------------------------------------------------------------- /assets/sounds/hitwall.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/hitwall.ogg -------------------------------------------------------------------------------- /assets/sounds/hurt.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/hurt.ogg -------------------------------------------------------------------------------- /assets/sounds/intro.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/intro.ogg -------------------------------------------------------------------------------- /assets/sounds/music.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/assets/sounds/music.ogg -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | #ablast-ui { 2 | font-family: Helvetica, Arial, sans-serif; 3 | position: absolute; 4 | left: 20px; 5 | bottom: 20px; 6 | line-height: 1.2em; 7 | background-color: rgba(10,10,10,0.8); 8 | border-radius: 6px; 9 | padding: 1.7em 2em; 10 | display: none; 11 | } 12 | 13 | #ablast-ui .help { 14 | color: #ccc; 15 | font-size: 14px; 16 | text-shadow: 0px 2px 1px #000; 17 | } 18 | 19 | #ablast-ui .help h1 { 20 | font-size: 22px; 21 | font-weight: 100; 22 | color: #e42b5a; 23 | margin-top: 0; 24 | } 25 | 26 | #ablast-ui .button { 27 | background-color: #ef2d5e; 28 | color: #fff; 29 | cursor: pointer; 30 | font-size: 12px; 31 | width: 130px; 32 | text-align: center; 33 | max-width: 115px; 34 | padding: 6px; 35 | } 36 | 37 | #ablast-ui .button:hover { 38 | background-color: #f43b6a; 39 | } 40 | 41 | #ablast-ui .form { 42 | display: flex; 43 | font-size: 14px; 44 | align-content: center; 45 | } 46 | 47 | #ablast-ui .hiscore input { 48 | width: 200px; 49 | color: #333; 50 | text-align: center; 51 | } 52 | 53 | #ablast-ui .hide { 54 | display: none !important; 55 | } 56 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aframevr/a-blast/1984d1adb7845399fabd8672909ce1c74ccd5cf7/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A-Blast 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 84 | 85 | 91 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 124 | 125 | 126 | 130 | 131 | 132 | 139 | 140 | 141 | 142 | 143 | 144 | 149 | 150 | 151 | 152 | 153 | 154 | 163 | 164 | 165 | 166 | 169 | 171 | 172 | 176 | 177 | 178 | 183 | 184 | 185 | 186 |
187 |
188 |
189 |

Good job!

190 |

Please enter your name to join the Hall of Fame!

191 |
192 |
193 | 194 |
SAVE!
195 |
196 |
197 |
198 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "lang": "en", 3 | "dir": "ltr", 4 | "name": "A-Blast", 5 | "short_name": "A-Blast", 6 | "description": "Save the world from the cutest creatures in the Universe!", 7 | "start_url": "./", 8 | "scope": "./", 9 | "display": "fullscreen", 10 | "theme_color": "#3d4d55", 11 | "background_color": "#3d4d55", 12 | "icons": [ 13 | { 14 | "src": "assets/images/icon.png", 15 | "sizes": "1024x1024", 16 | "type": "image/png" 17 | }, 18 | { 19 | "src": "assets/images/icon.png", 20 | "sizes": "256x256", 21 | "type": "image/png" 22 | }, 23 | { 24 | "src": "assets/images/icon.png", 25 | "sizes": "128x128", 26 | "type": "image/png" 27 | } 28 | ], 29 | "screenshots": [ 30 | { 31 | "src": "assets/readme/mainmenu2.png", 32 | "sizes": "1166x707", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "assets/readme/a-blast-3s.gif", 37 | "sizes": "395x330", 38 | "type": "image/gif" 39 | } 40 | ], 41 | "about_url": "https://blog.mozvr.com/a-blast/", 42 | "vr_default_display": "HTC Vive", 43 | "vr_available_displays": [ 44 | "HTC Vive", 45 | "Oculus Rift" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a-blast", 3 | "version": "0.1.0", 4 | "description": "A WebVR FPS mini-game demo using A-Frame by Mozilla VR.", 5 | "scripts": { 6 | "build": "cross-env NODE_ENV=production webpack", 7 | "start": "webpack-dev-server --host 0.0.0.0 --progress --colors --hot -d --inline", 8 | "lint": "semistandard -v | snazzy" 9 | }, 10 | "repository": "aframevr/a-blast", 11 | "license": "MIT", 12 | "semistandard": { 13 | "ignore": [ 14 | "**/vendor/**" 15 | ] 16 | }, 17 | "devDependencies": { 18 | "cross-env": "^3.0.0", 19 | "semistandard": "^9.0.0", 20 | "snazzy": "^4.0.1", 21 | "webpack": "^1.13.2", 22 | "webpack-dev-server": "^1.16.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/a-asset-image.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerElement('a-asset-image', { 2 | prototype: Object.create(AFRAME.ANode.prototype, { 3 | createdCallback: { 4 | value: function () { 5 | this.isAssetItem = true; 6 | } 7 | }, 8 | 9 | attachedCallback: { 10 | value: function () { 11 | var src = this.getAttribute('src'); 12 | var textureLoader = new THREE.ImageLoader(); 13 | textureLoader.load(src, this.onImageLoaded.bind(this)); 14 | } 15 | }, 16 | 17 | onImageLoaded: { 18 | value : function () { 19 | AFRAME.ANode.prototype.load.call(this); 20 | } 21 | } 22 | }) 23 | }); 24 | -------------------------------------------------------------------------------- /src/bullets/enemy-fast.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | ABLAST.registerBullet( 3 | // name 4 | 'enemy-fast', 5 | // data 6 | { 7 | components: { 8 | bullet: { 9 | name: 'enemy-fast', 10 | maxSpeed: 0.1, 11 | initialSpeed: 0.1, 12 | acceleration: 0.1, 13 | destroyable: true, 14 | color: '#FF7F00' 15 | }, 16 | 'collision-helper': { 17 | debug: false, 18 | radius: 0.1 19 | }, 20 | 'json-model': { 21 | src: '#enemyBullet' 22 | } 23 | }, 24 | poolSize: 10 25 | }, 26 | // implementation 27 | { 28 | init: function () { 29 | var el = this.el; 30 | var color = this.bullet.components.bullet.color; 31 | el.setAttribute('material', 'color', color); 32 | el.setAttribute('scale', {x: 0.09, y: 0.09, z: 0.09}); 33 | this.trail = null; 34 | this.glow = null; 35 | var self = this; 36 | el.addEventListener('model-loaded', function(event) { 37 | // @todo Do it outside 38 | event.detail.model.children[0].material.color.setStyle(color); 39 | self.trail = self.el.getObject3D('mesh').getObjectByName('trail'); 40 | self.trail.scale.setY(0.001); 41 | self.glow = self.el.getObject3D('mesh').getObjectByName('glow'); 42 | }); 43 | }, 44 | reset: function () { 45 | var el = this.el; 46 | el.setAttribute('scale', {x: 0.09, y: 0.09, z: 0.09}); 47 | if (this.trail) { 48 | this.trail.scale.setY(0.001); 49 | } 50 | }, 51 | tick: function (time, delta) { 52 | //stretch trail 53 | if (this.trail && this.trail.scale.y < 1) { 54 | var trailScale = this.trail.scale.y + delta/1000; 55 | if (trailScale > 1) { trailScale = 1; } 56 | this.trail.scale.setY(trailScale); 57 | } 58 | if (this.glow) { 59 | var sc = 1 + Math.sin(time / 10.0) * 0.1; 60 | this.glow.scale.set(sc, sc, sc); 61 | } 62 | }, 63 | onHit: function (type) { 64 | } 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /src/bullets/enemy-fat.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | ABLAST.registerBullet( 3 | // name 4 | 'enemy-fat', 5 | // data 6 | { 7 | components: { 8 | bullet: { 9 | name: 'enemy-fat', 10 | maxSpeed: 0.3, 11 | initialSpeed: 0.1, 12 | acceleration: 0.04, 13 | destroyable: true, 14 | color: '#8762FF' 15 | }, 16 | 'collision-helper': { 17 | debug: false, 18 | radius: 0.5 19 | }, 20 | 'json-model': { 21 | src: '#enemyBullet' 22 | } 23 | }, 24 | poolSize: 10 25 | }, 26 | // implementation 27 | { 28 | init: function () { 29 | var el = this.el; 30 | var color = this.bullet.components.bullet.color; 31 | el.setAttribute('material', 'color', color); 32 | el.setAttribute('scale', {x: 0.5, y: 0.5, z: 0.5}); 33 | this.trail = null; 34 | this.glow = null; 35 | var self = this; 36 | el.addEventListener('model-loaded', function(event) { 37 | // @todo Do it outside 38 | event.detail.model.children[0].material.color.setStyle(color); 39 | self.trail = self.el.getObject3D('mesh').getObjectByName('trail'); 40 | self.trail.scale.setY(0.001); 41 | self.glow = self.el.getObject3D('mesh').getObjectByName('glow'); 42 | }); 43 | }, 44 | reset: function () { 45 | var el = this.el; 46 | el.setAttribute('scale', {x: 0.5, y: 0.5, z: 0.5}); 47 | if (this.trail) { 48 | this.trail.scale.setY(0.001); 49 | } 50 | }, 51 | tick: function (time, delta) { 52 | //stretch trail 53 | if (this.trail && this.trail.scale.y < 0.1) { 54 | var trailScale = this.trail.scale.y + delta/1000; 55 | if (trailScale > 0.1) { trailScale = 0.1; } 56 | this.trail.scale.setY(trailScale); 57 | } 58 | if (this.glow) { 59 | var sc = 1 + Math.abs(Math.sin(time / 80.0) * 0.4); 60 | this.glow.scale.set(sc, sc, sc); 61 | } 62 | }, 63 | onHit: function (type) { 64 | } 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /src/bullets/enemy-medium.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | ABLAST.registerBullet( 3 | // name 4 | 'enemy-medium', 5 | // data 6 | { 7 | components: { 8 | bullet: { 9 | name: 'enemy-medium', 10 | maxSpeed: 0.5, 11 | initialSpeed: 0.1, 12 | acceleration: 0.06, 13 | destroyable: true, 14 | color: '#FF5533' 15 | }, 16 | 'collision-helper': { 17 | debug: false, 18 | radius: 0.16 19 | }, 20 | 'json-model': { 21 | src: 'url(assets/models/enemy-bullet.json)' 22 | } 23 | }, 24 | poolSize: 10 25 | }, 26 | // implementation 27 | { 28 | init: function () { 29 | var el = this.el; 30 | var color = this.bullet.components.bullet.color; 31 | el.setAttribute('material', 'color', color); 32 | el.setAttribute('scale', {x: 0.16, y: 0.16, z: 0.16}); 33 | this.trail = null; 34 | this.glow = null; 35 | var self = this; 36 | el.addEventListener('model-loaded', function(event) { 37 | // @todo Do it outside 38 | event.detail.model.children[0].material.color.setStyle(color); 39 | self.trail = self.el.getObject3D('mesh').getObjectByName('trail'); 40 | self.trail.scale.setY(0.001); 41 | self.glow = self.el.getObject3D('mesh').getObjectByName('glow'); 42 | }); 43 | }, 44 | reset: function () { 45 | var el = this.el; 46 | el.setAttribute('scale', {x: 0.13, y: 0.13, z: 0.13}); 47 | if (this.trail) { 48 | this.trail.scale.setY(0.001); 49 | } 50 | }, 51 | tick: function (time, delta) { 52 | //stretch trail 53 | if (this.trail && this.trail.scale.y < 0.3) { 54 | var trailScale = this.trail.scale.y + delta/1000; 55 | if (trailScale > 0.3) { trailScale = 0.3; } 56 | this.trail.scale.setY(trailScale); 57 | } 58 | if (this.glow) { 59 | var sc = 1 + Math.sin(time / 20.0) * 0.1; 60 | this.glow.scale.set(sc, sc, sc); 61 | } 62 | }, 63 | onHit: function (type) { 64 | } 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /src/bullets/enemy-slow.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | ABLAST.registerBullet( 3 | // name 4 | 'enemy-slow', 5 | // data 6 | { 7 | components: { 8 | bullet: { 9 | name: 'enemy-slow', 10 | maxSpeed: 0.5, 11 | initialSpeed: 0.1, 12 | acceleration: 0.03, 13 | destroyable: true, 14 | color: '#FFB911' 15 | }, 16 | 'collision-helper': { 17 | debug: false, 18 | radius: 0.13 19 | }, 20 | 'json-model': { 21 | src: '#enemyBullet' 22 | } 23 | }, 24 | poolSize: 10 25 | }, 26 | // implementation 27 | { 28 | init: function () { 29 | var el = this.el; 30 | var color = this.bullet.components.bullet.color; 31 | el.setAttribute('material', 'color', color); 32 | el.setAttribute('scale', {x: 0.13, y: 0.13, z: 0.13}); 33 | this.trail = null; 34 | this.glow = null; 35 | var self = this; 36 | el.addEventListener('model-loaded', function(event) { 37 | // @todo Do it outside 38 | event.detail.model.children[0].material.color.setStyle(color); 39 | self.trail = self.el.getObject3D('mesh').getObjectByName('trail'); 40 | self.trail.scale.setY(0.001); 41 | self.glow = self.el.getObject3D('mesh').getObjectByName('glow'); 42 | }); 43 | }, 44 | reset: function () { 45 | var el = this.el; 46 | el.setAttribute('scale', {x: 0.13, y: 0.13, z: 0.13}); 47 | if (this.trail) { 48 | this.trail.scale.setY(0.001); 49 | } 50 | }, 51 | tick: function (time, delta) { 52 | //stretch trail 53 | if (this.trail && this.trail.scale.y < 0.3) { 54 | var trailScale = this.trail.scale.y + delta/1000; 55 | if (trailScale > 0.3) { trailScale = 0.3; } 56 | this.trail.scale.setY(trailScale); 57 | } 58 | if (this.glow) { 59 | var sc = 1 + Math.sin(time / 20.0) * 0.1; 60 | this.glow.scale.set(sc, sc, sc); 61 | } 62 | }, 63 | onHit: function (type) { 64 | } 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /src/bullets/player.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | ABLAST.registerBullet( 3 | // name 4 | 'default', 5 | // data 6 | { 7 | components: { 8 | bullet: { 9 | name: 'default', 10 | maxSpeed: 1, 11 | initialSpeed: 0.1, 12 | acceleration: 0.4, 13 | color: '#24CAFF' 14 | }, 15 | 'collision-helper': { 16 | debug: false, 17 | radius: 0.2 18 | }, 19 | 'json-model': { 20 | src: '#playerBullet' 21 | } 22 | }, 23 | poolSize: 10 24 | }, 25 | // implementation 26 | { 27 | init: function () { 28 | var el = this.el; 29 | var color = this.bullet.components.bullet.color; 30 | el.setAttribute('material', 'color', color); 31 | el.setAttribute('scale', {x: 0.2, y: 0.2, z: 0.2}); 32 | this.trail = null; 33 | var self = this; 34 | el.addEventListener('model-loaded', function(event) { 35 | // @todo Do it outside 36 | //event.detail.model.children[0].material.color.setRGB(1,0,0); 37 | self.trail = self.el.getObject3D('mesh').getObjectByName('trail'); 38 | self.trail.scale.setY(0.001); 39 | }); 40 | }, 41 | reset: function () { 42 | var el = this.el; 43 | el.setAttribute('scale', {x: 0.2, y: 0.2, z: 0.2}); 44 | if (this.trail) { 45 | this.trail.scale.setY(0.001); 46 | } 47 | }, 48 | tick: function (time, delta) { 49 | //stretch trail 50 | if (this.trail && this.trail.scale.y < 1) { 51 | var trailScale; 52 | if (this.trail.scale.y < 0.005) { 53 | trailScale = this.trail.scale.y + 0.001; 54 | } 55 | else { 56 | trailScale = this.trail.scale.y + delta/50; 57 | } 58 | if (trailScale > 1) { trailScale = 1; } 59 | this.trail.scale.setY(trailScale); 60 | } 61 | }, 62 | onHit: function (type) { 63 | this.el.setAttribute('material', 'color', '#FFF'); 64 | } 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /src/components/animate-message.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('animate-message', { 2 | init: function () { 3 | var self = this; 4 | this.startMsg = null; 5 | this.el.addEventListener('model-loaded', function(event) { 6 | self.startMsg = self.el.getObject3D('mesh').getObjectByName('start'); 7 | }); 8 | }, 9 | tick: function (time, delta) { 10 | if (this.startMsg) { 11 | this.startMsg.rotation.z = -Math.PI + Math.abs(Math.sin(time / 200) * 0.03); 12 | } 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /src/components/bullet.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME ABLAST THREE */ 2 | AFRAME.registerComponent('bullet', { 3 | schema: { 4 | name: { default: '' }, 5 | direction: { type: 'vec3' }, 6 | maxSpeed: { default: 5.0 }, 7 | initialSpeed: { default: 5.0 }, 8 | position: { type: 'vec3' }, 9 | acceleration: { default: 0.5 }, 10 | destroyable: { default: false }, 11 | owner: {default: 'player', oneOf: ['enemy', 'player']}, 12 | color: {default: '#fff'} 13 | }, 14 | 15 | init: function () { 16 | this.startEnemy = document.getElementById('start_enemy'); 17 | this.backgroundEl = document.getElementById('border'); 18 | this.bullet = ABLAST.BULLETS[this.data.name]; 19 | this.bullet.definition.init.call(this); 20 | this.hit = false; 21 | this.direction = new THREE.Vector3(); 22 | this.temps = { 23 | direction: new THREE.Vector3(), 24 | position: new THREE.Vector3() 25 | } 26 | }, 27 | 28 | update: function (oldData) { 29 | var data = this.data; 30 | this.owner = this.data.owner; 31 | this.direction.set(data.direction.x, data.direction.y, data.direction.z); 32 | this.currentAcceleration = data.acceleration; 33 | this.speed = data.initialSpeed; 34 | this.startPosition = data.position; 35 | }, 36 | 37 | play: function () { 38 | this.initTime = null; 39 | }, 40 | 41 | hitObject: function (type, data) { 42 | this.bullet.definition.onHit.call(this); 43 | this.hit = true; 44 | if (this.data.owner === 'enemy') { 45 | this.el.emit('player-hit'); 46 | document.getElementById('hurtSound').components.sound.playSound(); 47 | } 48 | else { 49 | if (type === 'bullet') { 50 | // data is the bullet entity collided with 51 | data.components.bullet.resetBullet(); 52 | this.el.sceneEl.systems.explosion.createExplosion(type, data.object3D.position, data.getAttribute('bullet').color, 1, this.direction); 53 | ABLAST.currentScore.validShoot++; 54 | } 55 | else if (type === 'background') { 56 | this.el.sceneEl.systems.decals.addDecal(data.point, data.face.normal); 57 | var posOffset = data.point.clone().sub(this.direction.clone().multiplyScalar(0.2)); 58 | this.el.sceneEl.systems.explosion.createExplosion(type, posOffset, '#fff', 1, this.direction); 59 | } 60 | else if (type === 'enemy') { 61 | var enemy = data.getAttribute('enemy'); 62 | if (data.components['enemy'].health <= 0) { 63 | this.el.sceneEl.systems.explosion.createExplosion('enemy', data.object3D.position, enemy.color, enemy.scale, this.direction, enemy.name); 64 | } 65 | else { 66 | this.el.sceneEl.systems.explosion.createExplosion('bullet', this.el.object3D.position, enemy.color, enemy.scale, this.direction); 67 | } 68 | ABLAST.currentScore.validShoot++; 69 | } 70 | } 71 | this.resetBullet(); 72 | }, 73 | 74 | resetBullet: function () { 75 | this.hit = false; 76 | this.bullet.definition.reset.call(this); 77 | this.initTime = null; 78 | 79 | this.direction.set(this.data.direction.x, this.data.direction.y, this.data.direction.z); 80 | 81 | this.currentAcceleration = this.data.acceleration; 82 | this.speed = this.data.initialSpeed; 83 | this.startPosition = this.data.position; 84 | 85 | this.system.returnBullet(this.data.name, this.el); 86 | }, 87 | 88 | tick: (function () { 89 | //var position = new THREE.Vector3(); 90 | //var direction = new THREE.Vector3(); 91 | return function tick (time, delta) { 92 | 93 | if (!this.initTime) {this.initTime = time;} 94 | 95 | this.bullet.definition.tick.call(this, time, delta); 96 | 97 | // Align the bullet to its direction 98 | this.el.object3D.lookAt(this.direction.clone().multiplyScalar(1000)); 99 | 100 | // Update acceleration based on the friction 101 | this.temps.position.copy(this.el.getAttribute('position')); 102 | 103 | // Update speed based on acceleration 104 | this.speed = this.currentAcceleration * .1 * delta; 105 | if (this.speed > this.data.maxSpeed) { this.speed = this.data.maxSpeed; } 106 | 107 | // Set new position 108 | this.temps.direction.copy(this.direction); 109 | var newBulletPosition = this.temps.position.add(this.temps.direction.multiplyScalar(this.speed)); 110 | this.el.setAttribute('position', newBulletPosition); 111 | 112 | // Check if the bullet is lost in the sky 113 | if (this.temps.position.length() >= 50) { 114 | this.resetBullet(); 115 | return; 116 | } 117 | 118 | var collisionHelper = this.el.getAttribute('collision-helper'); 119 | if (!collisionHelper) { return; } 120 | 121 | var bulletRadius = collisionHelper.radius; 122 | 123 | // Detect collision depending on the owner 124 | if (this.data.owner === 'player') { 125 | // megahack 126 | 127 | // Detect collision against enemies 128 | if (this.data.owner === 'player') { 129 | // Detect collision with the start game enemy 130 | var state = this.el.sceneEl.getAttribute('gamestate').state; 131 | if (state === 'STATE_MAIN_MENU') { 132 | var enemy = this.startEnemy; 133 | var helper = enemy.getAttribute('collision-helper'); 134 | var radius = helper.radius; 135 | if (newBulletPosition.distanceTo(enemy.object3D.position) < radius + bulletRadius) { 136 | this.el.sceneEl.systems.explosion.createExplosion('enemy', this.el.getAttribute('position'), '#ffb911', 0.5, this.direction, 'enemy_start'); 137 | enemy.emit('hit'); 138 | document.getElementById('introMusic').components.sound.pauseSound(); 139 | document.getElementById('mainThemeMusic').components.sound.playSound(); 140 | return; 141 | } 142 | } else if (state === 'STATE_GAME_WIN' || state === 'STATE_GAME_OVER') { 143 | var enemy = document.getElementById('reset'); 144 | var helper = enemy.getAttribute('collision-helper'); 145 | var radius = helper.radius; 146 | if (newBulletPosition.distanceTo(enemy.object3D.position) < radius * 2 + bulletRadius * 2) { 147 | this.el.sceneEl.systems.explosion.createExplosion('enemy', this.el.getAttribute('position'), '#f00', 0.5, this.direction, 'enemy_start'); 148 | this.el.sceneEl.emit('reset'); 149 | return; 150 | } 151 | } else { 152 | // Detect collisions with all the active enemies 153 | var enemies = this.el.sceneEl.systems.enemy.activeEnemies; 154 | for (var i = 0; i < enemies.length; i++) { 155 | var enemy = enemies[i]; 156 | var helper = enemy.getAttribute('collision-helper'); 157 | if (!helper) continue; 158 | var radius = helper.radius; 159 | if (newBulletPosition.distanceTo(enemy.object3D.position) < radius + bulletRadius) { 160 | enemy.emit('hit'); 161 | this.hitObject('enemy', enemy); 162 | return; 163 | } 164 | } 165 | } 166 | 167 | var bullets = this.system.activeBullets; 168 | for (var i = 0; i < bullets.length; i++) { 169 | var bullet = bullets[i]; 170 | var data = bullet.components['bullet'].data; 171 | if (!data || data.owner === 'player' || !data.destroyable) { continue; } 172 | 173 | var colhelper = bullet.components['collision-helper']; 174 | if (!colhelper) continue; 175 | var enemyBulletRadius = colhelper.data.radius; 176 | if (newBulletPosition.distanceTo(bullet.getAttribute('position')) < enemyBulletRadius + bulletRadius) { 177 | this.hitObject('bullet', bullet); 178 | return; 179 | } 180 | } 181 | } 182 | } else { 183 | // @hack Any better way to get the head position ? 184 | var head = this.el.sceneEl.camera.el.components['look-controls'].dolly.position; 185 | if (newBulletPosition.distanceTo(head) < 0.10 + bulletRadius) { 186 | this.hitObject('player'); 187 | return; 188 | } 189 | } 190 | 191 | // Detect collission aginst the background 192 | var ray = new THREE.Raycaster(this.temps.position, this.temps.direction.clone().normalize()); 193 | var background = this.backgroundEl.getObject3D('mesh'); 194 | if (background) { 195 | var collisionResults = ray.intersectObjects(background.children, true); 196 | var self = this; 197 | collisionResults.forEach(function (collision) { 198 | if (collision.distance < self.temps.position.length()) { 199 | if (!collision.object.el) { return; } 200 | self.hitObject('background', collision); 201 | return; 202 | } 203 | }); 204 | } 205 | }; 206 | })() 207 | }); 208 | -------------------------------------------------------------------------------- /src/components/collision-helper.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME THREE */ 2 | AFRAME.registerComponent('collision-helper', { 3 | schema: { 4 | type: {default: 'sphere', oneOf: ['sphere', 'box']}, 5 | radius: {default: 1, if: {type: ['sphere']}}, 6 | debug: {default: false}, 7 | color: {type: 'color', default: 0x888888} 8 | }, 9 | 10 | init: function () { 11 | var data = this.data; 12 | 13 | this.geometry = new THREE.IcosahedronGeometry(1, 1); 14 | this.material = new THREE.MeshBasicMaterial({color: data.color, wireframe: true}); 15 | this.helperMesh = null; 16 | 17 | if (data.debug) { 18 | this.createHelperMesh(); 19 | } 20 | }, 21 | 22 | createHelperMesh: function () { 23 | var radius = this.data.radius; 24 | this.helperMesh = new THREE.Mesh(this.geometry, this.material); 25 | this.helperMesh.visible = true; 26 | this.helperMesh.scale.set(radius, radius, radius); 27 | this.el.setObject3D('collision-helper-mesh', this.helperMesh); 28 | }, 29 | 30 | update: function (oldData) { 31 | var data = this.data; 32 | if (!data.debug) { return; } 33 | 34 | if (!this.helperMesh) { 35 | this.createHelperMesh(); 36 | } else { 37 | this.material.color.set(data.color); 38 | this.helperMesh.scale.set(data.radius, data.radius, data.radius); 39 | this.helperMesh.visible = data.debug; 40 | } 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /src/components/countdown.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('countdown', { 2 | schema: { 3 | start: {default: '01:00'}, 4 | value: {default: '00:00'}, 5 | autostart: {default: false} 6 | }, 7 | 8 | init: function () { 9 | this.timeinterval = null; 10 | if (this.data.autostart) { 11 | this.restart(); 12 | } 13 | 14 | var self = this; 15 | this.el.sceneEl.addEventListener('gamestate-changed', function (evt) { 16 | if ('state' in evt.detail.diff) { 17 | switch (evt.detail.state.state) { 18 | case 'STATE_PLAYING': 19 | self.restart(); 20 | break; 21 | case 'STATE_GAME_OVER': 22 | case 'STATE_GAME_WIN': 23 | case 'STATE_MAIN_MENU': 24 | self.stop(); 25 | } 26 | } 27 | }); 28 | }, 29 | 30 | initializeClock: function (endtime) { 31 | var self = this; 32 | 33 | this.el.sceneEl.emit('countdown-start', endtime); 34 | function updateTimer() { 35 | var total = Date.parse(endtime) - Date.parse(new Date()); 36 | var seconds = Math.floor( (total/1000) % 60 ); 37 | var minutes = Math.floor( (total/1000/60) % 60 ); 38 | var t = { 39 | 'total': total, 40 | 'minutes': minutes, 41 | 'seconds': seconds 42 | }; 43 | self.el.sceneEl.emit('countdown-update', t); 44 | if (t.total <= 0) { 45 | clearInterval(self.timeinterval); 46 | self.el.sceneEl.emit('countdown-end'); 47 | } 48 | } 49 | 50 | this.timeinterval = setInterval(updateTimer, 1000); 51 | updateTimer(); 52 | }, 53 | 54 | stop: function () { 55 | clearInterval(this.timeinterval); 56 | this.el.sceneEl.emit('countdown-update', { 57 | 'total': 0, 58 | 'minutes': 0, 59 | 'seconds': 0 60 | }); 61 | }, 62 | 63 | restart: function () { 64 | this.stop(); 65 | 66 | var values = this.data.start.split(':').map(function(value) { return parseInt(value); }); 67 | var deadline = new Date(Date.parse(new Date()) + (values[0] * 60 + values[1]) * 1000); 68 | this.initializeClock(deadline); 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /src/components/curve-movement.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME, THREE */ 2 | 3 | THREE.Spline = function ( points ) { 4 | 5 | this.points = points; 6 | 7 | var c = [], v3 = { x: 0, y: 0, z: 0 }, 8 | point, intPoint, weight, w2, w3, 9 | pa, pb, pc, pd; 10 | 11 | this.initFromArray = function ( a ) { 12 | 13 | this.points = []; 14 | 15 | for ( var i = 0; i < a.length; i ++ ) { 16 | 17 | this.points[ i ] = { x: a[ i ][ 0 ], y: a[ i ][ 1 ], z: a[ i ][ 2 ] }; 18 | 19 | } 20 | 21 | }; 22 | 23 | this.getPoint = function ( k ) { 24 | 25 | point = ( this.points.length - 1 ) * k; 26 | intPoint = Math.floor( point ); 27 | weight = point - intPoint; 28 | 29 | c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1; 30 | c[ 1 ] = intPoint; 31 | c[ 2 ] = intPoint > this.points.length - 2 ? this.points.length - 1 : intPoint + 1; 32 | c[ 3 ] = intPoint > this.points.length - 3 ? this.points.length - 1 : intPoint + 2; 33 | 34 | pa = this.points[ c[ 0 ] ]; 35 | pb = this.points[ c[ 1 ] ]; 36 | pc = this.points[ c[ 2 ] ]; 37 | pd = this.points[ c[ 3 ] ]; 38 | 39 | w2 = weight * weight; 40 | w3 = weight * w2; 41 | 42 | v3.x = interpolate( pa.x, pb.x, pc.x, pd.x, weight, w2, w3 ); 43 | v3.y = interpolate( pa.y, pb.y, pc.y, pd.y, weight, w2, w3 ); 44 | v3.z = interpolate( pa.z, pb.z, pc.z, pd.z, weight, w2, w3 ); 45 | 46 | return v3; 47 | 48 | }; 49 | 50 | this.getControlPointsArray = function () { 51 | 52 | var i, p, l = this.points.length, 53 | coords = []; 54 | 55 | for ( i = 0; i < l; i ++ ) { 56 | 57 | p = this.points[ i ]; 58 | coords[ i ] = [ p.x, p.y, p.z ]; 59 | 60 | } 61 | 62 | return coords; 63 | 64 | }; 65 | 66 | // approximate length by summing linear segments 67 | 68 | this.getLength = function ( nSubDivisions ) { 69 | 70 | var i, index, nSamples, position, 71 | point = 0, intPoint = 0, oldIntPoint = 0, 72 | oldPosition = new THREE.Vector3(), 73 | tmpVec = new THREE.Vector3(), 74 | chunkLengths = [], 75 | totalLength = 0; 76 | 77 | // first point has 0 length 78 | 79 | chunkLengths[ 0 ] = 0; 80 | 81 | if ( ! nSubDivisions ) nSubDivisions = 100; 82 | 83 | nSamples = this.points.length * nSubDivisions; 84 | 85 | oldPosition.copy( this.points[ 0 ] ); 86 | 87 | for ( i = 1; i < nSamples; i ++ ) { 88 | 89 | index = i / nSamples; 90 | 91 | position = this.getPoint( index ); 92 | tmpVec.copy( position ); 93 | 94 | totalLength += tmpVec.distanceTo( oldPosition ); 95 | 96 | oldPosition.copy( position ); 97 | 98 | point = ( this.points.length - 1 ) * index; 99 | intPoint = Math.floor( point ); 100 | 101 | if ( intPoint !== oldIntPoint ) { 102 | 103 | chunkLengths[ intPoint ] = totalLength; 104 | oldIntPoint = intPoint; 105 | 106 | } 107 | 108 | } 109 | 110 | // last point ends with total length 111 | 112 | chunkLengths[ chunkLengths.length ] = totalLength; 113 | 114 | return { chunks: chunkLengths, total: totalLength }; 115 | 116 | }; 117 | 118 | this.reparametrizeByArcLength = function ( samplingCoef ) { 119 | 120 | var i, j, 121 | index, indexCurrent, indexNext, 122 | realDistance, 123 | sampling, position, 124 | newpoints = [], 125 | tmpVec = new Vector3(), 126 | sl = this.getLength(); 127 | 128 | newpoints.push( tmpVec.copy( this.points[ 0 ] ).clone() ); 129 | 130 | for ( i = 1; i < this.points.length; i ++ ) { 131 | 132 | //tmpVec.copy( this.points[ i - 1 ] ); 133 | //linearDistance = tmpVec.distanceTo( this.points[ i ] ); 134 | 135 | realDistance = sl.chunks[ i ] - sl.chunks[ i - 1 ]; 136 | 137 | sampling = Math.ceil( samplingCoef * realDistance / sl.total ); 138 | 139 | indexCurrent = ( i - 1 ) / ( this.points.length - 1 ); 140 | indexNext = i / ( this.points.length - 1 ); 141 | 142 | for ( j = 1; j < sampling - 1; j ++ ) { 143 | 144 | index = indexCurrent + j * ( 1 / sampling ) * ( indexNext - indexCurrent ); 145 | 146 | position = this.getPoint( index ); 147 | newpoints.push( tmpVec.copy( position ).clone() ); 148 | 149 | } 150 | 151 | newpoints.push( tmpVec.copy( this.points[ i ] ).clone() ); 152 | 153 | } 154 | 155 | this.points = newpoints; 156 | 157 | }; 158 | 159 | // Catmull-Rom 160 | 161 | function interpolate( p0, p1, p2, p3, t, t2, t3 ) { 162 | 163 | var v0 = ( p2 - p0 ) * 0.5, 164 | v1 = ( p3 - p1 ) * 0.5; 165 | 166 | return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1; 167 | 168 | } 169 | 170 | } 171 | 172 | /** 173 | * Spline interpolation with waypoints. 174 | */ 175 | AFRAME.registerComponent('curve-movement', { 176 | schema: { 177 | debug: {default: false}, 178 | type: {default: 'single'}, 179 | restTime: {default: 150}, // ms. 180 | speed: {default: 3}, // meters per second. 181 | loopStart: {default: 1}, 182 | timeOffset: {default: 0} 183 | }, 184 | 185 | init: function () { 186 | this.direction = 1; 187 | }, 188 | 189 | isClosed: function () { 190 | return this.data.type === 'loop'; 191 | }, 192 | 193 | addPoints: function (points) { 194 | var data = this.data; 195 | var spline; 196 | var chunkLengths; 197 | 198 | // Set waypoints. 199 | if (data.type === 'loop') { 200 | points = points.slice(0); // clone array as we'll need to modify it 201 | points.push(points[this.data.loopStart]); 202 | } 203 | 204 | // Build spline. 205 | spline = this.spline = ASpline(); 206 | spline.initFromArray(points); 207 | 208 | // Keep track of current point to get to the next point. 209 | this.currentPointIndex = 0; 210 | 211 | // Compute how long to get from each point to the next for each chunk using speed. 212 | chunkLengths = spline.getLength().chunks; 213 | this.cycleTimes = chunkLengths.map(function (chunkLength, i) { 214 | if (i === 0) { return null; } 215 | return (chunkLength - chunkLengths[i - 1]) / data.speed * 1000; 216 | }).filter(function (length) { return length !== null; }); 217 | 218 | // Keep a local time to reset at each point, for separate easing from point to point. 219 | this.time = this.data.timeOffset; 220 | this.initTime = null; 221 | this.restTime = 0; 222 | this.direction = 1; 223 | this.end = false; 224 | }, 225 | 226 | update: function () { 227 | var data = this.data; 228 | var el = this.el; 229 | 230 | // Visual debug stuff. 231 | if (data.debug) { 232 | el.setAttribute('spline-line', {pointer: 'movement-pattern.movementPattern.spline'}); 233 | } else { 234 | el.removeAttribute('spline-line'); 235 | } 236 | }, 237 | play: function () { 238 | this.time = this.data.timeOffset; 239 | this.initTime = null; 240 | }, 241 | tick: function (time, delta) { 242 | var cycleTime; 243 | var data = this.data; 244 | var el = this.el; 245 | var percent; 246 | var point; 247 | var spline = this.spline; 248 | 249 | if (!this.initTime) { 250 | this.initTime = time; 251 | } 252 | 253 | // If not closed and reached the end, just stop (for now). 254 | if (this.end) {return;} 255 | if (!this.isClosed() && this.currentPointIndex === spline.points.length - 1) { return; } 256 | 257 | // Mod the current time to get the current cycle time and divide by total time. 258 | cycleTime = this.cycleTimes[this.currentPointIndex]; 259 | 260 | var t = 0; 261 | var jump = false; 262 | if (this.time > cycleTime) { 263 | t = 1; 264 | jump = true; 265 | } else { 266 | t = this.time / cycleTime; 267 | } 268 | 269 | if (this.direction === -1) { 270 | t = 1 - t; 271 | } 272 | 273 | if (data.type === 'single') { 274 | percent = inOutSine(t); 275 | } 276 | else { 277 | percent = t; 278 | } 279 | 280 | this.time = time - this.initTime; 281 | 282 | if (this.time < 0) { console.log(percent); return; } 283 | 284 | point = spline.getPointFrom(percent, this.currentPointIndex); 285 | el.setAttribute('position', {x: point.x, y: point.y, z: point.z}); 286 | this.lastPercent = percent; 287 | 288 | if (jump) { 289 | if (this.direction === 1) { 290 | if (this.currentPointIndex === spline.points.length - 2) { 291 | if (data.type === 'single') { 292 | this.end = true; 293 | } else if (data.type === 'loop') { 294 | this.currentPointIndex = this.data.loopStart; 295 | } else { 296 | this.direction = -1; 297 | } 298 | } else { 299 | this.currentPointIndex ++; 300 | } 301 | } else { 302 | this.currentPointIndex --; 303 | if (this.currentPointIndex < this.data.loopStart) { 304 | this.currentPointIndex = this.data.loopStart; 305 | this.direction = 1; 306 | } 307 | } 308 | this.initTime = time; 309 | this.time = 0; 310 | } 311 | } 312 | }); 313 | 314 | function inOutSine (k) { 315 | return .5 * (1 - Math.cos(Math.PI * k)); 316 | } 317 | 318 | /** 319 | * Spline with point to point interpolation. 320 | */ 321 | function ASpline (points) { 322 | var spline = new THREE.Spline(points); 323 | 324 | /** 325 | * Interpolate between pointIndex and the next index. 326 | * 327 | * k {number} - From 0 to 1. 328 | * pointIndex {number} - Starting point index to interpolate from. 329 | */ 330 | spline.getPointFrom = function (k, pointIndex) { 331 | var c, pa, pb, pc, pd, points, midpoint, w2, w3, v3, weight; 332 | points = this.points; 333 | 334 | midpoint = pointIndex + k; 335 | 336 | c = []; 337 | c[0] = pointIndex === 0 ? pointIndex : pointIndex - 1; 338 | c[1] = pointIndex; 339 | c[2] = pointIndex > points.length - 2 ? points.length - 1 : pointIndex + 1; 340 | c[3] = pointIndex > points.length - 3 ? points.length - 1 : pointIndex + 2; 341 | 342 | pa = points[c[0]]; 343 | pb = points[c[1]]; 344 | pc = points[c[2]]; 345 | pd = points[c[3]]; 346 | 347 | weight = midpoint - pointIndex; 348 | w2 = weight * weight; 349 | w3 = weight * w2; 350 | 351 | v3 = {}; 352 | v3.x = interpolate(pa.x, pb.x, pc.x, pd.x, weight, w2, w3); 353 | v3.y = interpolate(pa.y, pb.y, pc.y, pd.y, weight, w2, w3); 354 | v3.z = interpolate(pa.z, pb.z, pc.z, pd.z, weight, w2, w3); 355 | return v3; 356 | }; 357 | spline.getPointFrom = spline.getPointFrom.bind(spline); 358 | 359 | /** 360 | * Catmull-Rom 361 | */ 362 | function interpolate (p0, p1, p2, p3, t, t2, t3) { 363 | var v0 = (p2 - p0) * 0.5; 364 | var v1 = (p3 - p1) * 0.5; 365 | return (2 * (p1 - p2) + v0 + v1) * t3 + (-3 * (p1 - p2) - 2 * v0 - v1) * t2 + v0 * t + p1; 366 | } 367 | 368 | return spline; 369 | } 370 | -------------------------------------------------------------------------------- /src/components/decals.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME THREE */ 2 | AFRAME.registerSystem('decals', { 3 | schema: { 4 | size: {default: 0.1}, 5 | src: {default: '', type: 'asset'}, 6 | maxDecals: {default: 30} // 0 for infinite 7 | }, 8 | 9 | init: function () { 10 | this.numDecals = 0; 11 | this.decals = []; 12 | this.oldestDecalIdx = 0; 13 | this.textureSrc = null; 14 | 15 | this.geometry = new THREE.PlaneGeometry(1, 1); 16 | this.material = new THREE.MeshBasicMaterial({ 17 | transparent: true, 18 | color: '#24CAFF', 19 | depthTest: true, 20 | depthWrite: false, 21 | polygonOffset: true, 22 | polygonOffsetFactor: -20 23 | }); 24 | 25 | this.updateMap(); 26 | }, 27 | 28 | updateMap: function () { 29 | var src = this.data.src; 30 | 31 | if (src) { 32 | if (src === this.textureSrc) { return; } 33 | // Texture added or changed. 34 | this.textureSrc = src; 35 | this.sceneEl.systems.material.loadTexture(src, {src: src}, setMap.bind(this)); 36 | return; 37 | } 38 | 39 | // Texture removed. 40 | if (!this.material.map) { return; } 41 | setMap(null); 42 | 43 | function setMap (texture) { 44 | this.material.map = texture; 45 | this.material.needsUpdate = true; 46 | } 47 | }, 48 | 49 | update: function (oldData) { 50 | this.updateMap(); 51 | }, 52 | 53 | getDecal: function () { 54 | var maxDecals = this.data.maxDecals; 55 | var size = this.data.size; 56 | var decal = null; 57 | 58 | if (maxDecals === 0 || this.numDecals < maxDecals) { 59 | decal = new THREE.Mesh(this.geometry, this.material); 60 | this.numDecals++; 61 | this.decals.push(decal); 62 | } else { 63 | decal = this.decals[this.oldestDecalIdx]; 64 | this.oldestDecalIdx = (this.oldestDecalIdx + 1) % this.data.maxDecals; 65 | } 66 | decal.scale.set(size, size, size); 67 | 68 | return decal; 69 | }, 70 | 71 | addDecal: function (point, normal) { 72 | var decal = this.getDecal(); 73 | if (decal) { 74 | decal.position.set(0, 0, 0); 75 | decal.position.copy(point); 76 | decal.lookAt(normal); 77 | decal.rotation.z += Math.random() * Math.PI * 2; 78 | this.sceneEl.object3D.add(decal); 79 | } 80 | } 81 | }); 82 | -------------------------------------------------------------------------------- /src/components/enemy.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME ABLAST THREE */ 2 | AFRAME.registerComponent('enemy', { 3 | schema: { 4 | name: {default: 'enemy0'}, 5 | bulletName: {default: 'enemy-slow'}, 6 | shootingDelay: {default: 200}, // ms 7 | health: {default: 1}, 8 | color: {default: '#fff'}, 9 | scale: {default: 1}, 10 | canShoot: {default: true} 11 | }, 12 | init: function () { 13 | this.alive = true; 14 | this.hipBone = null; 15 | this.definition = ABLAST.ENEMIES[this.data.name].definition; 16 | this.definition.init.call(this); 17 | var comp = ABLAST.ENEMIES[this.data.name].components.enemy; 18 | this.maxhealth = this.health = comp.health; 19 | this.color = comp.color; 20 | this.scale = comp.scale; 21 | this.gunOffset = new THREE.Vector3(0.0, 0.44, 0.5).multiplyScalar(this.scale); 22 | this.lastShootTime = undefined; 23 | this.shootAt = 0; 24 | this.warmUpTime = 1000; 25 | this.paused = false; 26 | 27 | var self = this; 28 | this.el.addEventListener('model-loaded', function(event) { 29 | self.el.components['json-model'].playAnimation('fly', true); 30 | self.hipBone = self.el.object3D.children[3].children[0]; 31 | }); 32 | 33 | // gun glow 34 | this.gunGlowMaterial = new THREE.MeshBasicMaterial({ 35 | color: this.color, 36 | side: THREE.DoubleSide, 37 | transparent: true, 38 | blending: THREE.AdditiveBlending, 39 | depthTest: true, 40 | depthWrite: false, 41 | visible: false 42 | }); 43 | var src = document.querySelector('#fx3').getAttribute('src'); 44 | this.el.sceneEl.systems.material.loadTexture(src, {src: src}, setMap.bind(this)); 45 | 46 | function setMap (texture) { 47 | this.gunGlowMaterial.alphaMap = texture; 48 | this.gunGlowMaterial.needsUpdate = true; 49 | this.gunGlowMaterial.visible = true; 50 | } 51 | this.gunGlow = new THREE.Mesh(new THREE.PlaneGeometry(this.scale, this.scale), this.gunGlowMaterial); 52 | this.gunGlow.position.copy(this.gunOffset); 53 | this.el.setObject3D('glow', this.gunGlow); 54 | 55 | this.exploding = false; 56 | this.explodingDuration = 500 + Math.floor(Math.random()*300); 57 | this.el.addEventListener('hit', this.collided.bind(this)); 58 | 59 | this.sounds = [ 60 | document.getElementById('explosion0'), 61 | document.getElementById('explosion1'), 62 | document.getElementById('explosion2') 63 | ]; 64 | // @todo Maybe we could send the time in init? 65 | }, 66 | update: function (oldData) { 67 | }, 68 | play: function () { 69 | this.paused = false; 70 | }, 71 | pause: function () { 72 | this.paused = true; 73 | }, 74 | collided: function () { 75 | if (this.exploding) { 76 | return; 77 | } 78 | 79 | this.health--; 80 | 81 | if (this.health <= 0) { 82 | this.el.emit('enemy-hit'); 83 | this.exploding = true; 84 | 85 | var mesh = this.el.getObject3D('mesh'); 86 | this.whiteMaterial = new THREE.MeshBasicMaterial({color: this.color, transparent: true }); 87 | mesh.normalMaterial = mesh.material; 88 | mesh.material = this.whiteMaterial; 89 | 90 | this.gunGlow.visible = false; 91 | 92 | this.system.activeEnemies.splice(this.system.activeEnemies.indexOf(this.el), 1); 93 | } 94 | }, 95 | 96 | die: function () { 97 | this.alive = false; 98 | this.reset(); 99 | this.system.onEnemyDeath(this.data.name, this.el); 100 | }, 101 | 102 | reset: function () { 103 | var mesh = this.el.getObject3D('mesh'); 104 | if (mesh) { 105 | mesh.material.opacity = 1; 106 | mesh.scale.set(this.scale, this.scale, this.scale); 107 | mesh.material = mesh.normalMaterial; 108 | this.gunGlow.visible = true; 109 | this.gunGlow.scale.set(1, 1, 1); 110 | this.gunGlowMaterial.opacity = 0.3; 111 | } 112 | 113 | this.el.setAttribute('scale', '1 1 1'); 114 | this.explodingTime = undefined; 115 | this.lastShootTime = undefined; 116 | this.shootAt = 0; 117 | this.warmUpTime = 1000; 118 | 119 | this.health = this.maxhealth; 120 | this.alive = true; 121 | this.exploding = false; 122 | this.definition.reset.call(this); 123 | }, 124 | 125 | shoot: function (time, delta) { 126 | var el = this.el; 127 | if (!el) return; 128 | var data = this.data; 129 | var mesh = el.object3D; 130 | var gunPosition = mesh.localToWorld(this.gunGlow.position.clone()); 131 | var head = el.sceneEl.camera.el.components['look-controls'].dolly.position.clone(); 132 | var direction = head.sub(mesh.position).normalize(); 133 | 134 | this.lastShootTime = time; 135 | 136 | this.gunGlow.scale.set(3, 3, 3); 137 | this.gunGlowMaterial.opacity = 1; 138 | 139 | /* 140 | var explosion = document.createElement('a-entity'); 141 | explosion.setAttribute('position', gunPosition); 142 | explosion.setAttribute('explosion', { 143 | type: 'enemygun', 144 | color: this.color, 145 | scale: this.scale, 146 | lookAt: direction 147 | }); 148 | explosion.setAttribute('sound', { 149 | src: document.getElementById(this.data.name + 'shoot').src, 150 | volume: 0.5, 151 | poolSize: 8, 152 | autoplay: true 153 | }); 154 | this.el.sceneEl.appendChild(explosion); 155 | */ 156 | this.el.sceneEl.systems.explosion.createExplosion('enemygun', gunPosition, this.color, this.scale, direction, this.data.name); 157 | 158 | // Ask system for bullet and set bullet position to starting point. 159 | var bulletEntity = el.sceneEl.systems.bullet.getBullet(data.bulletName); 160 | bulletEntity.setAttribute('bullet', { 161 | position: gunPosition, 162 | direction: direction, 163 | owner: 'enemy' 164 | }); 165 | bulletEntity.setAttribute('position', gunPosition); 166 | bulletEntity.setAttribute('visible', true); 167 | bulletEntity.play(); 168 | }, 169 | 170 | willShoot: function (time, delta, warmUpTime) { 171 | this.shootAt = time + warmUpTime; 172 | this.warmUpTime = warmUpTime; 173 | }, 174 | 175 | tick: function (time, delta) { 176 | if (!this.alive || this.paused) { 177 | return; 178 | } 179 | if (!this.exploding) { 180 | //gun glow 181 | var glowFadeOutTime = 700; 182 | if (this.lastShootTime === undefined) { 183 | this.lastShootTime = time; 184 | } 185 | else { 186 | if (this.shootAt - time < this.warmUpTime) { 187 | this.gunGlowMaterial.opacity = (this.shootAt - time) / this.warmUpTime; 188 | var glowScale = 1.0 + Math.abs(Math.sin(time / 50)); 189 | this.gunGlow.scale.set(glowScale, glowScale, glowScale); 190 | } 191 | else if (time - this.lastShootTime < glowFadeOutTime) { 192 | this.gunGlowMaterial.opacity = 1 - (time - this.lastShootTime) / glowFadeOutTime; 193 | } 194 | } 195 | this.gunGlow.position.copy(this.gunOffset); 196 | if (this.hipBone) { 197 | this.gunGlow.position.y += this.hipBone.position.y; 198 | } 199 | // Make the droid to look the headset 200 | var head = this.el.sceneEl.camera.el.components['look-controls'].dolly.position.clone(); 201 | this.el.object3D.lookAt(head); 202 | 203 | this.definition.tick.call(this, time, delta); 204 | } else { 205 | if (!this.explodingTime) { 206 | this.explodingTime = time; 207 | } 208 | var t0 = (time - this.explodingTime) / this.explodingDuration; 209 | 210 | var scale = this.scale + t0 * ( 2 - t0 ); //out easing 211 | 212 | var mesh = this.el.getObject3D('mesh'); 213 | mesh.scale.set(scale, scale, scale); 214 | mesh.material.opacity = Math.max(0, 1 - t0 * 2.5); 215 | if (t0 >= 1) { 216 | this.die(); 217 | } 218 | } 219 | 220 | } 221 | }); 222 | -------------------------------------------------------------------------------- /src/components/explosion.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME ABLAST THREE */ 2 | 3 | AFRAME.registerComponent('explosion', { 4 | schema: { 5 | type: { default: 'enemy', oneOf: ['enemy', 'bullet', 'background', 'enemygun'] }, 6 | duration: { default: 500 }, 7 | color: { type: 'color', default: '#FFFFFF' }, 8 | lookAt: { type: 'vec3', default: null}, 9 | scale: { default: 1 } 10 | }, 11 | 12 | update: function (oldData) { 13 | if (this.data.type === 'enemy') { 14 | this.materials[2].color.set(this.data.color); 15 | this.materials[4].color.set(this.data.color); 16 | } else if (this.data.type === 'bullet') { 17 | this.data.scale *= 0.5; // HACK! remove! 18 | this.materials[0].color.set(this.data.color); 19 | this.materials[2].color.set(this.data.color); 20 | } else if (this.data.type === 'enemygun') { 21 | this.materials[0].color.set(this.data.color); 22 | this.data.duration = 300; 23 | } else if (this.data.type === 'background') { 24 | this.data.duration = 300; 25 | } 26 | 27 | for (var i = 0; i < this.meshes.children.length; i++){ 28 | var mesh = this.meshes.children[i]; 29 | if (mesh.part.billboard && this.data.lookAt) { 30 | mesh.lookAt(this.data.lookAt); 31 | } 32 | } 33 | 34 | this.el.setAttribute('scale', {x: this.data.scale, y: this.data.scale, z: this.data.scale }); 35 | }, 36 | 37 | init: function () { 38 | this.life = 0; 39 | this.starttime = null; 40 | this.meshes = new THREE.Group(); 41 | 42 | this.materials = []; 43 | var textureSrcs = new Array('#fx1', '#fx2', '#fx3', '#fx4', '#fx8'); 44 | 45 | this.el.setAttribute('scale', {x: this.data.scale, y: this.data.scale, z: this.data.scale }); 46 | 47 | switch(this.data.type) { 48 | case 'enemy': 49 | this.parts = [ 50 | {textureIdx: 2, billboard: true, color: 16777215, scale: 1.5, grow: 4, dispersion: 0, copies: 1, speed: 0 }, 51 | {textureIdx: 0, billboard: true, color: 16777215, scale: 0.4, grow: 2, dispersion: 2.5, copies: 1, speed: 1 }, 52 | {textureIdx: 3, billboard: false, color: this.data.color, scale: 1, grow: 6, dispersion: 0, copies: 1, speed: 0 }, 53 | {textureIdx: 1, billboard: true, color: 16577633, scale: 0.04, grow: 2, dispersion: 3, copies: 20, speed: 2}, 54 | {textureIdx: 3, billboard: true, color: this.data.color, scale: 0.2, grow: 2, dispersion: 2, copies: 5, speed: 1} 55 | ]; 56 | break; 57 | case 'bullet': 58 | //this.data.scale = this.data.scale * 0.5; 59 | this.parts = [ 60 | {textureIdx: 2, billboard: true, color: this.data.color, scale: .5, grow: 3, dispersion: 0, copies: 1, speed: 0 }, 61 | {textureIdx: 4, billboard: true, color: '#24CAFF', scale: .3, grow: 4, dispersion: 0, copies: 1, speed: 0 }, 62 | {textureIdx: 0, billboard: true, color: this.data.color, scale: 0.04, grow: 2, dispersion: 1.5, copies: 8, speed: 1 } 63 | ]; 64 | break; 65 | case 'background': 66 | this.parts = [ 67 | {textureIdx: 4, billboard: true, color: '#24CAFF', scale: .3, grow: 3, dispersion: 0, copies: 1, speed: 0 }, 68 | {textureIdx: 0, billboard: true, color: '#24CAFF', scale: 0.03, grow: 1, dispersion: 0.3, copies: 8, speed: 1.6, noFade: true } 69 | ]; 70 | break; 71 | case 'enemygun': 72 | this.parts = [ 73 | {textureIdx: 3, billboard: true, color: this.data.color, scale: .5, grow: 3, dispersion: 0, copies: 1, speed: 0 }, 74 | ]; 75 | break; 76 | } 77 | 78 | 79 | for (var i in this.parts) { 80 | var part = this.parts[i]; 81 | part.meshes = []; 82 | var planeGeometry = new THREE.PlaneGeometry(part.scale, part.scale); 83 | var material = new THREE.MeshBasicMaterial({ 84 | color: part.color, 85 | side: THREE.DoubleSide, 86 | transparent: true, 87 | blending: THREE.AdditiveBlending, 88 | depthTest: true, 89 | depthWrite: false, 90 | visible: false 91 | }); 92 | material['noFade'] = part['noFade'] === true; 93 | 94 | this.materials.push(material); 95 | var src = document.querySelector(textureSrcs[part.textureIdx]).getAttribute('src'); 96 | this.el.sceneEl.systems.material.loadTexture(src, {src: src}, setMap.bind(this, i)); 97 | 98 | function setMap (idx, texture) { 99 | this.materials[idx].alphaMap = texture; 100 | this.materials[idx].needsUpdate = true; 101 | this.materials[idx].visible = true; 102 | } 103 | 104 | var dispersionCenter = part.dispersion / 2; 105 | 106 | for (var n = 0; n < part.copies; n++) { 107 | var mesh = new THREE.Mesh(planeGeometry, material); 108 | if (!part.billboard) { 109 | mesh.rotation.set(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2); 110 | } 111 | else if (this.data.lookAt) { 112 | mesh.lookAt(this.data.lookAt); 113 | } 114 | if (part.dispersion > 0) { 115 | mesh.position.set( 116 | Math.random() * part.dispersion - dispersionCenter, 117 | Math.random() * part.dispersion - dispersionCenter, 118 | Math.random() * part.dispersion - dispersionCenter 119 | ); 120 | mesh.speed = part.speed + Math.random() / part.dispersion; 121 | } 122 | mesh.part = part; 123 | this.meshes.add(mesh); 124 | part.meshes.push(mesh); 125 | } 126 | } 127 | 128 | this.el.setObject3D('explosion', this.meshes); 129 | }, 130 | 131 | reset: function () { 132 | this.life = 0; 133 | this.starttime = null; 134 | 135 | for (var i in this.parts) { 136 | var part = this.parts[i]; 137 | 138 | var dispersionCenter = part.dispersion / 2; 139 | 140 | for (var n = 0; n < part.copies; n++) { 141 | var mesh = part.meshes[n]; 142 | 143 | if (!part.billboard) { 144 | mesh.rotation.set(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2); 145 | } 146 | else if (this.data.lookAt) { 147 | mesh.lookAt(this.data.lookAt); 148 | } 149 | if (part.dispersion > 0) { 150 | mesh.position.set( 151 | Math.random() * part.dispersion - dispersionCenter, 152 | Math.random() * part.dispersion - dispersionCenter, 153 | Math.random() * part.dispersion - dispersionCenter 154 | ); 155 | mesh.speed = part.speed + Math.random() / part.dispersion; 156 | } 157 | } 158 | } 159 | this.starttime = null; 160 | 161 | 162 | this.system.returnToPool(this.data.type, this.el); 163 | }, 164 | 165 | tick: function (time, delta) { 166 | if (this.starttime === null) { 167 | this.starttime = time; 168 | } 169 | this.life = (time - this.starttime) / this.data.duration; 170 | 171 | if (this.life > 1) { 172 | this.reset(); 173 | return; 174 | } 175 | 176 | var t = this.life * ( 2 - this.life ); //out easing 177 | 178 | for (var i = 0; i < this.meshes.children.length; i++){ 179 | var mesh = this.meshes.children[i]; 180 | var s = 1 + t * mesh.part.grow; 181 | mesh.scale.set(s, s, s); 182 | if (mesh.part.speed > 0) { 183 | mesh.position.multiplyScalar(1 + delta / 1000 * mesh.speed); 184 | } 185 | } 186 | for (var i in this.materials) { 187 | if (this.materials[i].noFade) { 188 | continue; 189 | } 190 | this.materials[i].opacity = 1 - t; 191 | } 192 | } 193 | }); 194 | -------------------------------------------------------------------------------- /src/components/gamestate-debug.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME */ 2 | /** 3 | * Display entire game state as text. 4 | */ 5 | AFRAME.registerComponent('gamestate-debug', { 6 | init: function () { 7 | var el = this.el; 8 | var sceneEl = this.el.sceneEl; 9 | 10 | sceneEl.addEventListener('gamestate-initialized', setText); 11 | sceneEl.addEventListener('gamestate-changed', setText); 12 | 13 | function setText (evt) { 14 | el.setAttribute('bmfont-text', {text: buildText(evt.detail.state), color: '#DADADA'}); 15 | } 16 | } 17 | }); 18 | 19 | function buildText (state) { 20 | var text = 'DEBUG\n'; 21 | Object.keys(state).sort().forEach(function appendText (property) { 22 | text += property + ': ' + state[property] + '\n'; 23 | }); 24 | return text; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/gamestate-visuals.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME ABLAST THREE */ 2 | AFRAME.registerComponent('gamestate-visuals', { 3 | schema: { 4 | }, 5 | init: function () { 6 | this.logo = document.getElementById('logo'); 7 | this.startEnemy = document.getElementById('start_enemy'); 8 | this.mainMenuGroup = document.getElementById('mainmenu'); 9 | this.messageGroup = document.getElementById('message-group'); 10 | this.gameover = document.getElementById('gameover-model'); 11 | this.welldone = document.getElementById('welldone-model'); 12 | this.reset = document.getElementById('reset'); 13 | this.highscores = document.getElementById('highscores'); 14 | 15 | var self = this; 16 | this.el.sceneEl.addEventListener('gamestate-changed', function (evt) { 17 | if ('state' in evt.detail.diff) { 18 | if (evt.detail.state.state === 'STATE_PLAYING') { 19 | self.startPlaying(); 20 | } else if (evt.detail.state.state === 'STATE_GAME_OVER') { 21 | self.finishPlaying('GAME_OVER'); 22 | } else if (evt.detail.state.state === 'STATE_GAME_WIN') { 23 | self.finishPlaying('GAME_WIN'); 24 | } else if (evt.detail.state.state === 'STATE_MAIN_MENU') { 25 | self.mainMenu(); 26 | } 27 | } 28 | }.bind(this)); 29 | }, 30 | 31 | startPlaying: function () { 32 | this.highscores.setAttribute('visible', false); 33 | 34 | var self = this; 35 | var rotation = { x: 0.0 }; 36 | var tween = new AFRAME.TWEEN.Tween(rotation) 37 | .to({x: Math.PI * 0.6}, 1000) 38 | .onComplete(function () { 39 | self.mainMenuGroup.setAttribute('visible', false); 40 | }) 41 | .easing(AFRAME.TWEEN.Easing.Back.InOut) 42 | .onUpdate(function () { 43 | self.logo.object3D.rotation.x = rotation.x 44 | }); 45 | tween.start(); 46 | this.startEnemy.setAttribute('visible', false); 47 | }, 48 | 49 | finishPlaying: function (type) { 50 | var self = this; 51 | var gameover = type === 'GAME_OVER'; 52 | 53 | var group = document.getElementById('finished'); 54 | 55 | this.highscores.setAttribute('visible', true); 56 | this.gameover.setAttribute('visible', gameover); 57 | this.welldone.setAttribute('visible', !gameover); 58 | 59 | // Move the text info 60 | group.setAttribute('visible', true); 61 | group.object3D.position.y = -5; 62 | 63 | var groupPosition = { y: -5 }; 64 | var tweenGroup = new AFRAME.TWEEN.Tween(groupPosition) 65 | .to({y: 1}, 1000) 66 | .easing(AFRAME.TWEEN.Easing.Elastic.Out) 67 | .onUpdate(function () { 68 | group.object3D.position.y = groupPosition.y; 69 | }); 70 | tweenGroup.start(); 71 | 72 | // Move the reset buttom 73 | this.reset.object3D.position.y = -5; 74 | var resetPosition = { y: -5 }; 75 | var tweenReset = new AFRAME.TWEEN.Tween(resetPosition) 76 | .to({y: 0}, 1000) 77 | .delay(3000) 78 | .easing(AFRAME.TWEEN.Easing.Elastic.Out) 79 | .onUpdate(function () { 80 | self.reset.object3D.position.y = resetPosition.y; 81 | }); 82 | tweenReset.start(); 83 | }, 84 | 85 | mainMenu: function () { 86 | var self = this; 87 | this.startEnemy.setAttribute('position', '0 -5 -4'); 88 | this.startEnemy.setAttribute('visible', true); 89 | this.mainMenuGroup.setAttribute('visible', true); 90 | 91 | // Move the enemy up 92 | var enemyPosition = { positionY: -5 }; 93 | var tweenEnemy = new AFRAME.TWEEN.Tween(enemyPosition) 94 | .to({positionY: 1.4}, 1000) 95 | .delay(1000) 96 | .easing(AFRAME.TWEEN.Easing.Back.InOut) 97 | .onUpdate(function () { 98 | self.startEnemy.setAttribute('position', {x: 0, y: enemyPosition.positionY, z: -4}) 99 | }); 100 | tweenEnemy.start(); 101 | 102 | // Move the gameover & well done down 103 | var group = document.getElementById('finished'); 104 | 105 | group.object3D.position.y = 1; 106 | 107 | var textsPosition = { y: 1 }; 108 | var tween = new AFRAME.TWEEN.Tween(textsPosition) 109 | .to({y: -5}, 1000) 110 | .easing(AFRAME.TWEEN.Easing.Elastic.In) 111 | .onComplete(function () { 112 | group.setAttribute('visible', false); 113 | }) 114 | .onUpdate(function () { 115 | group.object3D.position.y = textsPosition.y; 116 | }); 117 | tween.start(); 118 | 119 | // A-Blast logo will appears after a 1s delay 120 | this.logo.object3D.rotation.x = Math.PI * 0.6; 121 | 122 | var logoRotation = { x: 0 }; 123 | var tween = new AFRAME.TWEEN.Tween(logoRotation) 124 | .to({x: Math.PI * 0.6}, 1000) 125 | .easing(AFRAME.TWEEN.Easing.Elastic.Out) 126 | .delay(1000) 127 | .onUpdate(function () { 128 | self.logo.object3D.rotation.x = Math.PI * 0.6 - logoRotation.x; 129 | }); 130 | tween.start(); 131 | } 132 | }); 133 | -------------------------------------------------------------------------------- /src/components/gamestate.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | ABLAST.currentScore = { 3 | name: '', 4 | points: 0, 5 | time: 0, 6 | shoots: 0, 7 | validShoot: 0 8 | }; 9 | 10 | AFRAME.registerComponent('gamestate', { 11 | schema: { 12 | health: {default: 5}, 13 | numEnemies: {default: 0}, 14 | numSequences: {default: 0}, 15 | points: {default: 0}, 16 | numEnemiesToWin: {default: 100}, 17 | isGameOver: {default: false}, 18 | isGameWin: {default: false}, 19 | state: {default: 'STATE_MAIN_MENU', oneOf: ['STATE_MAIN_MENU', 'STATE_PLAYING', 'STATE_GAME_OVER', 'STATE_GAME_WIN']}, 20 | wave: {default: 0}, 21 | waveSequence: {default: 0} 22 | }, 23 | 24 | gameEnd: function (newState, win) { 25 | newState.state = 'STATE_GAME_WIN'; 26 | newState.isGameWin = true; 27 | document.getElementById('introMusic').components.sound.playSound(); 28 | document.getElementById('mainThemeMusic').components.sound.pauseSound(); 29 | }, 30 | init: function () { 31 | var self = this; 32 | var el = this.el; 33 | var initialState = this.initialState; 34 | var state = this.data; 35 | 36 | // Initial state. 37 | if (!initialState) { initialState = state; } 38 | 39 | el.emit('gamestate-initialized', {state: initialState}); 40 | registerHandler('enemy-death', function (newState) { 41 | newState.points++; 42 | ABLAST.currentScore.points++; 43 | if (newState.points >= self.data.numEnemiesToWin) { 44 | self.gameEnd(newState, true); 45 | } 46 | 47 | newState.numEnemies--; 48 | // All enemies killed, advance wave. 49 | if (newState.numEnemies === 0) { 50 | newState.numSequences--; 51 | newState.waveSequence++; 52 | if (newState.numSequences === 0) { 53 | newState.waveSequence = 0; 54 | newState.wave++; 55 | if (newState.wave >= WAVES.length) { 56 | self.gameEnd(newState, true); 57 | } 58 | } 59 | } 60 | return newState; 61 | }); 62 | 63 | registerHandler('wave-created', function (newState, params) { 64 | var wave = params.detail.wave; 65 | newState.numSequences = wave.sequences.length; 66 | newState.waveSequence = 0; 67 | return newState; 68 | }); 69 | 70 | registerHandler('enemy-spawn', function (newState) { 71 | newState.numEnemies++; 72 | return newState; 73 | }); 74 | 75 | registerHandler('start-game', function (newState) { 76 | newState.isGameOver = false; 77 | newState.isGameWin = false; 78 | newState.state = 'STATE_PLAYING'; 79 | return newState; 80 | }); 81 | 82 | registerHandler('player-hit', function (newState) { 83 | if (newState.state === 'STATE_PLAYING') { 84 | newState.health -= 1; 85 | if (newState.health <= 0) { 86 | newState.isGameOver = true; 87 | newState.numEnemies = 0; 88 | newState.state = 'STATE_GAME_OVER'; 89 | document.getElementById('introMusic').components.sound.playSound(); 90 | document.getElementById('mainThemeMusic').components.sound.pauseSound(); 91 | } 92 | } 93 | return newState; 94 | }); 95 | 96 | registerHandler('reset', function () { 97 | ABLAST.currentScore = { 98 | name: '', 99 | points: 0, 100 | time: 0, 101 | shoots: 0, 102 | validShoot: 0 103 | }; 104 | 105 | return initialState; 106 | }); 107 | 108 | function registerHandler (event, handler) { 109 | el.addEventListener(event, function (param) { 110 | var newState = handler(AFRAME.utils.extend({}, state), param); 111 | publishState(event, newState); 112 | }); 113 | } 114 | 115 | function publishState (event, newState) { 116 | var oldState = AFRAME.utils.extend({}, state); 117 | el.setAttribute('gamestate', newState); 118 | state = newState; 119 | el.emit('gamestate-changed', { 120 | event: event, 121 | diff: AFRAME.utils.diff(oldState, newState), 122 | state: newState 123 | }); 124 | } 125 | } 126 | }); 127 | 128 | /** 129 | * Bind game state to a component property. 130 | */ 131 | AFRAME.registerComponent('gamestate-bind', { 132 | schema: { 133 | default: {}, 134 | parse: AFRAME.utils.styleParser.parse 135 | }, 136 | 137 | update: function () { 138 | var sceneEl = this.el.closestScene(); 139 | if (sceneEl.hasLoaded) { 140 | this.updateBinders(); 141 | } 142 | sceneEl.addEventListener('loaded', this.updateBinders.bind(this)); 143 | }, 144 | 145 | updateBinders: function () { 146 | var data = this.data; 147 | var el = this.el; 148 | var subscribed = Object.keys(this.data); 149 | 150 | el.sceneEl.addEventListener('gamestate-changed', function (evt) { 151 | syncState(evt.detail.diff); 152 | }); 153 | 154 | el.sceneEl.addEventListener('gamestate-initialized', function (evt) { 155 | syncState(evt.detail.state); 156 | }); 157 | 158 | function syncState (state) { 159 | Object.keys(state).forEach(function updateIfNecessary (stateProperty) { 160 | var targetProperty = data[stateProperty]; 161 | var value = state[stateProperty]; 162 | if (subscribed.indexOf(stateProperty) === -1) { return; } 163 | AFRAME.utils.entity.setComponentProperty(el, targetProperty, value); 164 | }); 165 | } 166 | } 167 | }); 168 | -------------------------------------------------------------------------------- /src/components/gun.js: -------------------------------------------------------------------------------- 1 | var WEAPONS = require('./weapon'); 2 | 3 | /** 4 | * Spawn bullets on an event. 5 | * Default schema optimized for Vive controllers. 6 | */ 7 | AFRAME.registerComponent('shoot', { 8 | schema: { 9 | direction: {type: 'vec3', default: {x: 0, y: -2, z: -1}}, // Event to fire bullet. 10 | on: {default: 'triggerdown'}, // Event to fire bullet. 11 | spaceKeyEnabled: {default: false}, // Keyboard support. 12 | weapon: {default: 'default'} // Weapon definition. 13 | }, 14 | 15 | init: function () { 16 | var data = this.data; 17 | var el = this.el; 18 | var self = this; 19 | 20 | this.coolingDown = false; // Limit fire rate. 21 | this.shoot = this.shoot.bind(this); 22 | this.weapon = null; 23 | 24 | // Add event listener. 25 | if (data.on) { el.addEventListener(data.on, this.shoot); } 26 | 27 | // Add keyboard listener. 28 | if (data.spaceKeyEnabled) { 29 | window.addEventListener('keydown', function (evt) { 30 | if (evt.code === 'Space' || evt.keyCode === '32') { self.shoot(); } 31 | }); 32 | } 33 | /* 34 | if (AFRAME.utils.device.isMobile()) 35 | { 36 | window.addEventListener('click', function (evt) { 37 | self.shoot(); 38 | }); 39 | } 40 | */ 41 | }, 42 | 43 | update: function (oldData) { 44 | // Update weapon. 45 | this.weapon = WEAPONS[this.data.weapon]; 46 | 47 | if (oldData.on !== this.data.on) { 48 | this.el.removeEventListener(oldData.on, this.shoot); 49 | this.el.addEventListener(this.data.on, this.shoot); 50 | } 51 | }, 52 | 53 | shoot: (function () { 54 | var direction = new THREE.Vector3(); 55 | var position = new THREE.Vector3(); 56 | var quaternion = new THREE.Quaternion(); 57 | var scale = new THREE.Vector3(); 58 | var translation = new THREE.Vector3(); 59 | var incVive = new THREE.Vector3(0.0, -0.23, -0.15); 60 | var incOculus = new THREE.Vector3(0, -0.23, -0.8); 61 | var inc = new THREE.Vector3(); 62 | 63 | return function () { 64 | var bulletEntity; 65 | var el = this.el; 66 | var data = this.data; 67 | var matrixWorld; 68 | var self = this; 69 | var weapon = this.weapon; 70 | 71 | if (this.coolingDown) { return; } 72 | 73 | ABLAST.currentScore.shoots++; 74 | 75 | // Get firing entity's transformations. 76 | el.object3D.updateMatrixWorld(); 77 | matrixWorld = el.object3D.matrixWorld; 78 | position.setFromMatrixPosition(matrixWorld); 79 | matrixWorld.decompose(translation, quaternion, scale); 80 | 81 | // Set projectile direction. 82 | direction.set(data.direction.x, data.direction.y, data.direction.z); 83 | direction.applyQuaternion(quaternion); 84 | direction.normalize(); 85 | 86 | if (el.components['weapon']) { 87 | inc.copy(el.components.weapon.controllerModel === 'oculus-touch-controller' ? incOculus : incVive); 88 | } 89 | inc.applyQuaternion(quaternion); 90 | position.add(inc); 91 | 92 | // Ask system for bullet and set bullet position to starting point. 93 | bulletEntity = el.sceneEl.systems.bullet.getBullet(weapon.bullet); 94 | bulletEntity.setAttribute('position', position); 95 | bulletEntity.setAttribute('bullet', { 96 | direction: direction.clone(), 97 | position: position.clone(), 98 | owner: 'player' 99 | }); 100 | bulletEntity.setAttribute('visible', true); 101 | bulletEntity.setAttribute('position', position); 102 | bulletEntity.play(); 103 | 104 | // Communicate the shoot. 105 | el.emit('shoot', bulletEntity); 106 | 107 | // Set cooldown period. 108 | this.coolingDown = true; 109 | setTimeout(function () { 110 | self.coolingDown = false; 111 | }, weapon.shootingDelay); 112 | }; 113 | })() 114 | }); 115 | -------------------------------------------------------------------------------- /src/components/headset.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME THREE */ 2 | AFRAME.registerComponent('headset', { 3 | schema: { 4 | on: { default: 'click' } 5 | }, 6 | 7 | init: function () { 8 | }, 9 | 10 | tick: function (time, delta) { 11 | var mesh = this.el.getObject3D('mesh'); 12 | if (mesh) { 13 | mesh.update(delta / 1000); 14 | } 15 | this.updatePose(); 16 | this.updateButtons(); 17 | }, 18 | 19 | updatePose: (function () { 20 | var controllerEuler = new THREE.Euler(); 21 | var controllerPosition = new THREE.Vector3(); 22 | var controllerQuaternion = new THREE.Quaternion(); 23 | var dolly = new THREE.Object3D(); 24 | var standingMatrix = new THREE.Matrix4(); 25 | controllerEuler.order = 'YXZ'; 26 | return function () { 27 | var controller; 28 | var pose; 29 | var orientation; 30 | var position; 31 | var el = this.el; 32 | var vrDisplay = this.system.vrDisplay; 33 | this.update(); 34 | controller = this.controller; 35 | if (!controller) { return; } 36 | pose = controller.pose; 37 | orientation = pose.orientation || [0, 0, 0, 1]; 38 | position = pose.position || [0, 0, 0]; 39 | controllerQuaternion.fromArray(orientation); 40 | dolly.quaternion.fromArray(orientation); 41 | dolly.position.fromArray(position); 42 | dolly.updateMatrix(); 43 | if (vrDisplay && vrDisplay.stageParameters) { 44 | standingMatrix.fromArray(vrDisplay.stageParameters.sittingToStandingTransform); 45 | dolly.applyMatrix(standingMatrix); 46 | } 47 | controllerEuler.setFromRotationMatrix(dolly.matrix); 48 | controllerPosition.setFromMatrixPosition(dolly.matrix); 49 | el.setAttribute('rotation', { 50 | x: THREE.Math.radToDeg(controllerEuler.x), 51 | y: THREE.Math.radToDeg(controllerEuler.y), 52 | z: THREE.Math.radToDeg(controllerEuler.z) 53 | }); 54 | el.setAttribute('position', { 55 | x: controllerPosition.x, 56 | y: controllerPosition.y, 57 | z: controllerPosition.z 58 | }); 59 | }; 60 | })() 61 | }); 62 | -------------------------------------------------------------------------------- /src/components/highscores.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME ABLAST THREE */ 2 | AFRAME.registerSystem('highscores', { 3 | schema: { 4 | maxScores: {default: 10}, 5 | }, 6 | 7 | init: function () { 8 | if (!this.isHighScore()) { 9 | console.warn('Highscore can\'t be loaded or saved as no localStorage support found!'); 10 | return; 11 | } 12 | 13 | this.scores = []; 14 | if (localStorage['highscores']) { 15 | this.scores = JSON.parse(localStorage['highscores']); 16 | } else { 17 | for (var i = 0; i < 5; i++) { 18 | this.scores[i] = { 19 | name: 'nobody', 20 | points: i*10, 21 | shoots: 0, 22 | time: 0, 23 | validShoot: 0 24 | }; 25 | } 26 | } 27 | 28 | var self = this; 29 | var ablastUI = document.getElementById('ablast-ui'); 30 | document.getElementById('save-score').addEventListener('click', function (event) { 31 | ABLAST.currentScore.name = document.getElementById('player-name').value; 32 | self.addNewScore(ABLAST.currentScore); 33 | ablastUI.style.display = 'none'; 34 | }); 35 | 36 | this.sceneEl.addEventListener('gamestate-changed', function (evt) { 37 | if ('state' in evt.detail.diff) { 38 | if (evt.detail.state.state === 'STATE_GAME_OVER' || evt.detail.state.state === 'STATE_GAME_WIN') { 39 | ablastUI.style.display = self.isHighScore(ABLAST.currentScore) ? 'block' : 'none'; 40 | } 41 | } 42 | }); 43 | }, 44 | 45 | isHighScore: function () 46 | { 47 | try { 48 | return 'localStorage' in window && window['localStorage'] !== null; 49 | } catch (e) { 50 | return false; 51 | } 52 | }, 53 | 54 | shouldStoreScore: function (data) { 55 | return (this.scores.length < this.data.maxScores || 56 | this.scores[this.scores.length - 1].points < data.points); 57 | }, 58 | 59 | addNewScore: function (data) { 60 | // Check if we need to insert it 61 | if (this.shouldStoreScore(data)) { 62 | this.scores.push(data); 63 | 64 | this.scores.sort(function(a,b) { 65 | return parseInt(a.points) <= parseInt(b.points); 66 | }); 67 | 68 | if (this.scores.length > this.data.maxScores) { 69 | this.scores.pop(); 70 | } 71 | 72 | localStorage['highscores'] = JSON.stringify(this.scores); 73 | this.sceneEl.emit('highscores-updated', this.scores); 74 | return true; 75 | } 76 | return false; 77 | } 78 | }); 79 | 80 | AFRAME.registerComponent('highscores', { 81 | schema: {}, 82 | 83 | init: function () { 84 | var el = this.el; 85 | var self = this; 86 | var sceneEl = this.el.sceneEl; 87 | 88 | sceneEl.addEventListener('highscores-updated', function (event) { 89 | el.setAttribute('bmfont-text', {text: buildText(self.system.scores)}); 90 | }); 91 | 92 | el.setAttribute('bmfont-text', { 93 | fntImage: 'assets/fonts/mozillavr.png', 94 | fnt: 'assets/fonts/mozillavr.fnt', 95 | scale: 0.0015, 96 | baseline: 'top', 97 | lineHeight: 90, 98 | text: buildText(this.system.scores), 99 | color: '#24caff' 100 | }); 101 | } 102 | }); 103 | 104 | function buildText (scores) { 105 | var text = ''; 106 | scores.sort(function(a,b) { 107 | return parseInt(a.points) <= parseInt(b.points); 108 | }).forEach(function appendText (score) { 109 | name = score.name.toLowerCase(); 110 | var score = score.points.toString(); 111 | for (var i = 10; i <= 100; i *= 10) { 112 | if (score < i) score = '0' + score; 113 | } 114 | text += score.pad(7) + name + '\n'; 115 | }); 116 | return text; 117 | } 118 | -------------------------------------------------------------------------------- /src/components/json-model.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME THREE */ 2 | AFRAME.registerComponent('json-model', { 3 | schema: { 4 | src: {type: 'asset'}, 5 | singleModel: {default: false}, 6 | texturePath: {type: 'asset', default: ''}, 7 | debugNormals: {default: false}, 8 | debugNormalsLength: {default: 0.2}, 9 | debugBones: {default: false} 10 | }, 11 | 12 | init: function () { 13 | }, 14 | 15 | fixNormal: function (vector) { 16 | var t = vector.y; 17 | vector.y = -vector.z; 18 | vector.z = t; 19 | }, 20 | 21 | update: function (oldData) { 22 | this.loader = null; 23 | this.helpers = new THREE.Group(); 24 | this.mixers = []; 25 | this.animationNames = []; 26 | this.skeletonHelper = null; 27 | 28 | var src = this.data.src; 29 | if (!src || src === oldData.src) { return; } 30 | 31 | if (this.data.singleModel) { 32 | this.loader = new THREE.JSONLoader(); 33 | this.loader.setTexturePath(this.data.texturePath); 34 | this.loader.load(src, this.onModelLoaded.bind(this)); 35 | } 36 | else { 37 | this.loader = new THREE.ObjectLoader(); 38 | this.loader.setCrossOrigin(''); 39 | this.loader.load(src, this.onSceneLoaded.bind(this)); 40 | } 41 | }, 42 | 43 | onModelLoaded: function(geometry, materials) { 44 | this.helpers = new THREE.Group(); 45 | 46 | var mesh = new THREE.SkinnedMesh(geometry, materials[0]); 47 | var self = this; 48 | mesh.geometry.faces.forEach(function(face) { 49 | face.vertexNormals.forEach(function(vertex) { 50 | if (!vertex.hasOwnProperty('fixed')) { 51 | self.fixNormal(vertex); 52 | vertex.fixed = true; 53 | } 54 | }); 55 | }); 56 | 57 | if (mesh.geometry['animations'] !== undefined && mesh.geometry.animations.length > 0){ 58 | mesh.material.skinning = true; 59 | var mixer = {mixer: new THREE.AnimationMixer(mesh), clips: {}}; 60 | for (var i in mesh.geometry.animations) { 61 | var anim = mesh.geometry.animations[i]; 62 | var clip = mixer.mixer.clipAction(anim).stop(); 63 | clip.setEffectiveWeight(1); 64 | mixer.clips[anim.name] = clip; 65 | } 66 | this.mixers.push(mixer); 67 | } 68 | 69 | self.addNormalHelpers(mesh); 70 | 71 | this.helpers.visible = this.data.debugNormals; 72 | this.el.setObject3D('helpers', this.helpers); 73 | 74 | this.skeletonHelper = new THREE.SkeletonHelper( mesh ); 75 | this.skeletonHelper.material.linewidth = 2; 76 | this.el.setObject3D('skelhelper', this.skeletonHelper ); 77 | this.skeletonHelper.visible = this.data.debugBones; 78 | 79 | this.el.setObject3D('mesh', mesh); 80 | this.el.emit('model-loaded', {format: 'json', model: mesh, src: this.data.src}); 81 | }, 82 | 83 | onSceneLoaded: function(group) { 84 | this.helpers = new THREE.Group(); 85 | 86 | if (group['animations'] !== undefined) { 87 | var mixer = {mixer: new THREE.AnimationMixer(group), clips: {}}; 88 | for (var i in group.animations) { 89 | var anim = group.animations[i]; 90 | var clip = mixer.mixer.clipAction(anim).stop(); 91 | mixer.clips[anim.name] = clip; 92 | } 93 | this.mixers.push(mixer); 94 | } 95 | var self = this; 96 | group.traverse(function (child) { 97 | if (!(child instanceof THREE.Mesh)) { return; } 98 | 99 | child.geometry.faces.forEach(function(face) { 100 | face.vertexNormals.forEach(function(vertex) { 101 | if (!vertex.hasOwnProperty('fixed')) { 102 | self.fixNormal(vertex); 103 | vertex.fixed = true; 104 | } 105 | }); 106 | }); 107 | 108 | self.addNormalHelpers(child); 109 | }); 110 | 111 | this.helpers.visible = this.data.debugNormals; 112 | this.el.setObject3D('helpers', this.helpers); 113 | this.el.setObject3D('mesh', group); 114 | this.el.emit('model-loaded', {format: 'json', model: group, src: this.data.src}); 115 | }, 116 | 117 | addNormalHelpers: function (mesh) { 118 | var fnh = new THREE.FaceNormalsHelper(mesh, this.data.debugNormalsLength); 119 | this.helpers.add(fnh); 120 | var vnh = new THREE.VertexNormalsHelper(mesh, this.data.debugNormalsLength); 121 | this.helpers.add(vnh); 122 | 123 | mesh.geometry.normalsNeedUpdate = true; 124 | mesh.geometry.verticesNeedUpdate = true; 125 | }, 126 | 127 | playAnimation: function (animationName, repeat) { 128 | for (var i in this.mixers) { 129 | var clip = this.mixers[i].clips[animationName]; 130 | if (clip === undefined) continue; 131 | clip.stop().play(); 132 | var repetitions = 0; 133 | if (repeat === true) repetitions = Infinity; 134 | else if (repeat == undefined) repeat = false; 135 | else if (typeof(repeat) == 'number') { 136 | if (repeat === 0) repeat = false; 137 | repetitions = repeat; 138 | } 139 | else repeat = false; 140 | clip.setLoop( repeat ? THREE.LoopRepeat : THREE.LoopOnce, repetitions ); 141 | } 142 | }, 143 | 144 | stopAnimation: function () { 145 | for (var i in this.mixers) { 146 | for (var j in this.mixers[i].clips) { 147 | this.mixers[i].clips[j].stop(); 148 | } 149 | } 150 | }, 151 | 152 | tick: function(time, timeDelta) { 153 | for (var i in this.mixers) { 154 | this.mixers[i].mixer.update( timeDelta / 1000 ); 155 | } 156 | } 157 | }); 158 | -------------------------------------------------------------------------------- /src/components/lifes-counter.js: -------------------------------------------------------------------------------- 1 | /* global THREE AFRAME */ 2 | var LetterPanel = require('../lib/letterpanel'); 3 | 4 | AFRAME.registerComponent('lifes-counter', { 5 | schema: { 6 | width: {default: 0.9}, 7 | value: {default: ' '}, 8 | numSegments: {default: 6}, 9 | height: {default: 0.2}, 10 | color: {default: 0xff0000} 11 | }, 12 | 13 | init: function () { 14 | this.letterPanel = new LetterPanel(this.el.sceneEl.systems.material, this.data); 15 | this.el.setObject3D('mesh', this.letterPanel.group); 16 | }, 17 | 18 | update: function () { 19 | var value = this.data.value; 20 | var computed = [0,0,0,0,0].map(function(e,i) {return i >= value ? ' ': '*'}).reverse().join(''); 21 | this.letterPanel.update(computed); 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /src/components/points-counter.js: -------------------------------------------------------------------------------- 1 | /* global THREE AFRAME */ 2 | var LetterPanel = require('../lib/letterpanel'); 3 | 4 | AFRAME.registerComponent('points-counter', { 5 | schema: { 6 | value: {default: 0}, 7 | height: {default: 0.7}, 8 | numSegments: {default: 3}, 9 | width: {default: 0.9}, 10 | color: {default: 0x024caff} 11 | }, 12 | 13 | init: function () { 14 | this.letterPanel = new LetterPanel(this.el.sceneEl.systems.material, this.data); 15 | this.el.setObject3D('mesh', this.letterPanel.group); 16 | 17 | this.el.sceneEl.addEventListener('enemy-death', function () { 18 | 19 | }); 20 | }, 21 | 22 | update: function () { 23 | this.letterPanel.update(this.data.value.padLeft(3)); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /src/components/proxy_event.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('proxy_event', { 2 | schema: { 3 | event: { default: '' }, 4 | dst: { type: 'selector' }, 5 | bubbles: { default: false } 6 | }, 7 | 8 | init: function () { 9 | this.el.sceneEl.addEventListener(this.data.event, function (event) { 10 | this.data.dst.emit(this.data.event, event, this.data.bubbles); 11 | }.bind(this)); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/restrict-position.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('restrict-position', { 2 | schema: { 3 | }, 4 | 5 | init: function () { 6 | this.active = !AFRAME.utils.device.checkHeadsetConnected(); 7 | this.radius = 2; 8 | }, 9 | 10 | tick: function (time, delta) { 11 | if (!this.active) { return; } 12 | var fromCircleToObject = new THREE.Vector3(); 13 | var y = this.el.object3D.position.y; 14 | fromCircleToObject.copy(this.el.object3D.position); 15 | var len = this.radius / fromCircleToObject.length(); 16 | if (len < 0.98) { 17 | fromCircleToObject.multiplyScalar(this.radius / fromCircleToObject.length()); 18 | this.el.setAttribute('position', { 19 | x: fromCircleToObject.x, 20 | y: y, 21 | z: fromCircleToObject.z 22 | }); 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /src/components/shoot-controls.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | AFRAME.registerComponent('shoot-controls', { 3 | // dependencies: ['tracked-controls'], 4 | schema: { 5 | hand: { default: 'left' } 6 | }, 7 | 8 | init: function () { 9 | var self = this; 10 | 11 | this.onButtonChanged = this.onButtonChanged.bind(this); 12 | this.onButtonDown = function (evt) { self.onButtonEvent(evt.detail.id, 'down'); }; 13 | this.onButtonUp = function (evt) { self.onButtonEvent(evt.detail.id, 'up'); }; 14 | }, 15 | 16 | play: function () { 17 | var el = this.el; 18 | el.addEventListener('buttonchanged', this.onButtonChanged); 19 | el.addEventListener('buttondown', this.onButtonDown); 20 | el.addEventListener('buttonup', this.onButtonUp); 21 | }, 22 | 23 | pause: function () { 24 | var el = this.el; 25 | el.removeEventListener('buttonchanged', this.onButtonChanged); 26 | el.removeEventListener('buttondown', this.onButtonDown); 27 | el.removeEventListener('buttonup', this.onButtonUp); 28 | }, 29 | 30 | // buttonId 31 | // 0 - trackpad 32 | // 1 - trigger ( intensity value from 0.5 to 1 ) 33 | // 2 - grip 34 | // 3 - menu ( dispatch but better for menu options ) 35 | // 4 - system ( never dispatched on this layer ) 36 | mapping: { 37 | axis0: 'trackpad', 38 | axis1: 'trackpad', 39 | button0: 'trackpad', 40 | button1: 'trigger', 41 | button2: 'grip', 42 | button3: 'menu', 43 | button4: 'system' 44 | }, 45 | 46 | onButtonChanged: function (evt) { 47 | var buttonName = this.mapping['button' + evt.detail.id]; 48 | if (buttonName !== 'trigger') { return; } 49 | var value = evt.detail.state.value; 50 | this.el.components['weapon'].setTriggerPressure(value); 51 | }, 52 | 53 | onButtonEvent: function (id, evtName) { 54 | var buttonName = this.mapping['button' + id]; 55 | this.el.emit(buttonName + evtName); 56 | }, 57 | 58 | update: function () { 59 | var data = this.data; 60 | var el = this.el; 61 | el.setAttribute('vive-controls', {hand: data.hand, model: false}); 62 | el.setAttribute('oculus-touch-controls', {hand: data.hand, model: false}); 63 | el.setAttribute('windows-motion-controls', {hand: data.hand, model: false}); 64 | if (data.hand === 'right') { 65 | el.setAttribute('daydream-controls', {hand: data.hand, model: false}); 66 | el.setAttribute('gearvr-controls', {hand: data.hand, model: false}); 67 | } 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /src/components/sound-fade.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME ABLAST */ 2 | 3 | AFRAME.registerComponent('sound-fade', { 4 | schema: { 5 | from: {default: 0.0}, 6 | to: {default: 1.0}, 7 | duration: {default: 1000}, 8 | }, 9 | 10 | init: function () { 11 | if (this.el.getAttribute('sound')) { 12 | this.el.setAttribute('sound', 'volume', this.data.from); 13 | this.fadeEnded = false; 14 | this.diff = this.data.to - this.data.from; 15 | } 16 | else { 17 | this.fadeEnded = true; 18 | } 19 | }, 20 | 21 | update: function (oldData) { 22 | this.endTime = undefined; 23 | this.fadeEnded = false; 24 | this.diff = this.data.to - this.data.from; 25 | }, 26 | 27 | tick: function (time, delta) { 28 | if (this.fadeEnded) { 29 | return; 30 | } 31 | if (this.endTime === undefined) { 32 | this.endTime = time + this.data.duration; 33 | return; 34 | } 35 | 36 | var ease = 1 - (this.endTime - time) / this.data.duration; 37 | ease = Math.max(0, Math.min(1, ease * ease)); //easeQuadIn 38 | var vol = this.data.from + this.diff * ease; 39 | this.el.setAttribute('sound', 'volume', vol); 40 | if (ease === 1) { 41 | this.fadeEnded = true; 42 | } 43 | } 44 | }); -------------------------------------------------------------------------------- /src/components/spline-line.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME, THREE */ 2 | 3 | /** 4 | * Draw spline. 5 | * Grab the spline object using `pointer`, which reaches into a component for the spline. 6 | * Some extra code done to generalize + decouple the component. 7 | */ 8 | AFRAME.registerComponent('spline-line', { 9 | schema: { 10 | pointer: {default: ''}, // `[componentName].[member]`. 11 | numPoints: {default: 250} 12 | }, 13 | 14 | init: function () { 15 | var componentName; 16 | var data = this.data; 17 | var el = this.el; 18 | var self = this; 19 | var spline; 20 | 21 | this.pointMeshes = []; 22 | 23 | // TODO: Get `component-initialized` event. 24 | spline = getSpline(); 25 | if (spline) { 26 | this.drawLine(spline); 27 | } else { 28 | componentName = data.pointer.split('.')[0]; 29 | el.addEventListener('componentchanged', function (evt) { 30 | if (evt.detail.name !== componentName) { return; } 31 | self.drawLine(getSpline()); 32 | }); 33 | } 34 | 35 | function getSpline () { 36 | var split = data.pointer.split('.'); 37 | var componentName = split.shift(); 38 | var member = el.components[componentName]; 39 | while (split.length) { 40 | if (!member) { return; } 41 | member = member[split.shift()]; 42 | } 43 | return member; 44 | } 45 | }, 46 | 47 | drawLine: function (spline) { 48 | var data = this.data; 49 | var el = this.el; 50 | var geometry; 51 | var i; 52 | var pointMeshes = this.pointMeshes; 53 | var material; 54 | 55 | // Create line. 56 | geometry = new THREE.Geometry(); 57 | material = new THREE.LineBasicMaterial({ 58 | color: new THREE.Color(Math.random(), Math.random(), Math.random()) 59 | }); 60 | for (i = 0; i < data.numPoints; i++) { 61 | var point = spline.getPoint(i / data.numPoints); 62 | geometry.vertices.push(new THREE.Vector3(point.x, point.y, point.z)); 63 | } 64 | geometry.verticesNeedsUpdate = true; 65 | 66 | // Draw points. 67 | spline.points.forEach(function addWaypoint (point) { 68 | var geometry = new THREE.SphereGeometry(0.2, 16, 16); 69 | var material = new THREE.MeshBasicMaterial({color: '#111'}); 70 | var mesh = new THREE.Mesh(geometry, material); 71 | mesh.position.set(point.x, point.y, point.z); 72 | pointMeshes.push(mesh); 73 | el.sceneEl.object3D.add(mesh); 74 | }); 75 | 76 | // Append line to scene. 77 | this.line = new THREE.Line(geometry, material); 78 | el.sceneEl.object3D.add(this.line); 79 | }, 80 | 81 | remove: function () { 82 | var scene = this.el.sceneEl.object3D; 83 | if (!this.line) { return; } 84 | scene.remove(this.line); 85 | this.pointMeshes.forEach(function (point) { 86 | scene.remove(point); 87 | }); 88 | } 89 | }); 90 | -------------------------------------------------------------------------------- /src/components/timer-counter.js: -------------------------------------------------------------------------------- 1 | var LetterPanel = require('../lib/letterpanel'); 2 | 3 | /* global THREE AFRAME */ 4 | AFRAME.registerComponent('timer-counter', { 5 | schema: { 6 | width: {default: 0.9}, 7 | value: {default: ''}, 8 | numSegments: {default: 5}, 9 | height: {default: 0.35}, 10 | color: {default: 0x024caff} 11 | }, 12 | 13 | init: function () { 14 | this.letterPanel = new LetterPanel(this.el.sceneEl.systems.material, this.data); 15 | this.el.setObject3D('mesh', this.letterPanel.group); 16 | var self = this; 17 | this.el.sceneEl.addEventListener('countdown-update', function(event) { 18 | var t = event.detail; 19 | var value = t.minutes.padLeft(2) + ':' + t.seconds.padLeft(2); 20 | self.letterPanel.material.color.set(t.total <= 10000 ? 0xff0000 : 0x24caff); 21 | self.el.setAttribute('timer-counter', {value: value}); 22 | }); 23 | }, 24 | 25 | update: function () { 26 | this.letterPanel.update(this.data.value); 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /src/components/vr-analytics.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('vr-analytics', { 2 | init: function () { 3 | var el = this.el; 4 | var emitted = false; 5 | 6 | el.addEventListener('enter-vr', function () { 7 | if (emitted || !AFRAME.utils.device.checkHeadsetConnected() || 8 | AFRAME.utils.device.isMobile()) { return; } 9 | ga('send', 'event', 'General', 'entervr'); 10 | emitted = true; 11 | }); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/weapon.js: -------------------------------------------------------------------------------- 1 | // Weapon definitions. 2 | var WEAPONS = { 3 | default: { 4 | model: { 5 | url: 'url(assets/models/gun.json)', 6 | positionOffset: [0, 0, 0], 7 | rotationOffset: [0, 0, 0] 8 | }, 9 | shootSound: 'url(assets/sounds/gun.ogg)', 10 | shootingDelay: 100, // In ms 11 | bullet: 'default' 12 | } 13 | }; 14 | 15 | 16 | /** 17 | * Tracked controls, gun model, firing animation, shooting effects. 18 | */ 19 | AFRAME.registerComponent('weapon', { 20 | dependencies: ['shoot-controls'], 21 | 22 | schema: { 23 | enabled: { default: true }, 24 | type: { default: 'default' } 25 | }, 26 | 27 | updateWeapon: function () { 28 | console.log(this.controllerModel); 29 | if (this.controllerModel === 'oculus-touch-controller') { 30 | this.model.applyMatrix(new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), 0.8)); 31 | this.el.setAttribute('shoot', {direction: '0 -0.3 -1'}); 32 | } else if (this.controllerModel === 'daydream-controls') { 33 | document.getElementById('rightHandPivot').setAttribute('position', '-0.2 0 -0.5'); 34 | this.el.setAttribute('shoot', {on: 'trackpaddown'}); 35 | } 36 | }, 37 | init: function () { 38 | var el = this.el; 39 | var self = this; 40 | 41 | this.model = null; 42 | this.isGamepadConnected = false; 43 | this.controllerModel = null; 44 | this.weapon = WEAPONS[ this.data.type ]; 45 | 46 | el.setAttribute('json-model', {src: this.weapon.model.url}); 47 | 48 | el.setAttribute('sound', { 49 | src: this.weapon.shootSound, 50 | on: 'shoot', 51 | volume: 0.5, 52 | poolSize: 10 53 | }); 54 | 55 | this.fires = []; 56 | this.trigger = null; 57 | 58 | el.addEventListener('controllerconnected', function (evt) { 59 | console.log(evt); 60 | self.controllerModel = evt.detail.name; 61 | if (self.model == null) { 62 | self.isGamepadConnected = true; 63 | } else { 64 | self.updateWeapon(); 65 | } 66 | }); 67 | 68 | el.addEventListener('model-loaded', function (evt) { 69 | this.model = evt.detail.model; 70 | var modelWithPivot = new THREE.Group(); 71 | modelWithPivot.add(this.model); 72 | el.setObject3D('mesh', modelWithPivot); 73 | 74 | for (var i = 0; i < 3; i++){ 75 | var fire = this.model.getObjectByName('fire'+i); 76 | if (fire) { 77 | fire.material.depthWrite = false; 78 | fire.visible = false; 79 | this.fires.push(fire); 80 | } 81 | } 82 | 83 | if (this.isGamepadConnected) { 84 | this.updateWeapon(); 85 | } 86 | 87 | this.trigger = this.model.getObjectByName('trigger'); 88 | 89 | }.bind(this)); 90 | 91 | var self = this; 92 | el.addEventListener('shoot', function (evt) { 93 | el.components['json-model'].playAnimation('default'); 94 | self.light.components.light.light.intensity = self.lightIntensity; 95 | for (var i in self.fires){ 96 | self.fires[i].visible = true; 97 | self.fires[i].life = 50 + Math.random() * 100; 98 | } 99 | }); 100 | 101 | this.lightIntensity = 3.0; 102 | this.life = this.data.lifespan; 103 | this.canShoot = true; 104 | 105 | this.light = document.createElement('a-entity'); 106 | el.appendChild(this.light); 107 | 108 | this.light.setAttribute('light', {color: '#24CAFF', intensity: 0.0, type: 'point'}); 109 | this.light.setAttribute('position', {x: 0, y: -0.22, z: -0.14}); 110 | var self = this; 111 | this.light.addEventListener('loaded', function () { 112 | self.lightObj = self.light.components.light.light; // threejs light 113 | }) 114 | }, 115 | 116 | tick: function (time, delta) { 117 | if (this.lightObj && this.lightObj.intensity > 0.0) { 118 | this.light.visible = true; 119 | this.lightObj.intensity -= delta / 1000 * 10; 120 | if (this.lightObj.intensity < 0.0) { 121 | this.lightObj.intensity = 0.0; 122 | this.light.visible = false; 123 | } 124 | for (var i in this.fires) { 125 | if (!this.fires[i].visible) continue; 126 | this.fires[i].life -= delta; 127 | if (i == 0) { 128 | this.fires[i].rotation.set(0, Math.random() * Math.PI * 2, 0); 129 | } 130 | else { 131 | this.fires[i].rotation.set(0, Math.random() * 1 - 0.5 + (Math.random() > 0.5 ? Math.PI: 0) , 0); 132 | } 133 | if (this.fires[i].life < 0){ 134 | this.fires[i].visible = false; 135 | } 136 | } 137 | } 138 | }, 139 | 140 | update: function () { 141 | var data = this.data; 142 | this.weapon = WEAPONS[ data.type ]; 143 | }, 144 | 145 | setTriggerPressure: function (pressure) { 146 | if (this.trigger) { 147 | this.trigger.position.setY(pressure * 0.01814); 148 | } 149 | } 150 | }); 151 | 152 | module.exports = WEAPONS; 153 | -------------------------------------------------------------------------------- /src/enemies/enemy0.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | ABLAST.registerEnemy( 3 | // name 4 | 'enemy0', 5 | // data 6 | { 7 | components: { 8 | enemy: { 9 | name: 'enemy0', 10 | bulletName: 'enemy-slow', 11 | color: '#FFB911', 12 | scale: 0.9, 13 | health: 1 14 | }, 15 | 'collision-helper': { 16 | debug: false, 17 | radius: 0.4 18 | }, 19 | 'json-model': { 20 | src: '#enemy0', 21 | texturePath: 'url(assets/images/)', 22 | singleModel: true 23 | } 24 | }, 25 | poolSize: 10 26 | }, 27 | // implementation 28 | { 29 | init: function () { 30 | this.shootingDelay = 3000; 31 | this.warmUpTime = 1000; 32 | this.reset(); 33 | }, 34 | reset: function () { 35 | var el = this.el; 36 | var sc = this.data.scale; 37 | this.actualShootingDelay = this.shootingDelay + Math.floor(this.shootingDelay * Math.random()); 38 | 39 | el.addEventListener('model-loaded', function(event) { 40 | el.getObject3D('mesh').scale.set(sc, sc, sc); 41 | }); 42 | this.lastShoot = undefined; 43 | this.willShootEmited = false; 44 | }, 45 | tick: function (time, delta) { 46 | if (this.lastShoot == undefined ) { 47 | this.lastShoot = time; 48 | } 49 | else if (time - this.lastShoot > this.actualShootingDelay) { 50 | // don't shoot when behind the player 51 | var pos = this.el.getAttribute('position'); 52 | if (pos.z < 0 && pos.y > 0) { 53 | this.el.components.enemy.shoot(time, delta); 54 | this.lastShoot = time; 55 | this.willShootEmited = false; 56 | this.actualShootingDelay = this.shootingDelay * (Math.random() < 0.7 ? 2 : 1); 57 | } 58 | } 59 | else if (!this.willShootEmited && time - this.lastShoot > this.actualShootingDelay - this.warmUpTime) { 60 | this.el.components.enemy.willShoot(time, delta, this.warmUpTime); 61 | this.willShootEmited = true; 62 | } 63 | }, 64 | onHit: function (type) {} 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /src/enemies/enemy1.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | ABLAST.registerEnemy( 3 | // name 4 | 'enemy1', 5 | // data 6 | { 7 | components: { 8 | enemy: { 9 | name: 'enemy1', 10 | bulletName: 'enemy-fast', 11 | color: '#FF7D00', 12 | scale: 0.6, 13 | health: 1 14 | }, 15 | 'collision-helper': { 16 | debug: false, 17 | radius: 0.3 18 | }, 19 | 'json-model': { 20 | src: '#enemy1', 21 | texturePath: 'url(assets/images/)', 22 | singleModel: true 23 | } 24 | }, 25 | poolSize: 10 26 | }, 27 | // implementation 28 | { 29 | init: function () { 30 | this.shootingDelay = 5000; 31 | this.warmUpTime = 1000; 32 | this.reset(); 33 | }, 34 | reset: function () { 35 | var el = this.el; 36 | var sc = this.data.scale; 37 | this.actualShootingDelay = this.shootingDelay + Math.floor(this.shootingDelay * Math.random()); 38 | 39 | el.addEventListener('model-loaded', function(event) { 40 | el.getObject3D('mesh').scale.set(sc, sc, sc); 41 | }); 42 | this.lastShoot = undefined; 43 | this.willShootEmited = false; 44 | }, 45 | tick: function (time, delta) { 46 | if (this.lastShoot == undefined ) { 47 | this.lastShoot = time; 48 | } 49 | else if (time - this.lastShoot > this.actualShootingDelay) { 50 | // don't shoot when behind the player 51 | var pos = this.el.getAttribute('position'); 52 | if (pos.z < 0 && pos.y > 0) { 53 | this.el.components.enemy.shoot(time, delta); 54 | this.lastShoot = time; 55 | this.willShootEmited = false; 56 | this.actualShootingDelay = this.shootingDelay * (Math.random() < 0.5 ? 2 : 1); 57 | } 58 | } 59 | else if (!this.willShootEmited && time - this.lastShoot > this.actualShootingDelay - this.warmUpTime) { 60 | this.el.components.enemy.willShoot(time, delta, this.warmUpTime); 61 | this.willShootEmited = true; 62 | } 63 | }, 64 | onHit: function (type) {} 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /src/enemies/enemy2.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | ABLAST.registerEnemy( 3 | // name 4 | 'enemy2', 5 | // data 6 | { 7 | components: { 8 | enemy: { 9 | name: 'enemy2', 10 | bulletName: 'enemy-medium', 11 | color: '#FF5533', 12 | scale: 1.3, 13 | health: 3 14 | }, 15 | 'collision-helper': { 16 | debug: false, 17 | radius: 0.6 18 | }, 19 | 'json-model': { 20 | src: '#enemy2', 21 | texturePath: 'url(assets/images/)', 22 | singleModel: true 23 | } 24 | }, 25 | poolSize: 10 26 | }, 27 | // implementation 28 | { 29 | init: function () { 30 | this.shootingDelay = 2000; 31 | this.warmUpTime = 800; 32 | this.reset(); 33 | }, 34 | reset: function () { 35 | var el = this.el; 36 | var sc = this.data.scale; 37 | this.actualShootingDelay = this.shootingDelay + Math.floor(this.shootingDelay * Math.random()); 38 | 39 | el.addEventListener('model-loaded', function(event) { 40 | el.getObject3D('mesh').scale.set(sc, sc, sc); 41 | }); 42 | this.lastShoot = undefined; 43 | this.willShootEmited = false; 44 | }, 45 | tick: function (time, delta) { 46 | if (this.lastShoot == undefined ) { 47 | this.lastShoot = time; 48 | } 49 | else if (time - this.lastShoot > this.actualShootingDelay) { 50 | // don't shoot when behind the player 51 | var pos = this.el.getAttribute('position'); 52 | if (pos.z < 0 && pos.y > 0) { 53 | this.el.components.enemy.shoot(time, delta); 54 | this.lastShoot = time; 55 | this.willShootEmited = false; 56 | this.actualShootingDelay = this.shootingDelay * (Math.random() < 0.2 ? 2 : 1); 57 | } 58 | } 59 | else if (!this.willShootEmited && time - this.lastShoot > this.actualShootingDelay - this.warmUpTime) { 60 | this.el.components.enemy.willShoot(time, delta, this.warmUpTime); 61 | this.willShootEmited = true; 62 | } 63 | }, 64 | onHit: function (type) {} 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /src/enemies/enemy3.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | ABLAST.registerEnemy( 3 | // name 4 | 'enemy3', 5 | // data 6 | { 7 | components: { 8 | enemy: { 9 | name: 'enemy3', 10 | bulletName: 'enemy-fat', 11 | color: '#8762FF', 12 | scale: 2.5, 13 | health: 30 14 | }, 15 | 'collision-helper': { 16 | debug: false, 17 | radius: 1.2 18 | }, 19 | 'json-model': { 20 | src: '#enemy3', 21 | texturePath: 'url(assets/images/)', 22 | singleModel: true 23 | } 24 | }, 25 | poolSize: 4 26 | }, 27 | // implementation 28 | { 29 | init: function () { 30 | this.shootingDelay = 800; 31 | this.warmUpTime = 500; 32 | this.reset(); 33 | }, 34 | reset: function () { 35 | var el = this.el; 36 | var sc = this.data.scale; 37 | this.actualShootingDelay = this.shootingDelay + Math.floor(this.shootingDelay * Math.random()); 38 | 39 | el.addEventListener('model-loaded', function(event) { 40 | el.getObject3D('mesh').scale.set(sc, sc, sc); 41 | }); 42 | this.lastShoot = undefined; 43 | this.willShootEmited = false; 44 | }, 45 | tick: function (time, delta) { 46 | if (this.lastShoot == undefined ) { 47 | this.lastShoot = time; 48 | } 49 | else if (time - this.lastShoot > this.actualShootingDelay) { 50 | // don't shoot when behind the player 51 | var pos = this.el.getAttribute('position'); 52 | if (pos.z < 0 && pos.y > 0) { 53 | this.el.components.enemy.shoot(time, delta); 54 | this.lastShoot = time; 55 | this.willShootEmited = false; 56 | this.actualShootingDelay = this.shootingDelay * (Math.random() < 0.3 ? 2 : 1); 57 | } 58 | } 59 | else if (!this.willShootEmited && time - this.lastShoot > this.actualShootingDelay - this.warmUpTime) { 60 | this.el.components.enemy.willShoot(time, delta, this.warmUpTime); 61 | this.willShootEmited = true; 62 | } 63 | }, 64 | onHit: function (type) {} 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /src/enemies/enemy_start.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST */ 2 | 3 | ABLAST.registerEnemy( 4 | // name 5 | 'enemy_start', 6 | // data 7 | { 8 | components: { 9 | enemy: { 10 | name: 'enemy_start', 11 | color: '#FFB911', 12 | scale: 0.9, 13 | health: 1 14 | }, 15 | 'collision-helper': { 16 | debug: false, 17 | radius: 0.4 18 | }, 19 | 'json-model': { 20 | src: 'url(assets/models/enemy0.json)', 21 | texturePath: 'url(assets/images/)', 22 | singleModel: true 23 | } 24 | }, 25 | poolSize: 1 26 | }, 27 | // implementation 28 | { 29 | init: function () { 30 | this.shootingDelay = 3000; 31 | this.warmUpTime = 1000; 32 | this.reset(); 33 | }, 34 | reset: function () { 35 | var el = this.el; 36 | var sc = this.data.scale; 37 | el.addEventListener('model-loaded', function(event) { 38 | el.getObject3D('mesh').scale.set(sc, sc, sc); 39 | }); 40 | this.lastShoot = undefined; 41 | this.willShootEmited = false; 42 | }, 43 | tick: function (time, delta) { 44 | this.el.components.enemy.willShoot(time, delta, this.warmUpTime); 45 | }, 46 | onHit: function (type) {} 47 | } 48 | ); 49 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | window.ABLAST = {}; 2 | 3 | // Assets managment 4 | require('./a-asset-image.js') 5 | 6 | require('./lib/utils.js'); 7 | 8 | // Systems. 9 | require('./systems/bullet.js'); 10 | require('./systems/enemy.js'); 11 | require('./systems/explosion.js'); 12 | 13 | // Bullets. 14 | require('./bullets/player.js'); 15 | require('./bullets/enemy-slow.js'); 16 | require('./bullets/enemy-medium.js'); 17 | require('./bullets/enemy-fast.js'); 18 | require('./bullets/enemy-fat.js'); 19 | 20 | // Enemies. 21 | require('./enemies/enemy_start.js'); 22 | require('./enemies/enemy0.js'); 23 | require('./enemies/enemy1.js'); 24 | require('./enemies/enemy2.js'); 25 | require('./enemies/enemy3.js'); 26 | 27 | // Components 28 | require('./components/highscores.js'); 29 | require('./components/proxy_event.js'); 30 | require('./components/countdown.js'); 31 | require('./components/decals.js'); 32 | require('./components/curve-movement.js'); 33 | require('./components/collision-helper.js'); 34 | require('./components/gamestate.js'); 35 | require('./components/gamestate-debug.js'); 36 | require('./components/shoot-controls.js'); 37 | require('./components/bullet.js'); 38 | require('./components/lifes-counter.js'); 39 | require('./components/points-counter.js'); 40 | require('./components/timer-counter.js'); 41 | require('./components/enemy.js'); 42 | require('./components/weapon.js'); 43 | require('./components/gun.js'); 44 | require('./components/headset.js'); 45 | require('./components/json-model.js'); 46 | require('./components/spline-line.js'); 47 | require('./components/explosion.js'); 48 | require('./components/animate-message.js'); 49 | require('./components/gamestate-visuals.js'); 50 | require('./components/sound-fade.js'); 51 | require('./components/restrict-position.js'); 52 | -------------------------------------------------------------------------------- /src/lib/letterpanel.js: -------------------------------------------------------------------------------- 1 | var fontText = '0123456789:* '; 2 | 3 | function LetterPanel (materialSystem, data) { 4 | 5 | this.width = data.width; 6 | this.height = data.height; 7 | this.numSegments = data.numSegments; 8 | 9 | var src = 'assets/images/font.png'; 10 | 11 | materialSystem.loadTexture(src, {src: src}, setMap.bind(this)); 12 | this.material = new THREE.MeshBasicMaterial({ 13 | side: THREE.DoubleSide, 14 | color: data.color, 15 | transparent: true 16 | }); 17 | 18 | function setMap (texture) { 19 | this.material.map = texture; 20 | this.material.needsUpdate = true; 21 | } 22 | 23 | this.group = new THREE.Group(); 24 | var segmentWidth = this.width / this.numSegments; 25 | this.numLetters = fontText.length; 26 | for (var i = 0; i < this.numSegments; i++) { 27 | plane = new THREE.Mesh(new THREE.PlaneBufferGeometry(segmentWidth, this.height), this.material); 28 | plane.position.x = i * segmentWidth; 29 | this.group.add(plane); 30 | } 31 | } 32 | 33 | LetterPanel.prototype = { 34 | update: function (string) { 35 | string = string.split('').map(function(char){ 36 | return fontText.indexOf(char); 37 | }); 38 | 39 | var inc = 1 / this.numLetters; 40 | for (var i = 0; i < this.numSegments; i++) { 41 | var uv = this.group.children[i].geometry.attributes.uv; 42 | var array = uv.array; 43 | var x1 = string[i] * inc; 44 | var x2 = (string[i] + 1) * inc; 45 | array[0] = x1; 46 | array[4] = x1; 47 | array[2] = x2; 48 | array[6] = x2; 49 | uv.needsUpdate = true; 50 | } 51 | } 52 | } 53 | 54 | module.exports = LetterPanel; 55 | -------------------------------------------------------------------------------- /src/lib/poolhelper.js: -------------------------------------------------------------------------------- 1 | var createMixin = require('./utils').createMixin; 2 | 3 | var PoolHelper = function (groupName, data, sceneEl) { 4 | this.groupName = groupName; 5 | this.sceneEl = sceneEl || document.querySelector('a-scene'); 6 | this.initializePools(groupName, data); 7 | }; 8 | 9 | PoolHelper.prototype = { 10 | initializePools: function (groupName, data) { 11 | var self = this; 12 | Object.keys(data).forEach(function (name) { 13 | var item = data[name]; 14 | var components = item.components; 15 | var mixinName = groupName + name; 16 | createMixin(mixinName, components, self.sceneEl); 17 | 18 | self.sceneEl.setAttribute('pool__' + mixinName, 19 | { 20 | size: item.poolSize, 21 | mixin: mixinName, 22 | dynamic: true 23 | }); 24 | }); 25 | }, 26 | 27 | returnEntity: function (name, entity) { 28 | var mixinName = this.groupName + name; 29 | var poolName = 'pool__' + mixinName; 30 | this.sceneEl.components[poolName].returnEntity(entity); 31 | }, 32 | 33 | requestEntity: function (name) { 34 | var mixinName = this.groupName + name; 35 | var poolName = 'pool__' + mixinName; 36 | var entity = this.sceneEl.components[poolName].requestEntity(); 37 | // entity.id= this.groupName + Math.floor(Math.random() * 1000); 38 | return entity; 39 | } 40 | }; 41 | 42 | module.exports = PoolHelper; 43 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | /* globals AFRAME */ 2 | function createMixin (id, obj, scene) { 3 | var mixinEl = document.createElement('a-mixin'); 4 | mixinEl.setAttribute('id', id); 5 | Object.keys(obj).forEach(function (componentName) { 6 | var value = obj[componentName]; 7 | if (typeof value === 'object') { 8 | value = AFRAME.utils.styleParser.stringify(value); 9 | } 10 | mixinEl.setAttribute(componentName, value); 11 | }); 12 | 13 | var assetsEl = scene ? scene.querySelector('a-assets') : document.querySelector('a-assets'); 14 | if (!assetsEl) { 15 | assetsEl = document.createElement('a-assets'); 16 | scene.appendChild(assetsEl); 17 | } 18 | assetsEl.appendChild(mixinEl); 19 | 20 | return mixinEl; 21 | } 22 | 23 | Number.prototype.padLeft = function (n,str) { 24 | return Array(n-String(this).length+1).join(str||'0')+this; 25 | } 26 | 27 | String.prototype.pad = function (n,left, str) { 28 | var string = String(this).substr(0,n); 29 | var empty = Array(n-string.length+1).join(str||' '); 30 | return left ? empty + this : this + empty; 31 | } 32 | 33 | module.exports = { 34 | createMixin: createMixin 35 | }; 36 | -------------------------------------------------------------------------------- /src/systems/bullet.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME ABLAST */ 2 | var PoolHelper = require('../lib/poolhelper.js'); 3 | 4 | ABLAST.BULLETS = {}; 5 | 6 | ABLAST.registerBullet = function (name, data, definition) { 7 | if (ABLAST.BULLETS[name]) { 8 | throw new Error('The bullet `' + name + '` has been already registered. ' + 9 | 'Check that you are not loading two versions of the same bullet ' + 10 | 'or two different bullets of the same name.'); 11 | } 12 | 13 | ABLAST.BULLETS[name] = { 14 | poolSize: data.poolSize, 15 | components: data.components, 16 | definition: definition 17 | }; 18 | 19 | console.info('Bullet registered ', name); 20 | }; 21 | 22 | AFRAME.registerSystem('bullet', { 23 | init: function () { 24 | var self = this; 25 | this.poolHelper = new PoolHelper('bullet', ABLAST.BULLETS, this.sceneEl); 26 | this.activeBullets = []; 27 | 28 | this.sceneEl.addEventListener('gamestate-changed', function (evt) { 29 | if ('state' in evt.detail.diff) { 30 | if (evt.detail.state.state === 'STATE_GAME_OVER' || evt.detail.state.state === 'STATE_GAME_WIN') { 31 | self.reset(); 32 | } 33 | } 34 | }); 35 | }, 36 | 37 | reset: function (entity) { 38 | var self = this; 39 | this.activeBullets.forEach(function (bullet) { 40 | self.returnBullet(bullet.getAttribute('bullet').name, bullet); 41 | }); 42 | }, 43 | 44 | returnBullet: function (name, entity) { 45 | this.activeBullets.splice(this.activeBullets.indexOf(entity), 1); 46 | this.poolHelper.returnEntity(name, entity); 47 | }, 48 | 49 | getBullet: function (name) { 50 | var self = this; 51 | var bullet = this.poolHelper.requestEntity(name); 52 | this.activeBullets.push(bullet); 53 | return bullet; 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /src/systems/enemy.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST AFRAME */ 2 | var PoolHelper = require('../lib/poolhelper.js'); 3 | 4 | ABLAST.ENEMIES = {}; 5 | 6 | ABLAST.registerEnemy = function (name, data, definition) { 7 | if (ABLAST.ENEMIES[name]) { 8 | throw new Error('The enemy `' + name + '` has been already registered. ' + 9 | 'Check that you are not loading two versions of the same enemy ' + 10 | 'or two different enemies of the same name.'); 11 | } 12 | 13 | ABLAST.ENEMIES[name] = { 14 | poolSize: data.poolSize, 15 | components: data.components, 16 | definition: definition, 17 | name: name 18 | }; 19 | 20 | console.info('Enemy registered ', name); 21 | }; 22 | 23 | AFRAME.registerSystem('enemy', { 24 | schema: { 25 | wave: {default: 0} 26 | }, 27 | 28 | init: function () { 29 | var self = this; 30 | var sceneEl = this.sceneEl; 31 | 32 | if (!sceneEl.hasLoaded) { 33 | sceneEl.addEventListener('loaded', this.init.bind(this)); 34 | return; 35 | } 36 | 37 | this.poolHelper = new PoolHelper('enemy', ABLAST.ENEMIES, this.sceneEl); 38 | 39 | this.activeEnemies = []; 40 | 41 | // TODO: Enable A-Frame `System.update()` to decouple from gamestate. 42 | sceneEl.addEventListener('gamestate-changed', function (evt) { 43 | if ('state' in evt.detail.diff) { 44 | if (evt.detail.state.state === 'STATE_PLAYING') { 45 | setTimeout(function(){ 46 | self.createWave(0); 47 | }, 1000); 48 | } 49 | else if (evt.detail.state.state === 'STATE_GAME_OVER' 50 | || evt.detail.state.state === 'STATE_GAME_WIN' 51 | ||evt.detail.state.state === 'STATE_MAIN_MENU') { 52 | self.reset(); 53 | return; 54 | } 55 | } 56 | 57 | if ('waveSequence' in evt.detail.diff) { 58 | self.createSequence(evt.detail.state.waveSequence); 59 | } 60 | 61 | if ('wave' in evt.detail.diff) { 62 | self.createWave(evt.detail.state.wave); 63 | } 64 | }); 65 | }, 66 | 67 | getEnemy: function (name) { 68 | return this.poolHelper.requestEntity(name); 69 | }, 70 | 71 | onEnemyDeath: function (name, entity) { 72 | if (this.sceneEl.getAttribute('gamestate').state === 'STATE_MAIN_MENU') { 73 | this.sceneEl.emit('start-game'); 74 | } else { 75 | this.poolHelper.returnEntity(name, entity); 76 | this.sceneEl.emit('enemy-death'); 77 | } 78 | }, 79 | 80 | createSequence: function (sequenceNumber) { 81 | var self = this; 82 | var startOffset = this.currentWave.sequences[sequenceNumber].start || 0; 83 | setTimeout(function initFirstSequence() { 84 | self.currentSequence = sequenceNumber; 85 | var sequence = self.currentWave.sequences[sequenceNumber]; 86 | sequence.enemies.forEach(function createEnemyFromDef (enemyDef) { 87 | self.createEnemies(enemyDef); 88 | }); 89 | }, startOffset); 90 | }, 91 | 92 | createWave: function (waveNumber) { 93 | this.currentWave = WAVES[waveNumber % WAVES.length]; 94 | // console.log('Creating wave', waveNumber); 95 | this.createSequence(0); 96 | this.sceneEl.emit('wave-created', {wave: this.currentWave}); 97 | }, 98 | 99 | createEnemy: function (enemyType, enemyDefinition, timeOffset) { 100 | var self = this; 101 | var entity = this.getEnemy(enemyType); 102 | 103 | entity.setAttribute('enemy', {shootingDelay: 3000}); 104 | entity.setAttribute('curve-movement', { 105 | type: enemyDefinition.movement, 106 | loopStart: enemyDefinition.loopStart || 1, 107 | timeOffset: timeOffset || 0 108 | }); 109 | 110 | function activateEnemy(entity) { 111 | entity.setAttribute('visible', true); 112 | entity.components['curve-movement'].addPoints(enemyDefinition.points); 113 | entity.play(); 114 | self.activeEnemies.push(entity); 115 | self.sceneEl.emit('enemy-spawn', {enemy: entity}); 116 | } 117 | 118 | if (timeOffset) { 119 | if (timeOffset < 0) { 120 | entity.setAttribute('visible', false); 121 | setTimeout(function() { 122 | activateEnemy(entity); 123 | }, -timeOffset); 124 | } else { 125 | 126 | } 127 | } else { 128 | activateEnemy(entity); 129 | } 130 | }, 131 | 132 | createEnemies: function (enemyDefinition) { 133 | if (Array.isArray(enemyDefinition.type)) { 134 | for (var i = 0; i < enemyDefinition.type.length; i++) { 135 | var type = enemyDefinition.type[i]; 136 | var timeOffset = (enemyDefinition.enemyTimeOffset || 0) * i; 137 | this.createEnemy(type, enemyDefinition, timeOffset); 138 | } 139 | } else { 140 | this.createEnemy(enemyDefinition.type, enemyDefinition); 141 | } 142 | }, 143 | 144 | reset: function (entity) { 145 | var self = this; 146 | this.activeEnemies.forEach(function (enemy) { 147 | self.poolHelper.returnEntity(enemy.getAttribute('enemy').name, enemy); 148 | }); 149 | } 150 | }); 151 | -------------------------------------------------------------------------------- /src/systems/explosion.js: -------------------------------------------------------------------------------- 1 | /* globals ABLAST AFRAME */ 2 | var PoolHelper = require('../lib/poolhelper.js'); 3 | 4 | ABLAST.EXPLOSIONS = {}; 5 | 6 | ABLAST.registerExplosion = function (name, data, definition) { 7 | if (ABLAST.EXPLOSIONS[name]) { 8 | throw new Error('The explosion `' + name + '` has been already registered. ' + 9 | 'Check that you are not loading two versions of the same explosion ' + 10 | 'or two different enemies of the same name.'); 11 | } 12 | 13 | ABLAST.EXPLOSIONS[name] = { 14 | poolSize: data.poolSize, 15 | components: data.components, 16 | definition: definition, 17 | name: name 18 | }; 19 | 20 | console.info('Explosion registered ', name); 21 | }; 22 | 23 | AFRAME.registerSystem('explosion', { 24 | schema: { 25 | wave: {default: 0} 26 | }, 27 | 28 | init: function () { 29 | this.poolHelper = new PoolHelper('explosion', ABLAST.EXPLOSIONS, this.sceneEl); 30 | this.activeExplosions = []; 31 | 32 | this.sounds = { 33 | 'enemy_start': document.getElementById('explosion0'), 34 | 'enemy0': document.getElementById('explosion0'), 35 | 'enemy1': document.getElementById('explosion1'), 36 | 'enemy2': document.getElementById('explosion2'), 37 | 'enemy3': document.getElementById('explosion3'), 38 | 'bullet': document.getElementById('hitbullet'), 39 | 'background': document.getElementById('hitbullet') 40 | }; 41 | 42 | this.soundVolumes = { 43 | 'enemy_start': 0.7, 44 | 'enemy0': 1, 45 | 'enemy1': 1, 46 | 'enemy2': 1, 47 | 'enemy3': 1, 48 | 'bullet': 0.4, 49 | 'background': 0.2 50 | }; 51 | }, 52 | 53 | reset: function (entity) { 54 | var self = this; 55 | this.activeExplosions.forEach(function (entity) { 56 | self.returnToPool(entity.getAttribute('explosion').name, entity); 57 | }); 58 | }, 59 | 60 | returnToPool: function (name, entity) { 61 | this.activeExplosions.splice(this.activeExplosions.indexOf(entity), 1); 62 | this.poolHelper.returnEntity(name, entity); 63 | }, 64 | 65 | getFromPool: function (name) { 66 | var entity = this.poolHelper.requestEntity(name); 67 | this.activeExplosions.push(entity); 68 | return entity; 69 | }, 70 | 71 | createExplosion: function (type, position, color, scale, direction, enemyName) { 72 | var explosionEntity = this.getFromPool(type); 73 | explosionEntity.setAttribute('position', position || this.el.getAttribute('position')); 74 | explosionEntity.setAttribute('explosion', { 75 | type: type, 76 | lookAt: direction.clone(), 77 | color: color || '#FFF', 78 | scale: scale || 1.0 79 | }); 80 | 81 | // This should be done by the pool!! 82 | explosionEntity.setAttribute('sound', { 83 | src: this.sounds[enemyName || type].src, 84 | volume: this.soundVolumes[enemyName || type], 85 | poolSize: 15, 86 | autoplay: true 87 | }); 88 | explosionEntity.setAttribute('visible', true); 89 | 90 | explosionEntity.play(); 91 | 92 | } 93 | }); 94 | 95 | 96 | /* globals ABLAST */ 97 | ABLAST.registerExplosion( 98 | // name 99 | 'enemy', 100 | // data 101 | { 102 | components: { 103 | explosion: { 104 | type: 'enemy', 105 | }, 106 | }, 107 | poolSize: 10 108 | }, 109 | // implementation 110 | { 111 | } 112 | ); 113 | 114 | /* globals ABLAST */ 115 | ABLAST.registerExplosion( 116 | // name 117 | 'enemygun', 118 | // data 119 | { 120 | components: { 121 | explosion: { 122 | type: 'enemygun', 123 | }, 124 | }, 125 | poolSize: 10 126 | }, 127 | // implementation 128 | { 129 | } 130 | ); 131 | 132 | 133 | /* globals ABLAST */ 134 | ABLAST.registerExplosion( 135 | // name 136 | 'bullet', 137 | // data 138 | { 139 | components: { 140 | explosion: { 141 | type: 'bullet', 142 | }, 143 | }, 144 | poolSize: 10 145 | }, 146 | // implementation 147 | { 148 | } 149 | ); 150 | 151 | /* globals ABLAST */ 152 | ABLAST.registerExplosion( 153 | // name 154 | 'background', 155 | // data 156 | { 157 | components: { 158 | explosion: { 159 | type: 'background', 160 | }, 161 | }, 162 | poolSize: 10 163 | }, 164 | // implementation 165 | { 166 | } 167 | ); 168 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | var PLUGINS = []; 4 | if (process.env.NODE_ENV === 'production') { 5 | PLUGINS.push(new webpack.optimize.UglifyJsPlugin()); 6 | } 7 | 8 | module.exports = { 9 | entry: './src/index.js', 10 | output: { 11 | path: __dirname, 12 | filename: 'build/build.js' 13 | }, 14 | plugins: PLUGINS, 15 | devServer: { 16 | disableHostCheck: true 17 | } 18 | }; 19 | --------------------------------------------------------------------------------