initial commit. work on ui

This commit is contained in:
Kevin Ngo
2018-07-18 20:48:45 +02:00
commit 4cf793848b
73 changed files with 98805 additions and 0 deletions

48
.gitignore vendored Normal file
View File

@@ -0,0 +1,48 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
.DS_Store
yarn.lock
exports/
*.sw*
/index.html
/build
dump.rdb
.ghpages

8
README.md Normal file
View File

@@ -0,0 +1,8 @@
# supersaber
From the [Supermedium team](https://supermedium.com).
```
npm install
npm run start
```

BIN
assets/images/BackspaceIcon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
assets/images/ButtonShadow.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
assets/images/CheckmarkIcon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/images/DismissIcon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
assets/images/EnterIcon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/images/GlobalIcon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
assets/images/KeyShadow.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
assets/images/ShiftActiveIcon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/images/ShiftIcon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/images/SwitchShadow.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
assets/img/env/space/negx.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 KiB

BIN
assets/img/env/space/negy.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

BIN
assets/img/env/space/negz.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

BIN
assets/img/env/space/posx.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 KiB

BIN
assets/img/env/space/posy.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

BIN
assets/img/env/space/posz.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 KiB

BIN
assets/img/fx/fx1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/img/fx/fx2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
assets/img/fx/fx3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
assets/img/fx/fx4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
assets/img/fx/fx5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
assets/img/fx/fx6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
assets/img/fx/fx7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
assets/img/fx/fx8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/img/grid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

BIN
assets/img/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/img/play.psd Normal file

Binary file not shown.

BIN
assets/img/result.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/img/result.psd Normal file

Binary file not shown.

BIN
assets/img/resulticons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
assets/img/slice.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
assets/img/tube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
assets/models/color.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

BIN
assets/models/mr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

BIN
assets/sounds/ButtonClick.mp3 Executable file

Binary file not shown.

Binary file not shown.

BIN
assets/sounds/InputClick.mp3 Executable file

Binary file not shown.

BIN
assets/sounds/KeyDown.mp3 Executable file

Binary file not shown.

BIN
assets/sounds/KeyIn.mp3 Executable file

Binary file not shown.

BIN
assets/sounds/ToastShow.mp3 Executable file

Binary file not shown.

BIN
assets/sounds/hover.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

11319
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

67
package.json Normal file
View File

@@ -0,0 +1,67 @@
{
"private": true,
"version": "1.0.0",
"scripts": {
"build": "cross-env NODE_ENV=production webpack",
"start": "webpack-dev-server --host 0.0.0.0 --progress --colors --hot --inline --port 3000"
},
"dependencies": {
"aframe-animation-component": "^4.1.1",
"aframe-animation-timeline-component": "^1.3.1",
"aframe-cubemap-component": "^0.1.2",
"aframe-event-set-component": "^4.0.1",
"aframe-gltf-part-component": "1.1.0",
"aframe-haptics-component": "^1.4.1",
"aframe-layout-component": "^4.3.1",
"aframe-orbit-controls": "^1.2.0",
"aframe-particle-system-component": "^1.0.11",
"aframe-proxy-event-component": "^1.1.1",
"aframe-slice9-component": "^1.0.0",
"aframe-state-component": "^4.1.0",
"algoliasearch": "^3.29.0",
"ansi-html": "0.0.7",
"autoprefixer": "^7.2.3",
"babel": "6.23.0",
"babel-core": "6.24.1",
"babel-loader": "7.0.0",
"babel-minify-webpack-plugin": "0.2.0",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "6.24.1",
"babel-preset-stage-0": "6.24.1",
"body-parser": "1.18.2",
"classnames": "^2.2.5",
"css-loader": "^0.28.7",
"html-entities": "^1.2.1",
"html-minifier": "^3.5.11",
"ip": "1.1.5",
"json-loader": "^0.5.7",
"nunjucks": "3.0.1",
"postcss-loader": "^2.0.9",
"uglify-js": "git://github.com/mishoo/UglifyJS2#harmony",
"uglifyjs-webpack-plugin": "0.4.6",
"webpack": "2.3.3",
"webpack-glsl-loader": "^1.0.1",
"webpack-sources": "1.0.1"
},
"devDependencies": {
"cross-env": "4.0.0",
"ghpages": "0.0.10",
"semistandard": "10.0.0",
"shx": "^0.2.2",
"snazzy": "7.0.0",
"superagent": "^3.8.2",
"webpack-dev-server": "2.4.4"
},
"semistandard": {
"globals": [
"AFRAME",
"THREE"
],
"ignore": [
"build/**"
]
},
"engines": {
"node": "8.9.1"
}
}

View File

@@ -0,0 +1,57 @@
AFRAME.registerComponent('song-loader', {
schema: {
challengeId: {type: 'string'},
},
update: async function () {
var challengeId = this.data.challengeId;
var frameSystem = this.el.systems.frames;
var punchSystem = this.el.systems.punches;
if (!challengeId) {
return;
}
const firstClient = new Client({ query: { id: challengeId } });
const resp = await firstClient.challengeById(challengeId);
const challenge = resp.performance;
const auth = firstClient.serialize();
const client = new Client({ auth });
this.el.emit('challengeloadstart');
console.log('Loading punches...');
this.el.emit('challengeloadingpunches');
const punches = await client.challengePunches(challenge);
console.log('Loading frames...');
this.el.emit('challengeloadingframes');
const frames = await client.challengeFrames(challenge);
history.pushState(
'',
challenge.song_name,
updateQueryParam(window.location.href, 'challenge', this.data.challengeId)
);
document.title = `Soundboxing - ${challenge.song_name}`;
this.el.emit('challengeloaded', {
frames: frames,
punches: punches,
title: challenge.song_name,
videoId: challenge.youtube_id,
});
console.log('Finished loading challenge data.');
},
});
function updateQueryParam(uri, key, value) {
var re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i');
var separator = uri.indexOf('?') !== -1 ? '&' : '?';
if (uri.match(re)) {
return uri.replace(re, '$1' + key + '=' + value + '$2');
} else {
return uri + separator + key + '=' + value;
}
}

View File

@@ -0,0 +1,6 @@
AFRAME.registerComponent('console-shortcuts', {
play: function() {
window.scene = this.el;
window.state = this.el.systems.state.state;
},
});

View File

@@ -0,0 +1,117 @@
/**
* Keyboard bindings to control controller.
* Position controller in front of camera.
*/
AFRAME.registerComponent('debug-controller', {
schema: {
enabled: { default: false },
},
init: function() {
var primaryHand;
var secondaryHand;
if (!this.data.enabled && !AFRAME.utils.getUrlParameter('debug')) {
return;
}
console.log('%c debug-controller enabled ', 'background: #111; color: red');
this.isTriggerDown = false;
primaryHand = document.getElementById('leftGloveContainer');
secondaryHand = document.getElementById('rightGloveContainer');
primaryHand.setAttribute('position', { x: 0.2, y: 1.5, z: -0.5 });
secondaryHand.setAttribute('position', { x: -0.2, y: 1.5, z: -0.5 });
primaryHand.setAttribute('rotation', { x: 0, y: 0, z: 0 });
secondaryHand.setAttribute('rotation', { x: 0, y: 0, z: 0 });
document.addEventListener('keydown', evt => {
var primaryPosition;
var primaryRotation;
var secondaryPosition;
if (!evt.shiftKey) {
return;
}
// <space> for trigger.
if (evt.keyCode === 32) {
if (this.isTriggerDown) {
primaryHand.emit('triggerup');
this.isTriggerDown = false;
} else {
primaryHand.emit('triggerdown');
this.isTriggerDown = true;
}
return;
}
// <n> secondary grip.
if (evt.keyCode === 78) {
if (this.secondaryGripDown) {
secondaryHand.emit('gripup');
this.secondaryGripDown = false;
} else {
secondaryHand.emit('gripdown');
this.secondaryGripDown = true;
}
}
// <m> primary grip.
if (evt.keyCode === 77) {
if (this.primaryGripDown) {
primaryHand.emit('gripup');
this.primaryGripDown = false;
} else {
primaryHand.emit('gripdown');
this.primaryGripDown = true;
}
}
// Position bindings.
if (!evt.ctrlKey) {
primaryPosition = primaryHand.object3D.position;
if (evt.keyCode === 72) {
primaryPosition.x -= 0.01;
} // h.
if (evt.keyCode === 74) {
primaryPosition.y -= 0.01;
} // j.
if (evt.keyCode === 75) {
primaryPosition.y += 0.01;
} // k.
if (evt.keyCode === 76) {
primaryPosition.x += 0.01;
} // l.
if (evt.keyCode === 59 || evt.keyCode === 186) {
primaryPosition.z -= 0.01;
} // ;.
if (evt.keyCode === 222) {
primaryPosition.z += 0.01;
} // ;.
} else {
secondaryPosition = secondaryHand.object3D.position;
if (evt.keyCode === 72) {
secondaryPosition.x -= 0.01;
} // h.
if (evt.keyCode === 74) {
secondaryPosition.y -= 0.01;
} // j.
if (evt.keyCode === 75) {
secondaryPosition.y += 0.01;
} // k.
if (evt.keyCode === 76) {
secondaryPosition.x += 0.01;
} // l.
if (evt.keyCode === 59 || evt.keyCode === 186) {
secondaryPosition.z -= 0.01;
} // ;.
if (evt.keyCode === 222) {
secondaryPosition.z += 0.01;
} // ;.
}
});
},
});

View File

@@ -0,0 +1,31 @@
/**
* Log cursor events.
*/
AFRAME.registerComponent('debug-cursor', {
init: function() {
if (process.env.NODE_ENV === 'production') {
return;
}
this.el.addEventListener('mouseenter', evt => {
this.log('mouseenter', evt.detail.intersectedEl, 'green');
});
this.el.addEventListener('mouseleave', evt => {
this.log('mouseleave', evt.detail.intersectedEl, 'red');
});
this.el.addEventListener('click', evt => {
this.log('click', evt.detail.intersectedEl, 'blue');
});
},
log: function(event, intersectedEl, color) {
if (intersectedEl.id) {
console.log(`%c[${event}] ${intersectedEl.id}`, `color: ${color}`);
} else {
console.log(`%c[${event}]`, `color: ${color}`);
console.log(intersectedEl);
}
},
});

View File

@@ -0,0 +1,16 @@
AFRAME.registerComponent('discolight', {
schema: {
color: { type: 'color' },
speed: { default: 1.0 },
},
init: function() {
this.color = new THREE.Color(this.data.color);
this.hsl = this.color.getHSL();
},
tick: function(time, delta) {
this.hsl.h += delta * 0.0001 * this.data.speed;
if (this.hsl.l > 1.0) this.hsl.l = 0.0;
this.color.setHSL(this.hsl.h, this.hsl.s, this.hsl.l);
this.el.setAttribute('light', { color: this.color.getHex() });
},
});

View File

@@ -0,0 +1,14 @@
AFRAME.registerComponent('discotube', {
schema: {
speedX: { default: 1.0 },
speedY: { default: 0.1 },
},
init: function() {
this.material = this.el.object3D.children[0].material;
},
tick: function(time, delta) {
if (this.material == null) return;
this.material.map.offset.x -= delta * 0.0001 * this.data.speedX;
this.material.map.offset.y -= delta * 0.0001 * this.data.speedY;
},
});

View File

@@ -0,0 +1,10 @@
AFRAME.registerComponent('keyboard-raycastable', {
play: function() {
var els;
var i;
els = this.el.querySelectorAll('*');
for (i = 0; i < els.length; i++) {
els[i].setAttribute('bind-toggle__raycastable', 'isSearchScreen');
}
},
});

View File

@@ -0,0 +1,26 @@
/**
* Play audio element on event.
*/
AFRAME.registerComponent('play-audio', {
schema: {
audio: { type: 'string' },
event: { type: 'string' },
volume: { type: 'number', default: 1 },
},
multiple: true,
init: function() {
var audio;
audio = document.querySelector(this.data.audio);
audio.volume = this.data.volume;
this.el.addEventListener(this.data.event, evt => {
if (!audio.paused) {
audio.pause();
audio.currentTime = 0;
}
audio.play();
});
},
});

View File

@@ -0,0 +1,13 @@
AFRAME.registerComponent('play-button', {
init: function() {
var el = this.el;
el.addEventListener('click', () => {
el.sceneEl.emit('playbuttonclick');
});
el.sceneEl.addEventListener('youtubefinished', evt => {
el.object3D.visible = false;
});
},
});

View File

@@ -0,0 +1,38 @@
AFRAME.registerComponent('gpu-preloader', {
init: function() {
setTimeout(() => {
this.preloadFont();
this.preloadKeyboard();
}, 1000);
setTimeout(() => {
this.el.object3D.visible = false;
}, 2000);
},
preloadFont: function() {
var text;
text = document.querySelector('[text]');
if (text.components.text.texture) {
this.preloadTexture(text.components.text.texture);
} else {
text.addEventListener('textfontset', () => {
this.preloadTexture(text.components.text.texture);
});
}
},
preloadKeyboard: function() {
var kbd;
kbd = document.getElementById('kb');
kbd.object3D.traverse(node => {
if (node.material && node.material.map) {
this.preloadTexture(node.material.map);
}
});
},
preloadTexture: function(texture) {
this.el.sceneEl.renderer.setTexture2D(texture, 0);
},
});

View File

@@ -0,0 +1 @@
AFRAME.registerComponent('raycastable', {});

View File

@@ -0,0 +1,75 @@
/**
* Pivot the scene when user enters VR to face the links.
*/
AFRAME.registerComponent('recenter', {
init: function() {
var sceneEl = this.el.sceneEl;
this.matrix = new THREE.Matrix4();
this.frustum = new THREE.Frustum();
this.rotationOffset = 0;
this.euler = new THREE.Euler();
this.euler.order = 'YXZ';
this.menuPosition = new THREE.Vector3();
this.recenter = this.recenter.bind(this);
this.checkInViewAfterRecenter = this.checkInViewAfterRecenter.bind(this);
// Delay to make sure we have a valid pose.
sceneEl.addEventListener('enter-vr', () => {
setTimeout(() => {
this.recenter();
}, 100);
});
// User can also recenter the menu manually.
sceneEl.addEventListener('menudown', () => {
this.recenter();
});
sceneEl.addEventListener('thumbstickdown', () => {
this.recenter();
});
},
recenter: function(skipCheck) {
var euler = this.euler;
euler.setFromRotationMatrix(
this.el.sceneEl.camera.el.object3D.matrixWorld,
'YXZ'
);
this.el.object3D.rotation.y = euler.y + this.rotationOffset;
// Check if the menu is in camera frustum in next tick after a frame has rendered.
if (skipCheck) {
return;
}
setTimeout(this.checkInViewAfterRecenter, 0);
},
/*
* Sometimes the quaternion returns the yaw in the [-180, 180] range.
* Check if the menu is in the camera frustum after recenter it to
* decide if we apply an offset or not.
*/
checkInViewAfterRecenter: function() {
var camera = this.el.sceneEl.camera;
var frustum = this.frustum;
var menu = document.querySelector('#menu');
var menuPosition = this.menuPosition;
camera.updateMatrix();
camera.updateMatrixWorld();
frustum.setFromMatrix(
this.matrix.multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
)
);
menu.object3D.updateMatrixWorld();
menuPosition.setFromMatrixPosition(menu.object3D.matrixWorld);
if (frustum.containsPoint(menuPosition)) {
return;
}
this.rotationOffset = this.rotationOffset === 0 ? Math.PI : 0;
// Recenter again with the new offset.
this.recenter(true);
},
remove: function() {
this.el.sceneEl.removeEventListener('enter-vr', this.recenter);
},
});

