Add scoring system based on stroke angles

This commit is contained in:
Diego Marcos
2018-10-24 18:40:58 -07:00
parent 73ec97eaae
commit e0f19e7f95
3 changed files with 88 additions and 15 deletions

View File

@@ -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.
*/

View File

@@ -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;

View File

@@ -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 => {