Add song beats. Need pace adjustment

This commit is contained in:
Diego Marcos
2018-10-01 17:37:11 -07:00
parent 865f0cb82f
commit 9855dbca7d
7 changed files with 1223 additions and 5 deletions

View File

@@ -6,18 +6,24 @@ var utils = require('../utils');
AFRAME.registerComponent('beat-loader', {
schema: {
challengeId: {type: 'string'},
difficulty: {type: 'string'}
difficulty: {type: 'string'},
beatSpeed: {default: 4.0},
beatAnticipationTime: {default: 2.0}
},
orientations: [180, 0, 270, 90, 225, 135, 315, 45, 0],
horizontalPositions: [-0.60, -0.25, 0.25, 0.60],
verticalPositions: [1.00, 1.35, 1.70],
update: function () {
if (!this.data.challengeId || !this.data.difficulty) { return; }
this.loadBeats(this.data.challengeId, this.data.difficulty);
this.loadBeats();
},
/**
* XHR.
*/
loadBeats: function (id, difficulty) {
loadBeats: function () {
var el = this.el;
var xhr;
@@ -34,12 +40,122 @@ AFRAME.registerComponent('beat-loader', {
},
/**
* TODO: Load the beat data into the game.
* Load the beat data into the game.
*/
handleBeats: function (beatData) {
this.el.sceneEl.emit('beatloaderfinish', beatData, false);
var lessThan = function (a, b) {
return a._time - b._time;
};
this.beatData = beatData;
this.beatData._obstacles.sort(lessThan);
this.beatData._notes.sort(lessThan);
console.log('Finished loading challenge data.');
},
tick: function (time, delta) {
var audioEl = this.el.components.song.audio;
var notes;
var obstacles;
var lastTime = this.lastTime || 0;
var msPerBeat;
var noteTime;
if (!this.data.challengeId || !this.beatData || !audioEl) { return; }
notes = this.beatData._notes;
obstacles = this.beatData._obstacles;
this.bpm = this.beatData._beatsPerMinute;
msPerBeat = 1000 * 60 / this.beatData._beatsPerMinute;
for (i=0; i < notes.length; ++i) {
noteTime = notes[i]._time * msPerBeat;
if (noteTime > lastTime && noteTime <= lastTime + delta) {
notes[i].time = noteTime;
this.generateBeat(notes[i]);
}
}
for (i=0; i < obstacles.length; ++i) {
noteTime = obstacles[i]._time * msPerBeat;
if (noteTime > lastTime && noteTime <= lastTime + delta) {
// this.generateWall(obstacles[i]);
}
}
this.lastTime = lastTime + delta;
// Sync audio with first element.
if (this.audioSync || !this.first) { return; }
if (this.first.el.object3D.position.z < this.el.sceneEl.camera.el.object3D.position.z) { return; }
this.audioSync = true;
this.el.components.song.audio.currentTime = this.first.time;
},
generateBeat: function (noteInfo) {
var color;
var orientation;
var el;
var type = noteInfo._cutDirection === 8 ? 'dot' : 'arrow';
color = noteInfo._type === 0 ? 'red' : 'blue';
if (noteInfo._type === 3) {
type = 'mine';
color = undefined;
}
el = this.requestBeat(type, color);
if (!el) { return; }
el.setAttribute('beat', {
color: color,
type: type,
speed: this.data.beatSpeed});
el.object3D.position.set(
this.horizontalPositions[noteInfo._lineIndex],
this.verticalPositions[noteInfo._lineLayer],
-this.data.beatAnticipationTime * this.data.beatSpeed
);
el.setAttribute('rotation', {x: 0, y: 0, z: this.orientations[noteInfo._cutDirection]});
el.play();
if (this.first) { return; }
this.first = {
el: el,
time: noteInfo._time
};
},
// generateWall: function (wallInfo) {
// var el = this.el.sceneEl.components.pool__wall.requestEntity();
// var speed = this.data.beatSpeed;
// var durationMs;
// if (!el) { return; }
// durationSeconds = 60 * (wallInfo._duration / this.bpm);
// el.setAttribute('wall', {
// speed: speed
// });
// el.object3D.position.set(
// this.horizontalPositions[wallInfo._lineIndex],
// 1.30,
// -(this.data.beatAnticipationTime * speed)
// );
// el.object3D.scale.set(wallInfo._width * 0.30, 2.5, durationSeconds * speed);
// el.play();
// if (this.first) { return; }
// this.first = {
// el: el,
// time: wallInfo._time
// };
// },
requestBeat: function (type, color) {
var beatPoolName = 'pool__beat-' + type;
var pool;
if (color) {beatPoolName += '-' + color; }
pool = this.el.sceneEl.components[beatPoolName];
if (!pool) {
console.warn('Poo ' + beatPoolName + ' unavailable');
return;
}
return pool.requestEntity();
}
});
function updateQueryParam(uri, key, value) {
@@ -51,3 +167,5 @@ function updateQueryParam(uri, key, value) {
return uri + separator + key + '=' + value;
}
}

411
src/components/beat.js Normal file
View File

@@ -0,0 +1,411 @@
AFRAME.registerComponent('beat', {
schema: {
speed: {default: 1.0},
color: {default: 'red', oneOf: ['red', 'blue']},
size: {default: 0.30},
debug: {default: false},
type: {default: 'arrow', oneOf: ['arrow', 'dot', 'mine']}
},
materialColor: {
blue: '#08083E',
red: '#290404'
},
cutColor: {
blue: '#b3dcff',
red: '#ffb3ca'
},
models: {
arrow: "#beat-obj",
dot: "#beat-obj",
mine: "#mine-obj"
},
signModels: {
arrow: "#arrow-obj",
dot: "#dot-obj"
},
init: function () {
var el = this.el;
var color;
var size = this.data.size;
this.beatCollidersConfiguration = [
{position: {x: size / 2, y: 0, z: 0}, size: {width: size / 5.0, height: size, depth: size}},
{position: {x: -size / 2, y: 0, z: 0}, size: {width: size / 5.0, height: size, depth: size}},
{position: {x: 0, y: size / 2, z: 0}, size: {width: size, height: size / 5.0, depth: size}},
{position: {x: 0, y: -size / 2, z: 0}, size: {width: size, height: size / 5.0, depth: size}},
{position: {x: 0, y: 0, z: size / 2}, size: {width: size, height: size, depth: size / 5.0}},
{position: {x: 0, y: 0, z: -size / 2}, size: {width: size, height: size, depth: size / 5.0}}
];
this.boundingBox = new THREE.Box3();
this.saberEls = this.el.sceneEl.querySelectorAll('[saber-controls]');
this.backToPool = false;
this.returnToPoolTimer = 800;
this.initBlock();
this.initColliders();
this.initFragments();
},
update: function () {
this.updateBlock();
this.updateFragments();
},
initBlock: function () {
var el = this.el;
var blockEl = this.blockEl = document.createElement('a-entity');
var signEl = this.signEl = document.createElement('a-entity');
// Small offset to prevent z-fighting when the blocks are far away
signEl.object3D.position.z += 0.02;
blockEl.appendChild(signEl);
el.appendChild(blockEl);
},
updateBlock: function () {
var blockEl = this.blockEl;
var signEl = this.signEl;
blockEl.setAttribute('obj-model', {obj: this.models[this.data.type]});
blockEl.setAttribute('material', {
metalness: 0.6,
roughness: 0.12,
sphericalEnvMap: '#envmapTexture',
color: this.materialColor[this.data.color]
});
// Model is 0.29 size. We make it 1.0 so we can easily scale based on 1m size.
blockEl.object3D.scale.multiplyScalar(3.45).multiplyScalar(this.data.size);
if (this.data.type === 'mine') {
blockEl.addEventListener('model-loaded', (evt) => {
var model = evt.detail.model.children[0];
model.material = this.el.sceneEl.components.stagecolors.mineMaterial;
});
}
signEl.setAttribute('obj-model', {obj: this.signModels[this.data.type]});
signEl.setAttribute('material', {
shader: 'flat',
color: '#88f'
});
},
initColliders: function () {
var beatColliderEl;
var beatCollidersConfiguration = this.beatCollidersConfiguration;
var beatCollidersEls = this.beatCollidersEls = [];
var i;
var size;
for (i = 0; i < 6; i++) {
beatColliderEl = document.createElement('a-entity');
size = beatCollidersConfiguration[i].size;
beatColliderEl.setAttribute('geometry', {
primitive: 'box',
height: size.height,
width: size.width,
depth: size.depth
});
beatColliderEl.setAttribute('position', beatCollidersConfiguration[i].position);
beatColliderEl.setAttribute('visible', false);
beatCollidersEls.push(beatColliderEl);
if (i == 2) { this.correctBeatColliderEl = beatColliderEl; }
this.el.appendChild(beatColliderEl);
if (this.data.debug) {
beatColliderEl.setAttribute('visible', true);
if (i == 2) {
beatColliderEl.setAttribute('material', 'color: yellow');
} else {
beatColliderEl.setAttribute('material', 'color: purple');
}
}
}
},
initFragments: function () {
var partEl;
var cutEl;
var color = this.data.color === 'red' ? '#5b0502' : '#083771';
var size = this.data.size;
var geometry = {primitive: 'box', height: size, width: size, depth: size};
this.cutDirection = new THREE.Vector3();
this.rotationAxis = new THREE.Vector3();
partEl = this.partLeftEl = document.createElement('a-entity');
cutEl = this.cutLeftEl = document.createElement('a-entity');
partEl.appendChild(cutEl);
this.el.appendChild(partEl);
partEl = this.partRightEl = document.createElement('a-entity');
cutEl = this.cutRightEl = document.createElement('a-entity');
partEl.appendChild(cutEl);
this.el.appendChild(partEl);
this.initCuttingClippingPlanes();
},
updateFragments: function () {
var cutLeftEl = this.cutLeftEl;
var cutRightEl = this.cutRightEl;
var partLeftEl = this.partLeftEl;
var partRightEl = this.partRightEl;
partLeftEl.setAttribute('obj-model', 'obj: #beat-obj');
partLeftEl.setAttribute('material', {
metalness: 0.8,
roughness: 0.12,
sphericalEnvMap: '#envmapTexture',
color: this.materialColor[this.data.color],
side: 'double'
});
partLeftEl.setAttribute('visible', false);
cutLeftEl.setAttribute('obj-model', 'obj: #beat-obj');
cutLeftEl.setAttribute('material', {
shader: 'flat',
color: this.data.cutColor,
side: 'double'
});
partRightEl.setAttribute('obj-model', 'obj: #beat-obj');
partRightEl.setAttribute('material', {
metalness: 0.8,
roughness: 0.12,
sphericalEnvMap: '#envmapTexture',
color: this.materialColor[this.data.color],
side: 'double'
});
partRightEl.setAttribute('visible', false);
cutRightEl.setAttribute('obj-model', 'obj: #beat-obj');
cutRightEl.setAttribute('material', {
shader: 'flat',
color: this.data.cutColor,
side: 'double'
});
},
destroyBeat: (function () {
var point1 = new THREE.Vector3();
var point2 = new THREE.Vector3();
var point3 = new THREE.Vector3();
var planeMaterial = new THREE.MeshBasicMaterial({color: 'grey', side: THREE.DoubleSide});
var parallelPlaneMaterial = new THREE.MeshBasicMaterial({color: '#00008b', side: THREE.DoubleSide});
return function (saberEl) {
var i;
var trailPoints = saberEl.components.trail.saberTrajectory;
var direction = this.cutDirection;
point1.copy(trailPoints[0].top);
point2.copy(trailPoints[0].center);
point3.copy(trailPoints[trailPoints.length - 1].top);
direction.copy(point1).sub(point3);
var parallelPlane;
var parallelPlane2;
var planeGeometry;
var planeMesh;
var coplanarPoint;
var focalPoint;
var cutThickness = this.cutThickness = 0.02;
var rightCutPlane = this.rightCutPlane;
var rightBorderOuterPlane = this.rightBorderOuterPlane;
var leftCutPlane = this.leftCutPlane;
var leftBorderOuterPlane = this.leftBorderOuterPlane;
var rightBorderInnerPlane = this.rightBorderInnerPlane;
var leftBorderInnerPlane = this.leftBorderInnerPlane;
this.partLeftEl.object3D.position.set(0, 0, 0);
this.partLeftEl.object3D.rotation.set(0, 0, 0)
this.partLeftEl.object3D.updateMatrixWorld();
this.partRightEl.object3D.position.set(0, 0, 0);
this.partRightEl.object3D.rotation.set(0, 0, 0);
this.partRightEl.object3D.updateMatrixWorld();
this.rightCutPlanePoints = [
this.partRightEl.object3D.worldToLocal(point1.clone()),
this.partRightEl.object3D.worldToLocal(point2.clone()),
this.partRightEl.object3D.worldToLocal(point3.clone()),
];
this.leftCutPlanePoints = [
this.partLeftEl.object3D.worldToLocal(point3.clone()),
this.partLeftEl.object3D.worldToLocal(point2.clone()),
this.partLeftEl.object3D.worldToLocal(point1.clone()),
];
this.generateCutClippingPlanes();
if (this.data.debug) {
coplanarPoint = new THREE.Vector3();
planeGeometry = new THREE.PlaneGeometry(4.0, 4.0, 1.0, 1.0);
rightCutPlane.coplanarPoint(coplanarPoint);
planeGeometry.lookAt(rightCutPlane.normal);
planeGeometry.translate(coplanarPoint.x, coplanarPoint.y, coplanarPoint.z);
planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
this.el.sceneEl.setObject3D('rightCutPlane', planeMesh);
planeGeometry = new THREE.PlaneGeometry(4.0, 4.0, 1.0, 1.0);
rightBorderOuterPlane.coplanarPoint(coplanarPoint);
planeGeometry.lookAt(rightBorderOuterPlane.normal);
planeGeometry.translate(coplanarPoint.x, coplanarPoint.y, coplanarPoint.z);
parallelPlaneMesh = new THREE.Mesh(planeGeometry, parallelPlaneMaterial);
this.el.sceneEl.setObject3D('planeParallel', parallelPlaneMesh);
}
this.blockEl.object3D.visible = false;
this.partRightEl.getObject3D('mesh').material.clippingPlanes = [rightCutPlane];
this.cutRightEl.getObject3D('mesh').material.clippingPlanes = [rightBorderOuterPlane, rightBorderInnerPlane];
this.partLeftEl.getObject3D('mesh').material.clippingPlanes = [leftCutPlane];
this.cutLeftEl.getObject3D('mesh').material.clippingPlanes = [leftBorderInnerPlane, leftBorderOuterPlane];
for (i = 0; i < 6; i++) {
this.beatCollidersEls[i].setAttribute('visible', false);
}
this.partLeftEl.object3D.visible = true;
this.partRightEl.object3D.visible = true;
this.el.sceneEl.renderer.localClippingEnabled = true;
this.destroyed = true;
this.el.emit('beatdestroyed');
this.rotationAxis = this.rightCutPlanePoints[0].clone().sub(this.rightCutPlanePoints[1]);
this.returnToPoolTimer = 800;
//this.el.sceneEl.components['json-particles__hit'].explode(this.el.object3D.position, rightCutPlane.normal, direction, this.data.color);
}
})(),
pause: function () {
this.el.object3D.visible = false;
this.partLeftEl.object3D.visible = false;
this.partRightEl.object3D.visible = false;
},
play: function () {
this.destroyed = false;
this.el.object3D.visible = true;
this.blockEl.object3D.visible = true;
},
initCuttingClippingPlanes: function () {
this.leftCutPlanePointsWorld = [
new THREE.Vector3(),
new THREE.Vector3(),
new THREE.Vector3()
];
this.rightCutPlanePointsWorld = [
new THREE.Vector3(),
new THREE.Vector3(),
new THREE.Vector3()
];
this.rightCutPlane = new THREE.Plane();
this.rightBorderOuterPlane = new THREE.Plane();
this.rightBorderInnerPlane = new THREE.Plane();
this.leftCutPlane = new THREE.Plane();
this.leftBorderOuterPlane = new THREE.Plane();
this.leftBorderInnerPlane = new THREE.Plane();
},
generateCutClippingPlanes: function () {
var leftCutPlanePointsWorld = this.leftCutPlanePointsWorld;
var rightCutPlanePointsWorld = this.rightCutPlanePointsWorld;
var rightCutPlane = this.rightCutPlane;
var rightBorderOuterPlane = this.rightBorderOuterPlane;
var rightBorderInnerPlane = this.rightBorderInnerPlane;
var leftCutPlane = this.leftCutPlane;
var leftBorderOuterPlane = this.leftBorderOuterPlane;
var leftBorderInnerPlane = this.leftBorderInnerPlane;
var partLeftEl = this.partLeftEl;
var partRightEl = this.partRightEl;
partRightEl.object3D.updateMatrixWorld();
partRightEl.object3D.localToWorld(rightCutPlanePointsWorld[0].copy(this.rightCutPlanePoints[0]));
partRightEl.object3D.localToWorld(rightCutPlanePointsWorld[1].copy(this.rightCutPlanePoints[1]));
partRightEl.object3D.localToWorld(rightCutPlanePointsWorld[2].copy(this.rightCutPlanePoints[2]));
partLeftEl.object3D.updateMatrixWorld();
partLeftEl.object3D.localToWorld(leftCutPlanePointsWorld[0].copy(this.leftCutPlanePoints[0]));
partLeftEl.object3D.localToWorld(leftCutPlanePointsWorld[1].copy(this.leftCutPlanePoints[1]));
partLeftEl.object3D.localToWorld(leftCutPlanePointsWorld[2].copy(this.leftCutPlanePoints[2]));
rightCutPlane.setFromCoplanarPoints(rightCutPlanePointsWorld[0], rightCutPlanePointsWorld[1], rightCutPlanePointsWorld[2]);
rightBorderOuterPlane.set(rightCutPlane.normal, rightCutPlane.constant + this.cutThickness);
leftCutPlane.setFromCoplanarPoints(leftCutPlanePointsWorld[0], leftCutPlanePointsWorld[1], leftCutPlanePointsWorld[2]);
leftBorderOuterPlane.set(leftCutPlane.normal, leftCutPlane.constant + this.cutThickness);
rightBorderInnerPlane.setFromCoplanarPoints(rightCutPlanePointsWorld[2], rightCutPlanePointsWorld[1], rightCutPlanePointsWorld[0]);
leftBorderInnerPlane.setFromCoplanarPoints(leftCutPlanePointsWorld[2], leftCutPlanePointsWorld[1], leftCutPlanePointsWorld[0]);
},
returnToPool: function () {
var poolName;
var type;
if (!this.backToPool) { return; }
type = this.data.type;
poolName = 'pool__beat-' + type;
if (type !== 'mine') { poolName += '-' + this.data.color; }
this.el.sceneEl.components[poolName].returnEntity(this.el);
this.el.pause();
},
tock: (function () {
var rightCutNormal = new THREE.Vector3();
var leftCutNormal = new THREE.Vector3();
var leftRotation = 0;
var rightRotation = 0;
var rotationStep = 2 * Math.PI / 150;
return function (time, timeDelta) {
var i;
var saberEls = this.saberEls;
var boundingBox;
var saberBoundingBox;
var plane;
if (!this.destroyed) {
if (!this.correctBeatColliderEl.getObject3D('mesh')) { return; }
boundingBox = this.boundingBox.setFromObject(this.correctBeatColliderEl.getObject3D('mesh'));
for (i = 0; i < saberEls.length; i++) {
saberBoundingBox = saberEls[i].components['saber-controls'].boundingBox;
if (boundingBox && saberBoundingBox && saberBoundingBox.intersectsBox(boundingBox)) {
this.destroyBeat(saberEls[i]);
break;
}
}
this.el.object3D.position.z += this.data.speed * (timeDelta / 1000);
this.backToPool = this.el.object3D.position.z >= 2;
} else {
rightCutNormal.copy(this.rightCutPlane.normal).multiplyScalar((this.data.speed / 2) * (timeDelta / 500));
this.partRightEl.object3D.position.add(rightCutNormal);
this.partRightEl.object3D.setRotationFromAxisAngle(this.rotationAxis, rightRotation);
rightRotation = rightRotation >= 2 * Math.PI ? 0 : rightRotation + rotationStep;
leftCutNormal.copy(this.leftCutPlane.normal).multiplyScalar((this.data.speed / 2) * (timeDelta / 500));
this.partLeftEl.object3D.position.add(leftCutNormal);
this.partLeftEl.object3D.setRotationFromAxisAngle(this.rotationAxis, leftRotation);
leftRotation = leftRotation >= 2 * Math.PI ? 0 : leftRotation + rotationStep;
this.generateCutClippingPlanes();
this.returnToPoolTimer -= timeDelta;
this.backToPool = this.returnToPoolTimer <= 0;
}
this.returnToPool();
};
})()
});

View File

@@ -18,12 +18,19 @@
effect-bloom="strength: 1"
intro-song
overlay="objects: #menu, #keyboard, #rightHand, #leftHand, [mixin~='cursorMesh']"
pool__beat-arrow-blue="mixin: beat arrow-blue-beat; size: 5; container: #beatContainer"
pool__beat-arrow-red="mixin: beat arrow-red-beat; size: 10; container: #beatContainer"
pool__beat-dot-blue="mixin: beat dot-blue-beat; size: 10; container: #beatContainer"
pool__beat-dot-red="mixin: beat dot-red-beat; size: 10; container: #beatContainer"
proxy-event="event: menuchallengeselect; to: #searchResultsContainer, #menuDifficultiesGroup"
search
stage-colors="blue"
fog="color: #a00; density: 0.035; type: exponential">
fog="color: #a00; density: 0.035; type: exponential" debug>
<a-assets timeout="10000">
<a-asset-item id="arrow-obj" src="assets/models/arrow.obj"></a-asset-item>
<a-asset-item id="backglow-obj" src="assets/models/backglow.obj"></a-asset-item>
<a-asset-item id="beat-obj" src="assets/models/beat.obj"></a-asset-item>
<a-asset-item id="dot-obj" src="assets/models/dot.obj"></a-asset-item>
<a-asset-item id="logofront-obj" src="assets/models/logofront.obj"></a-asset-item>
<a-asset-item id="logoback-obj" src="assets/models/logoback.obj"></a-asset-item>
<a-asset-item id="logofront-u-obj" src="assets/models/logofront-u.obj"></a-asset-item>
@@ -36,6 +43,7 @@
<img id="backglowTexture" src="assets/img/stage/backglow.png">
<img id="cursorMeshImg" src="assets/models/laser/laser.png">
<img id="downIconImg" src="assets/img/downIcon.png">
<img id="envmapTexture" src="assets/img/envMap.png"></img>
<img id="floorImg" src="assets/img/stage/floor.png">
<img id="gridImg" src="assets/img/grid.png">
<img id="playImg" src="assets/img/play.png">
@@ -47,8 +55,15 @@
<a-mixin id="stageLaser" geometry="height: 300; depth: 0.16; width: 0.16" materials="neon"></a-mixin>
<a-mixin id="font" text="font: assets/fonts/Teko-Bold.json; shader: msdf; letterSpacing: 1"></a-mixin>
<a-mixin id="textFont" text="font: assets/fonts/Teko-Bold.json; shader: msdf; letterSpacing: 1"></a-mixin>
<a-mixin id="beat" beat></a-mixin>
<a-mixin id="arrow-blue-beat" beat="color: blue; type: arrow"></a-mixin>
<a-mixin id="arrow-red-beat" beat="color: red; type: arrow"></a-mixin>
<a-mixin id="dot-blue-beat" beat="color: blue; type: dot"></a-mixin>
</a-assets>
<a-entity id="beatContainer"></a-entity>
<a-entity id="cursorLaser" obj-model="obj: #laserObj" visible="false"></a-entity>
<a-entity id="container">