42
src/components/search.js Normal file
View File

@@ -0,0 +1,42 @@
var algoliasearch = require('algoliasearch/lite');
var client = algoliasearch('QULTOY3ZWU', 'be07164192471df7e97e6fa70c1d041d');
var index = client.initIndex('supersaber');
/**
* Search (including the initial list of popular searches).
*/
AFRAME.registerComponent('search', {
init: function() {
this.eventDetail = {results: []};
this.queryObject = {query: ''};
// Populate popular.
this.search('');
},
search: function (query) {
this.queryObject.query = query;
index.search(this.queryObject, (err, content) => {
this.eventDetail.results = content.hits;
console.log(content.hits);
this.el.sceneEl.emit('searchresults', this.eventDetail);
});
}
});
/**
* Click listener for search result.
*/
AFRAME.registerComponent('search-result', {
init: function() {
var el = this.el;
this.eventDetail = {};
el.addEventListener('click', () => {
this.eventDetail.challengeId = el.getAttribute('data-song-id');
this.eventDetail.title = el.getAttribute('data-title');
el.sceneEl.emit('challengeset', this.eventDetail);
});
},
});

View File

@@ -0,0 +1,10 @@
AFRAME.registerComponent('toggle-pause-play', {
schema: {
isPlaying: { default: false },
},
update: function() {
const action = this.data.isPlaying ? 'pause' : 'play';
parent.postMessage(JSON.stringify({ verify: 'game-action', action }), '*');
},
});

