Skip to content

Commit

Permalink
add integration unit tests for wasd controls
Browse files Browse the repository at this point in the history
  • Loading branch information
ngokevin committed Sep 13, 2016
1 parent 25958a2 commit bd7ef27
Show file tree
Hide file tree
Showing 2 changed files with 234 additions and 77 deletions.
184 changes: 107 additions & 77 deletions src/components/wasd-controls.js
Original file line number Diff line number Diff line change
@@ -1,92 +1,75 @@
var registerComponent = require('../core/component').registerComponent;
var bind = require('../utils/bind');
var shouldCaptureKeyEvent = require('../utils/').shouldCaptureKeyEvent;
var KEYCODE_TO_CODE = require('../constants').keyboardevent.KEYCODE_TO_CODE;
var registerComponent = require('../core/component').registerComponent;
var THREE = require('../lib/three');
var utils = require('../utils/');

var bind = utils.bind;
var shouldCaptureKeyEvent = utils.shouldCaptureKeyEvent;

var CLAMP_VELOCITY = 0.00001;
var MAX_DELTA = 0.2;

/**
* WASD component to control entities using WASD keys.
*/
module.exports.Component = registerComponent('wasd-controls', {
schema: {
easing: {default: 20},
acceleration: {default: 65},
adAxis: {default: 'x', oneOf: ['x', 'y', 'z']},
adEnabled: {default: true},
adInverted: {default: false},
easing: {default: 20},
enabled: {default: true},
fly: {default: false},
wsAxis: {default: 'z', oneOf: [ 'x', 'y', 'z' ]},
adAxis: {default: 'x', oneOf: [ 'x', 'y', 'z' ]},
wsInverted: {default: false},
wsAxis: {default: 'z', oneOf: ['x', 'y', 'z']},
wsEnabled: {default: true},
adInverted: {default: false},
adEnabled: {default: true}
wsInverted: {default: false}
},

init: function () {
this.velocity = new THREE.Vector3();
// To keep track of the pressed keys
// To keep track of the pressed keys.
this.keys = {};

this.velocity = new THREE.Vector3();

// Bind methods and add event listeners.
this.onBlur = bind(this.onBlur, this);
this.onFocus = bind(this.onFocus, this);
this.onVisibilityChange = bind(this.onVisibilityChange, this);
this.onKeyDown = bind(this.onKeyDown, this);
this.onKeyUp = bind(this.onKeyUp, this);
this.onVisibilityChange = bind(this.onVisibilityChange, this);
this.attachVisibilityEventListeners();
},

update: function (previousData) {
tick: function (time, delta) {
var data = this.data;
var acceleration = data.acceleration;
var easing = data.easing;
var velocity = this.velocity;
var prevTime = this.prevTime = this.prevTime || Date.now();
var time = window.performance.now();
var delta = (time - prevTime) / 1000;
var keys = this.keys;
var movementVector;
var adAxis = data.adAxis;
var wsAxis = data.wsAxis;
var adSign = data.adInverted ? -1 : 1;
var wsSign = data.wsInverted ? -1 : 1;
var el = this.el;
this.prevTime = time;

// If data changed or FPS too low, reset velocity.
if (previousData || delta > MAX_DELTA) {
velocity[adAxis] = 0;
velocity[wsAxis] = 0;
return;
}

velocity[adAxis] -= velocity[adAxis] * easing * delta;
velocity[wsAxis] -= velocity[wsAxis] * easing * delta;
var movementVector;
var position;
var velocity = this.velocity;

var position = el.getComputedAttribute('position');
// Use seconds.
delta = delta / 1000;

if (data.enabled) {
if (data.adEnabled) {
if (keys.KeyA || keys.ArrowLeft) { velocity[adAxis] -= adSign * acceleration * delta; }
if (keys.KeyD || keys.ArrowRight) { velocity[adAxis] += adSign * acceleration * delta; }
}
if (data.wsEnabled) {
if (keys.KeyW || keys.ArrowUp) { velocity[wsAxis] -= wsSign * acceleration * delta; }
if (keys.KeyS || keys.ArrowDown) { velocity[wsAxis] += wsSign * acceleration * delta; }
}
}
// Get velocity.
this.updateVelocity(delta);
if (!velocity[data.adAxis] && !velocity[data.wsAxis]) { return; }

// Get movement vector and translate position.
movementVector = this.getMovementVector(delta);
el.object3D.translateX(movementVector.x);
el.object3D.translateY(movementVector.y);
el.object3D.translateZ(movementVector.z);

position = el.getComputedAttribute('position');
el.setAttribute('position', {
x: position.x + movementVector.x,
y: position.y + movementVector.y,
z: position.z + movementVector.z
});
},

remove: function () {
this.removeKeyEventListeners();
this.removeVisibilityEventListeners();
},

play: function () {
this.attachKeyEventListeners();
},
Expand All @@ -96,15 +79,77 @@ module.exports.Component = registerComponent('wasd-controls', {
this.removeKeyEventListeners();
},

tick: function (t) {
this.update();
},
updateVelocity: function (delta) {
var acceleration;
var adAxis;
var adSign;
var data = this.data;
var keys = this.keys;
var velocity = this.velocity;
var wsAxis;
var wsSign;

remove: function () {
this.pause();
this.removeVisibilityEventListeners();
adAxis = data.adAxis;
wsAxis = data.wsAxis;

// If FPS too low, reset velocity.
if (delta > MAX_DELTA) {
velocity[adAxis] = 0;
velocity[wsAxis] = 0;
return;
}

// Decay velocity.
if (velocity[adAxis] !== 0) {
velocity[adAxis] -= velocity[adAxis] * data.easing * delta;
}
if (velocity[wsAxis] !== 0) {
velocity[wsAxis] -= velocity[wsAxis] * data.easing * delta;
}

// Clamp velocity easing.
if (Math.abs(velocity[adAxis]) < CLAMP_VELOCITY) { velocity[adAxis] = 0; }
if (Math.abs(velocity[wsAxis]) < CLAMP_VELOCITY) { velocity[wsAxis] = 0; }

if (!data.enabled) { return; }

// Update velocity using keys pressed.
acceleration = data.acceleration;
if (data.adEnabled) {
adSign = data.adInverted ? -1 : 1;
if (keys.KeyA || keys.ArrowLeft) { velocity[adAxis] -= adSign * acceleration * delta; }
if (keys.KeyD || keys.ArrowRight) { velocity[adAxis] += adSign * acceleration * delta; }
}
if (data.wsEnabled) {
wsSign = data.wsInverted ? -1 : 1;
if (keys.KeyW || keys.ArrowUp) { velocity[wsAxis] -= wsSign * acceleration * delta; }
if (keys.KeyS || keys.ArrowDown) { velocity[wsAxis] += wsSign * acceleration * delta; }
}
},

getMovementVector: (function () {
var directionVector = new THREE.Vector3(0, 0, 0);
var rotationEuler = new THREE.Euler(0, 0, 0, 'YXZ');

return function (delta) {
var rotation = this.el.getComputedAttribute('rotation');
var velocity = this.velocity;

directionVector.copy(velocity);
directionVector.multiplyScalar(delta);

// Absolute.
if (!rotation) { return directionVector; }

if (!this.data.fly) { rotation.x = 0; }

// Transform direction relative to heading.
rotationEuler.set(THREE.Math.degToRad(rotation.x), THREE.Math.degToRad(rotation.y), 0);
directionVector.applyEuler(rotationEuler);
return directionVector;
};
})(),

attachVisibilityEventListeners: function () {
window.addEventListener('blur', this.onBlur);
window.addEventListener('focus', this.onFocus);
Expand Down Expand Up @@ -144,31 +189,16 @@ module.exports.Component = registerComponent('wasd-controls', {
},

onKeyDown: function (event) {
var code;
if (!shouldCaptureKeyEvent(event)) { return; }
var code = event.code || KEYCODE_TO_CODE[event.keyCode];
code = event.code || KEYCODE_TO_CODE[event.keyCode];
this.keys[code] = true;
},

onKeyUp: function (event) {
var code;
if (!shouldCaptureKeyEvent(event)) { return; }
var code = event.code || KEYCODE_TO_CODE[event.keyCode];
code = event.code || KEYCODE_TO_CODE[event.keyCode];
this.keys[code] = false;
},

getMovementVector: (function (delta) {
var direction = new THREE.Vector3(0, 0, 0);
var rotation = new THREE.Euler(0, 0, 0, 'YXZ');
return function (delta) {
var velocity = this.velocity;
var elRotation = this.el.getComputedAttribute('rotation');
direction.copy(velocity);
direction.multiplyScalar(delta);
if (!elRotation) { return direction; }
if (!this.data.fly) { elRotation.x = 0; }
rotation.set(THREE.Math.degToRad(elRotation.x),
THREE.Math.degToRad(elRotation.y), 0);
direction.applyEuler(rotation);
return direction;
};
})()
}
});
127 changes: 127 additions & 0 deletions tests/core/controls.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* global Event, assert, process, setup, suite, test */
var helpers = require('../helpers');

suite('position controls on camera with WASD controls (integration unit test)', function () {
setup(function (done) {
var el = helpers.entityFactory();
var self = this;

el.addEventListener('loaded', function () {
el.sceneEl.addEventListener('camera-set-active', function () {
self.el = el.sceneEl.querySelector('[camera]'); // Default camera.
process.nextTick(function () { done(); });
});
});
});

test('w moves forward', function (done) {
var el = this.el;
var keydownEvent;
var position;

position = el.getAttribute('position');
keydownEvent = new Event('keydown');
keydownEvent.code = 'KeyW';
window.dispatchEvent(keydownEvent);

process.nextTick(function () {
el.sceneEl.tick(20, 20);
process.nextTick(function () {
var newPos = el.getAttribute('position');
assert.equal(newPos.x, position.x);
assert.equal(newPos.y, position.y);
assert.ok(newPos.z < position.z, 'Translated forward');
done();
});
});
});

test('a strafes left', function (done) {
var el = this.el;
var keydownEvent;
var position;

position = el.getAttribute('position');
keydownEvent = new Event('keydown');
keydownEvent.code = 'KeyA';
window.dispatchEvent(keydownEvent);

process.nextTick(function () {
el.sceneEl.tick(20, 20);
process.nextTick(function () {
var newPos = el.getAttribute('position');
assert.ok(newPos.x < position.x, 'Strafed left');
assert.equal(newPos.y, position.y);
assert.equal(newPos.z, position.z);
done();
});
});
});

test('s moves backwards', function (done) {
var el = this.el;
var keydownEvent;
var position;

position = el.getAttribute('position');
keydownEvent = new Event('keydown');
keydownEvent.code = 'KeyS';
window.dispatchEvent(keydownEvent);

process.nextTick(function () {
el.sceneEl.tick(20, 20);
process.nextTick(function () {
var newPos = el.getAttribute('position');
assert.equal(newPos.x, position.x);
assert.equal(newPos.y, position.y);
assert.ok(newPos.z > position.z, 'Translated backwards');
done();
});
});
});

test('d strafes right', function (done) {
var el = this.el;
var keydownEvent;
var position;

position = el.getAttribute('position');
keydownEvent = new Event('keydown');
keydownEvent.code = 'KeyD';
window.dispatchEvent(keydownEvent);

process.nextTick(function () {
el.sceneEl.tick(20, 20);
process.nextTick(function () {
var newPos = el.getAttribute('position');
assert.ok(newPos.x > position.x, 'Strafed right');
assert.equal(newPos.y, position.y);
assert.equal(newPos.z, position.z);
done();
});
});
});

test('moves relative to heading', function (done) {
var el = this.el;
var keydownEvent;
var position;

el.setAttribute('rotation', '0 90 0');
position = el.getAttribute('position');
keydownEvent = new Event('keydown');
keydownEvent.code = 'KeyW';
window.dispatchEvent(keydownEvent);

process.nextTick(function () {
el.sceneEl.tick(20, 20);
process.nextTick(function () {
var newPos = el.getAttribute('position');
assert.ok(newPos.x < position.x, 'Turned left and moved forward');
assert.equal(newPos.y, position.y);
assert.equal(Math.round(newPos.z), position.z);
done();
});
});
});
});

0 comments on commit bd7ef27

Please sign in to comment.