diff --git a/src/components/beat.js b/src/components/beat.js index b17223a..7fc95c2 100644 --- a/src/components/beat.js +++ b/src/components/beat.js @@ -48,6 +48,7 @@ AFRAME.registerComponent('beat', { this.cutDirection = new THREE.Vector3(); this.destroyed = false; this.gravityVelocity = 0; + this.hitEventDetail = {}; this.hitBoundingBox = new THREE.Box3(); this.poolName = undefined; this.returnToPoolTimer = 800; @@ -74,6 +75,8 @@ AFRAME.registerComponent('beat', { this.saberColors = {right: 'blue', left: 'red'}; + this.onEndStroke = this.onEndStroke.bind(this); + this.initBlock(); this.initColliders(); if (this.data.type === 'mine') { @@ -546,6 +549,7 @@ AFRAME.registerComponent('beat', { }, checkCollisions: function () { + const cutDirection = this.data.cutDirection; const saberColors = this.saberColors; const saberEls = this.saberEls; const hitBoundingBox = this.hitColliderEl && this.hitBoundingBox.setFromObject( @@ -555,15 +559,29 @@ AFRAME.registerComponent('beat', { for (let i = 0; i < saberEls.length; i++) { let saberBoundingBox = saberEls[i].components['saber-controls'].boundingBox; + let saberControls; + let maxAngle; if (!saberBoundingBox) { break; } const hand = saberEls[i].getAttribute('saber-controls').hand; if (hitBoundingBox && saberBoundingBox.intersectsBox(hitBoundingBox)) { - if (saberEls[i].components['saber-controls'].swinging && - this.data.color === saberColors[hand]) { - this.el.emit('beathit', null, true); - this.el.emit(`beathit${hand}`, null, true); + if (saberEls[i].components['saber-controls'].swinging && this.data.color === saberColors[hand]) { + saberControls = saberEls[i].components['saber-controls']; + this.hitHand = hand; + this.hitSaberEl = saberEls[i]; + this.hitSaberEl.addEventListener('strokeend', this.onEndStroke); + if (cutDirection === 'up' || cutDirection === 'down') { + maxAngle = saberControls.maxAnglePlaneX; + } else if (cutDirection === 'left' || cutDirection === 'right') { + maxAngle = saberControls.maxAnglePlaneY; + } else { + maxAngle = saberControls.maxAnglePlaneXY; + } + this.angleBeforeHit = maxAngle; + saberControls.maxAnglePlaneX = 0; + saberControls.maxAnglePlaneY = 0; + saberControls.maxAnglePlaneXY = 0; } else { this.wrongHit(hand); } @@ -590,8 +608,15 @@ AFRAME.registerComponent('beat', { if (this.data.type === 'dot' && saberEls[i].components['saber-controls'].swinging && this.data.color === saberColors[hand]) { - this.el.emit('beathit', null, true); - this.el.emit(`beathit${hand}`, null, true); + this.hitSaberEl = saberEls[i]; + this.hitSaberEl.addEventListener('strokeend', this.onEndStroke); + saberControls = saberEls[i].components['saber-controls']; + maxAngle = Math.max(saberControls.maxAnglePlaneX, saberControls.maxAnglePlaneY, saberControls.maxAnglePlaneXY); + this.hitHand = hand; + this.angleBeforeHit = maxAngle; + saberControls.maxAnglePlaneX = 0; + saberControls.maxAnglePlaneY = 0; + saberControls.maxAnglePlaneXY = 0; } else { this.wrongHit(hand); } @@ -600,6 +625,28 @@ AFRAME.registerComponent('beat', { } }, + onEndStroke: function () { + var saberControls = this.hitSaberEl.components['saber-controls']; + var maxAngle; + var cutDirection = this.data.cutDirection; + var hitEventDetail = this.hitEventDetail; + if (cutDirection === 'up' || cutDirection === 'down') { + maxAngle = saberControls.maxAnglePlaneX; + } else if (cutDirection === 'left' || cutDirection === 'right') { + maxAngle = saberControls.maxAnglePlaneY; + } else { + maxAngle = saberControls.maxAnglePlaneXY; + } + hitEventDetail.angleBeforeHit = this.angleBeforeHit * 180 / Math.PI; + hitEventDetail.angleAfterHit = maxAngle * 180 / Math.PI; + console.log("MAX ANGLE BEORE: " + this.angleBeforeHit * 180 / Math.PI); + console.log("MAX ANGLE AFTER: " + maxAngle * 180 / Math.PI); + this.hitSaberEl.removeEventListener('strokeend', this.onEndStroke); + + this.el.emit('beathit', hitEventDetail, true); + this.el.emit(`beathit${this.hitHand}`, null, true); + }, + /** * Destroyed animation. */ diff --git a/src/components/saber-controls.js b/src/components/saber-controls.js index 138bd56..2ec4999 100644 --- a/src/components/saber-controls.js +++ b/src/components/saber-controls.js @@ -17,7 +17,12 @@ AFRAME.registerComponent('saber-controls', { this.boundingBox = new THREE.Box3(); this.bladeEl = el.querySelector('.blade'); this.controllerType = ''; + this.xPlaneNormal = new THREE.Vector3(0, 1, 0); + this.yPlaneNormal = new THREE.Vector3(1, 0, 0); + this.xyPlaneNormal = new THREE.Vector3(1, 1, 0); this.bladeTipPosition = new THREE.Vector3(); + this.bladePosition = new THREE.Vector3(); + this.bladeVector = new THREE.Vector3(); this.bladeTipPreviousPosition = new THREE.Vector3(); this.saberPosition = new THREE.Vector3(); this.swinging = false; @@ -57,13 +62,19 @@ AFRAME.registerComponent('saber-controls', { var startSpeed; var strokeMinSpeed = this.swinging ? startSpeed : this.data.strokeMinSpeed; - if (this.data.hand === 'left') { return; } - // Tip of the blade position in world coordinates. this.bladeTipPosition.set(0, 0.9, 0); + this.bladePosition.set(0, 0, 0); bladeObject = this.el.object3D; bladeObject.parent.updateMatrixWorld(); bladeObject.localToWorld(this.bladeTipPosition); + bladeObject.localToWorld(this.bladePosition); + + // Angles between saber and major planes. + this.bladeVector.copy(this.bladeTipPosition).sub(this.bladePosition).normalize(); + var anglePlaneX = this.bladeVector.angleTo(this.xPlaneNormal); + var anglePlaneY = this.bladeVector.angleTo(this.yPlaneNormal); + var anglePlaneXY = this.bladeVector.angleTo(this.xyPlaneNormal); // Distance covered but the saber tip in one frame. distance = this.bladeTipPreviousPosition.sub(this.bladeTipPosition).length() * 1000000; @@ -75,12 +86,14 @@ AFRAME.registerComponent('saber-controls', { this.distanceSamples.push(distance); this.accumulatedDistance += distance; - // Filter out saber movements that are too slow. - // Too slow or small angle is considered wrong hit. + // Filter out saber movements that are too slow. Too slow or small angle is considered wrong hit. + // Cap stroke to 180 degrees. if (this.accumulatedDistance > this.data.strokeMinSpeed && this.strokeAngle < 180) { - // Calculate angle covered by the saber in one frame. + // Calculate angle covered by the saber in one frame and accumulate. // Trig: Triangle formed by the saber and the linear distance covered by its tip - // Arcsin((distanceCoveredByTip / 2.0) / Length of the blade) + // Arcsin((distanceCoveredByTip / 2.0) / Length of the blade) * 2 + // This is not exact becasuse assumes wrist as pivot point, ignoring elbow and shoulder. + // It seems to work well in practice but might need tweaking. this.strokeAngle += ((Math.asin(((distance / 1000000) / 2.0) / 0.9)) / (2 * Math.PI)) * 360 * 2; // Saber has to move more than strokeMinAngle to consider a swing. @@ -88,11 +101,18 @@ AFRAME.registerComponent('saber-controls', { if (!this.swinging && this.strokeAngle > data.strokeMinAngle) { startSpeed = this.accumulatedDistance; this.swinging = true; + this.maxAnglePlaneX = 0; + this.maxAnglePlaneY = 0; + this.maxAnglePlaneXY = 0; } + this.maxAnglePlaneX = anglePlaneX > this.maxAnglePlaneX ? anglePlaneX : this.maxAnglePlaneX; + this.maxAnglePlaneY = anglePlaneY > this.maxAnglePlaneY ? anglePlaneY : this.maxAnglePlaneY; + this.maxAnglePlaneXY = anglePlaneXY > this.maxAnglePlaneXY ? anglePlaneXY : this.maxAnglePlaneXY; } else { // Stroke finishes. Reset swinging state. if (this.swinging) { - console.log("Angle " + this.strokeAngle); + // console.log("MaxAngle " + this.maxAnglePlaneX * 180 / Math.PI); + this.el.emit('strokeend'); this.swinging = false; this.strokeAngle = 0; this.accumulatedDistance = 0; diff --git a/src/state/index.js b/src/state/index.js index 2057266..f64662b 100644 --- a/src/state/index.js +++ b/src/state/index.js @@ -101,19 +101,25 @@ AFRAME.registerState({ localStorage.setItem('activeHand', state.activeHand); }, - beathit: state => { + beathit: (state, payload) => { + var score = 0; if (state.damage > DAMAGE_DECAY) { state.damage -= DAMAGE_DECAY; } state.score.beatsHit++; - state.score.score += 1000; state.score.combo++; if (state.score.combo > state.score.maxCombo) { state.score.maxCombo = state.score.combo; } + + score += payload.angleBeforeHit >= 90 ? 70 : (payload.angleBeforeHit / 90) * 70; + score += payload.angleAfterHit >= 60 ? 30 : (payload.angleAfterHit / 60) * 30; + state.score.score += Math.floor(score * state.score.multiplier); + state.score.multiplier = state.score.combo >= 8 ? 8 : 2 * Math.floor(Math.log2(state.score.combo)); + // console.log("BEAT SCORE: " + score); }, beatmiss: state => {