48
src/index.html Normal file
View File

@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>Supersaber</title>
<script src="vendor/aframe.dev.js"></script>
<script src="build/build.js"></script>
</head>
<body>
<audio id="hoverSound" src="/assets/sounds/hover.ogg"></audio>
<a-scene bind__challenge-loader="challengeId: challenge.id"
bind__embedded="embedded"
challenge-loader
console-shortcuts
search>
<a-assets timeout="10000">
<a-mixin id="raycaster"
raycaster="objects: [raycastable], [data-ui]; far: 2"></a-mixin>
<img id="playImg" src="assets/img/play.png">
<img id="resultImg" src="assets/img/result.png">
<img id="resultIconsImg" src="assets/img/resulticons.png">
<img id="tubeImg" src="assets/img/tube.png">
<img id="gridImg" src="assets/img/grid.png">
<img id="sliceImg" src="assets/img/slice.png">
<a-mixin id="slice" slice9="color: #050505; src: #sliceImg; left: 50; right: 52; top: 50; bottom: 52; padding: 0.04"></a-mixin>
<a-mixin id="font" text="font: exo2semibold; letterSpacing: -1"></a-mixin>
<a-mixin id="textFont" text="font: exo2semibold; letterSpacing: -1"></a-mixin>
</a-assets>
<a-entity id="dustParticles" particle-system="maxAge: 100; positionSpread: 5 5 5; type: 2; rotationAxis: x; rotationAngle: 0.001; accelerationValue: 0.001 0.001 0.001; accelerationSpread: 0.001 0.001 0.001; velocityValue: 0.001 0.001 0.001; velocitySpread: 0.0125 0.0125 0.0125; color: #ffffff; size: 0.1; opacity: 0.85; direction: 0; particleCount: 150; texture: assets/img/smokeparticle.png"></a-entity>
<a-entity id="container">
{% include './templates/environment.html' %}
{% include './templates/gameUi.html' %}
{% include './templates/menu.html' %}
</a-entity>
<a-entity id="cameraRig">
<a-entity id="camera" camera look-controls="enabled: false" orbit-controls="target: 0 1.6 -0.5; maxDistance: 20; initialPosition: 0 1.6 0"></a-entity>
<a-entity id="leftHand"></a-entity>
<a-entity id="rightHand"></a-entity>
</a-entity>
<a-entity id="mouseCursor" mixin="raycaster" cursor="rayOrigin: mouse" bind__isPlaying="!inVR"></a-entity>
</a-scene>
</body>
</html>

17
src/index.js Normal file
View File

@@ -0,0 +1,17 @@
require('babel-polyfill');
function requireAll (req) { req.keys().forEach(req); }
require('aframe-animation-component');
require('aframe-cubemap-component');
require('aframe-event-set-component');
require('aframe-haptics-component');
require('aframe-layout-component');
require('aframe-orbit-controls');
require('aframe-particle-system-component');
require('aframe-proxy-event-component');
require('aframe-state-component');
require('aframe-slice9-component');
requireAll(require.context('./components/', true, /\.js$/));
requireAll(require.context('./state/', true, /\.js$/));

3115
src/lib/aframe-material.js Executable file

File diff suppressed because it is too large Load Diff

22
src/lib/soundpool.js Normal file
View File

@@ -0,0 +1,22 @@
module.exports = function SoundPool (src, volume, size) {
var currSound = 0;
var i;
var pool = [];
var sound;
for (i = 0; i < size; i++) {
sound = new Audio(src);
sound.volume = volume;
sound.load();
pool.push(sound);
}
return {
play: function () {
if (pool[currSound].currentTime === 0 || pool[currSound].ended) {
pool[currSound].play();
}
currSound = (currSound + 1) % size;
}
};
};

141
src/state/index.js Normal file
View File

@@ -0,0 +1,141 @@
var hasInitialChallenge = !!AFRAME.utils.getUrlParameter('challenge');
AFRAME.registerState({
initialState: {
challenge: {
id: AFRAME.utils.getUrlParameter('challenge'),
isLoading: false,
loadingText: ''
},
discoLightsOn: true,
discotube: {speedX: -0.05, speedY: -0.1},
featuredPanelShowing: !hasInitialChallenge,
inVR: false,
isChallengeScreen: hasInitialChallenge,
isFeaturedScreen: !hasInitialChallenge,
isSearchScreen: false,
maxStreak: 0,
menuActive: true,
playButtonShowing: hasInitialChallenge,
playButtonText: 'Play',
score: 0,
scoreText: '',
screen: hasInitialChallenge ? 'challenge' : 'featured',
screenHistory: [],
searchResults: [],
streak: 0
},
handlers: {
/**
* Update search results. Will automatically render using `bind-for` (menu.html).
*/
searchresults: function (state, payload) {
var i;
state.searchResults.length = 0;
for (i = 0; i < 6; i++) {
state.searchResults.push(payload.results[i]);
}
},
setscreen: function (state, payload) {
console.log('setscreen: ' + payload.screen);
if (state.screen === payload.screen) {
return;
}
switch (payload.screen) {
case 'challenge':
case 'featured':
case 'search':
state.screenHistory.push(state.screen);
state.screen = payload.screen;
break;
default:
console.log('Unknown screen set: ' + payload.screen);
}
},
popscreen: function (state, payload) {
var prevScreen = state.screenHistory.pop();
if (!prevScreen) {
return;
}
state.screen = prevScreen;
console.log('popscreen: ' + state.screen);
},
challengeloaded: function (state, payload) {
state.challenge.isLoading = false;
state.isLoadingFrames = false;
state.isLoadingPunches = false;
state.score = 0;
},
challengeloadstart: function (state, payload) {
state.challenge.isLoading = true;
},
challengeloadingframes: function (state, payload) {
state.challenge.isLoadingPunches = false;
state.challenge.isLoadingFrames = true;
},
challengeloadingpunches: function (state, payload) {
state.challenge.isLoadingPunches = true;
state.challenge.isLoadingFrames = false;
},
challengeset: function (state, payload) {
state.challenge.id = payload.challengeId;
state.score = 0;
state.streak = 0;
state.maxStreak = 0;
state.menuActive = false;
this.setscreen(state, {screen: 'challenge'});
},
playbuttonclick: function (state) {
state.menuActive = false;
},
searchblur: function (state) {
this.popscreen(state);
},
searchfocus: function (state) {
this.setscreen(state, {screen: 'search'});
},
togglemenu: function (state) {
state.menuActive = !state.menuActive;
},
togglediscolights: function (state, payload) {
state.discoLightsOn = !state.discoLightsOn;
},
'enter-vr': function (state, payload) {
state.inVR = true;
},
'exit-vr': function (state, payload) {
state.inVR = false;
}
},
computeState: function (state) {
state.scoreText = `Streak: ${state.streak} / Max Streak: ${state.maxStreak} / Score: ${state.score}`;
state.isFeaturedScreen = state.screen === 'featured';
state.isSearchScreen = state.screen === 'search';
state.isChallengeScreen = state.screen === 'challenge';
state.featuredPanelShowing = state.isFeaturedScreen || (state.isChallengeScreen && state.menuActive);
if (state.challenge.isLoading) {
state.loadingText = 'Loading challenge...';
} else {
state.loadingText = '';
}
}
});

View File

@@ -0,0 +1,42 @@
<a-entity id="sky"
cubemap="folder: /assets/img/env/space/"></a-entity>
<a-entity id="skyFade"
bind__visible="menuActive"
bind__isPlaying="menuActive"
geometry="primitive: box; width: 50; height: 50; depth: 50"
material="shader: flat; color: #111; opacity: 0.6; side: back"></a-entity>
<a-entity id="tube"
geometry="primitive:cylinder;radius:3;height:100;openEnded:true"
material="fog: false; depthWrite: false; src:#tubeImg;transparent:true;side:back;repeat:1 5"
position="0 1.5 -40"
rotation="90 0 0"
bind__discotube="speedX: discotube.speedX; speedY: discotube.speedY"></a-entity>
<a-plane id="floor"
width="40"
height="40"
rotation="-90 0 0"
material="src:#gridImg; transparent: true; color: #f9e3fb; repeat: 80 80; depthWrite:false">
<a-plane position="0 0 -0.01"
width="2"
height="2"
material="color: #000; transparent: true; opacity: 0.7"></a-plane>
</a-plane>
<!-- Disco lights -->
<a-entity light="type: directional; intensity: 1"
position="0 -2 1"
discolight="color: #fda6f7; speed: 2.0"
bind__visible="discoLightsOn"></a-entity>
<a-entity light="type: directional; intensity: 1.6"
position="-0 2 .6"
discolight="color: #7a9efc; speed: 0.01"
bind__visible="discoLightsOn"></a-entity>
<!-- Default lights -->
<a-entity light="type: ambient; color: #BBB"
bind__visible="!discoLightsOn"></a-entity>
<a-entity light="type: directional; color: #FFF; intensity: 0.6"
position="-0.5 1 1"
bind__visible="!discoLightsOn"></a-entity>

36
src/templates/gameUi.html Normal file
View File

@@ -0,0 +1,36 @@
<a-entity id="gameInfo"
bind__visible="isChallengeScreen"
position="0 0 -80"
scale="100 100 100">
<a-entity mixin="textFont"
text="align:right; width:1.25; value:SCORE; color:#d6d955; anchor:right; letterSpacing:-2"
position="-0.089 0.183 -1.01"
bind__visible="!challenge.isLoading"></a-entity>
<a-entity mixin="textFont"
text="width:0.9; value:STREAK; color:#f95895; letterSpacing:-2; anchor:left"
position="0.097 0.183 -1.01"
bind__visible="!challenge.isLoading"></a-entity>
<a-entity mixin="textFont"
text="width:0.7; value:MAX; color:#f95895; anchor:left; letterSpacing:-2"
position="0.101 0.1 -1.01"
bind__visible="!challenge.isLoading"></a-entity>
<a-entity id="score"
bind__text="value: score"
mixin="textFont"
text="align:right; width:2; color:#feffc1; anchor:right; letterSpacing:-2"
position="-0.08 0.132 -1"
bind__visible="!challenge.isLoading"></a-entity>
<a-entity id="streak"
bind__text="value: streak"
mixin="textFont"
text="width:1.25; color:#ffbdd6; anchor:left; letterSpacing:-2"
position="0.098 0.144 -1"
bind__visible="!challenge.isLoading"></a-entity>
<a-entity id="maxStreak"
bind__text="value: maxStreak"
mixin="textFont"
text="width:0.8; color:#ffbdd6; anchor:left; letterSpacing:-2"
position="0.1 0.074 -1"
bind__visible="!challenge.isLoading"></a-entity>
</a-entity>

101
src/templates/menu.html Normal file
View File

@@ -0,0 +1,101 @@
{% macro searchResults () %}
<a-entity id="searchResultList"
layout="type: box; columns: 3; marginRow: 0.25; marginColumn: 0.85"
bind-for="for: item; in: searchResults; key: id; template: #searchResultTemplate">
</a-entity>
{% raw %}
<template id="searchResultTemplate">
<a-entity class="searchResult" search-result data-song-id="{{ item.id }}" data-title="{{ item.songName }}">
<a-entity>
<a-entity class="searchResultSlice"
geometry="primitive: plane; width: 0.8; height: 0.2"
material="shader:flat; transparent: true; src:#resultImg; color: #BBB"
position="0 -0.13 -0.01"
play-audio="event: mouseenter; audio: #hoverSound; volume: 0.03"
animation__mouseenter="property: material.color; from: #BBB; to: #FFF; startEvents: mouseenter; pauseEvents: mouseleave; dur: 150"
animation__mouseleave="property: material.color; from: #FFF; to: #BBB; startEvents: mouseleave; pauseEvents: mouseenter; dur: 150"
raycastable></a-entity>
<a-entity class="searchResultIcons"
geometry="primitive: plane; width: 0.8; height: 0.2"
material="shader: flat; transparent: true; src: #resultIconsImg; color: #FFF"
position="0 -0.13 -0.005"
raycastable></a-entity>
</a-entity>
<a-entity class="searchResultAuthor" mixin="textFont" text="wrapCount: 48; align: center; color: #54c2fd; value: {{ item.subSongName }}" position="0 -0.060 0"></a-entity>
<a-entity class="searchResultTitle" mixin="textFont" text="align: center; color: #FFF; wrapCount: 35; value: {{ item.songName }}" position="0 -0.111 0"></a-entity>
<a-entity class="searchResultDownloads" mixin="textFont" text="color: #cc0856; anchor: left; value: {{ item.downloads }}" position="0.192 -0.184 0"></a-entity>
</a-entity>
</template>
{% endraw %}
{% endmacro %}
<a-entity id="menu"
bind__visible="menuActive"
position="0 1 -1">
<a-input id="search"
event-set__1="_event: mouseenter; background-color: #333; placeholder-color: #FFF"
event-set__2="_event: mouseleave; background-color: #182029; placeholder-color: #F3D765"
event-set__3="_event: blur; value:"
event-set__4="_event: challengeset; value:"
position="-0.42 0.32 -0.1"
scale="0.65 0.65 0.65"
placeholder=" Search challenges..."
play-audio="event: mouseenter; audio: #hoverSound; volume: 0.03"
color="#FFF"
cursorColor="#f3d765"
font="exo2semibold"
letter-spacing="-1"
placeholder-color="#f3d765"
background-color="#182029"
bind-toggle__raycastable="menuActive && !isSearchScreen"
proxy-event="event: click; to: a-scene; as: searchfocus; captureBubbles: true"
search
width="1.3"></a-input>
<a-plane id="play"
play-button
play-audio="event: mouseenter; audio: #hoverSound; volume: 0.03"
position="0 0.15 0"
material="shader: flat; src: #playImg; transparent: true; color: #BBB"
width="0.512"
height="0.256"
raycaster="far:2"
animation__mouseenter1="property: material.color; from: #BBB; to: #FFF; startEvents: mouseenter; pauseEvents: mouseleave; dur: 150"
animation__mouseleave1="property: material.color; from: #FFF; to: #BBB; startEvents: mouseleave; pauseEvents: mouseenter; dur: 150"
animation__mouseenter2="property: scale; from: 1 1 1; to: 1.1 1.1 1.1; startEvents: mouseenter; pauseEvents: mouseleave; dur: 150"
animation__mouseleave2="property: scale; to: 1 1 1; from: 1.1 1.1 1.1; startEvents: mouseleave; pauseEvents: mouseenter; dur: 150"
bind-toggle__raycastable="playButtonShowing"
bind__visible="playButtonShowing"></a-plane>
<a-entity id="keyboardContaner"
position="0 0 1"
scale="0.4 0.4 0.4"
bind__visible="isSearchScreen">
<a-keyboard id="kb"
bind__keyboard="isOpen: isSearchScreen"
bind__isPlaying="isSearchScreen"
event__set="_event: diddismiss; visible: false"
proxy-event="event: diddismiss; to: a-scene; as: searchblur"
keyboard-raycastable></a-keyboard>
</a-entity>
<a-entity id="searchResultsContainer" position="-0.85 0.7 0">
{{ searchResults() }}
</a-entity>
</a-entity>
<a-entity id="songInfo"
bind__visible="isChallengeScreen"
position="0 -0.25 0">
<a-entity id="loadingText"
bind__text="value: loadingText"
mixin="textFont"
text="align: center; width: 1.25"
position="0 1 -1"
rotation="-25 0 0"
bind__visible="challenge.isLoading"></a-entity>
<a-entity id="title"
mixin="textFont"
text="align: center; width: 2.50; wrapCount: 80"
position="0 0.9 -1"
rotation="-25 0 0"></a-entity>
</a-entity>

25
src/templates/ui.html Normal file
View File

@@ -0,0 +1,25 @@
{% macro searchresult (featured) %}
<a-entity class="searchResult" search-result visible="{{ featured and 'true' or 'false' }}">
<a-entity>
<a-entity class="searchResultSlice"
geometry="primitive: plane; width: 0.8; height: 0.2"
material="shader:flat; transparent: true; src:#resultImg; color: #BBB"
position="0 -0.13 -0.01"
play-audio="event: mouseenter; audio: #hoverSound; volume: 0.03"
animation__mouseenter="property: material.color; from: #BBB; to: #FFF; startEvents: mouseenter; pauseEvents: mouseleave; dur: 150"
animation__mouseleave="property: material.color; from: #FFF; to: #BBB; startEvents: mouseleave; pauseEvents: mouseenter; dur: 150"
bind-toggle__raycastable="{{ featured and 'featuredPanelShowing' or 'isSearchScreen' }}"></a-entity>
<a-entity class="searchResultIcons"
geometry="primitive: plane; width: 0.8; height: 0.2"
material="shader:flat; transparent: true; src:#resultIconsImg; color: #FFF"
position="0 -0.13 -0.005"
bind-toggle__visible="{{ featured and 'featuredPanelShowing' or 'isSearchScreen' }}"
></a-entity>
</a-entity>
<a-entity class="searchResultAuthor" mixin="textFont" text="wrapCount: 48; align: center; color: #54c2fd" position="0 -0.060 0"></a-entity>
<a-entity class="searchResultTitle" mixin="textFont" text="align: center; color: #FFF; wrapCount: 35;" position="0 -0.111 0"></a-entity>
<a-entity class="searchResultTime" mixin="textFont" text="color: #54c2fd; anchor: left;" position="-0.192 -0.184 0"></a-entity>
<a-entity class="searchResultPunches" mixin="textFont" text="color: #fff155; anchor: left;" position="0.01 -0.184 0"></a-entity>
<a-entity class="searchResultFavorites" mixin="textFont" text="color: #cc0856; anchor: left;" position="0.192 -0.184 0"></a-entity>
</a-entity>
{% endmacro %}

83261
vendor/aframe.dev.js vendored Normal file

File diff suppressed because one or more lines are too long

99
webpack.config.js Normal file
View File

@@ -0,0 +1,99 @@
var MinifyPlugin = require('babel-minify-webpack-plugin');
var Nunjucks = require('nunjucks');
var fs = require('fs');
var htmlMinify = require('html-minifier').minify;
var ip = require('ip');
var path = require('path');
var webpack = require('webpack');
// Set up templating.
var nunjucks = Nunjucks.configure(path.resolve(__dirname, 'src'), {
noCache: true,
});
nunjucks.addGlobal('HOST', ip.address());
// Initial Nunjucks render.
var context = {IS_PRODUCTION: process.env.NODE_ENV === 'production'};
fs.writeFileSync('index.html', nunjucks.render('index.html', context));
// For development, watch HTML for changes to compile Nunjucks.
// The production Express server will handle Nunjucks by itself.
if (process.env.NODE_ENV !== 'production') {
fs.watch('src', { recursive: true }, (eventType, filename) => {
if (filename.indexOf('.html') === -1) {
return;
}
try {
fs.writeFileSync('index.html', nunjucks.render('index.html', context));
} catch (e) {
console.error(e);
}
});
}
PLUGINS = [new webpack.EnvironmentPlugin(['NODE_ENV'])];
if (process.env.NODE_ENV === 'production') {
PLUGINS.push(
new MinifyPlugin(
{
booleans: true,
builtIns: true,
consecutiveAdds: true,
deadcode: true,
evaluate: false,
flipComparisons: true,
guards: true,
infinity: true,
mangle: false,
memberExpressions: true,
mergeVars: true,
numericLiterals: true,
propertyLiterals: true,
regexpConstructors: true,
removeUndefined: true,
replace: true,
simplify: true,
simplifyComparisons: true,
typeConstructors: true,
undefinedToVoid: true,
keepFnName: true,
keepClassName: true,
tdz: true,
},
{
sourceMap: 'source-map',
}
)
);
}
module.exports = {
devtool: '#inline-source-map',
devServer: {
disableHostCheck: true,
},
entry: './src/index.js',
output: {
path: __dirname,
filename: 'build/build.js',
},
plugins: PLUGINS,
module: {
rules: [
{
test: /\.js/,
exclude: path =>
path.indexOf('node_modules') !== -1 || path.indexOf('panel') !== -1,
loader: 'babel-loader',
},
{
test: /\.glsl/,
exclude: /(node_modules)/,
loader: 'webpack-glsl-loader',
},
],
},
resolve: {
modules: [path.join(__dirname, 'node_modules')],
},
};