title: A-FRAME Extend component
body: <p>Consider this:</p>
  
  <pre><code>&lt;a-entity id="player"&gt;
      &lt;a-entity id="camera" camera look-controls&gt;&lt;/a-entity&gt;
      &lt;a-entity id="leftHand" oculus-touch-controls="hand: left"&gt;&lt;/a-entity&gt;
      &lt;a-entity id="rightHand" oculus-touch-controls="hand: right"&gt;&lt;/a-entity&gt;
  &lt;/a-entity&gt;
  </code></pre>
  
  <p>If I want to move my player through the scene, I would add <code>wasd-controls</code> to <code>#player</code>. Doing that, disregards the orientation of the head (<code>camera</code>): <code>W</code> always moves "north", wherever you are looking at. If I add <code>wasd-controls</code> to <code>#camera</code>, the head moves correctly but the controllers are left behind.</p>
  
  <p>So I thought of creating a custom <code>wasd-controls</code>, but I am unable to extend that component. I have been successful copying and pasting all the code, but that is very nasty.</p>
  
  <p>This did not work: <a href="https://stackoverflow.com/questions/41478249/aframe-extend-component-and-override">AFrame extend component and override</a>.</p>
  
  <p>Any idea?</p>
  
  <p><em>my-wasd-controls.js</em></p>
  
  <pre><code>var KEYCODE_TO_CODE = require('aframe/src/constants').keyboardevent.KEYCODE_TO_CODE;
  var AFRAME = require('aframe');
  var THREE = require('aframe/src/lib/three');
  var utils = require('aframe/src/utils');
  
  var bind = utils.bind;
  var shouldCaptureKeyEvent = utils.shouldCaptureKeyEvent;
  
  var CLAMP_VELOCITY = 0.00001;
  var MAX_DELTA = 0.2;
  var KEYS = [
      'KeyW', 'KeyA', 'KeyS', 'KeyD',
      'ArrowUp', 'ArrowLeft', 'ArrowRight', 'ArrowDown'
  ];
  
  /**
   * WASD component to control entities using WASD keys.
   */
  module.exports.Component = AFRAME.registerComponent('my-wasd-controls', {
      schema: {
          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},
          head: {type: 'selector'},
          wsAxis: {default: 'z', oneOf: ['x', 'y', 'z']},
          wsEnabled: {default: true},
          wsInverted: {default: false}
      },
  
      init: function () {
          // To keep track of the pressed keys.
          this.keys = {};
  
          this.position = {};
          this.velocity = new THREE.Vector3();
  
          // Bind methods and add event listeners.
          this.onBlur = bind(this.onBlur, this);
          this.onFocus = bind(this.onFocus, this);
          this.onKeyDown = bind(this.onKeyDown, this);
          this.onKeyUp = bind(this.onKeyUp, this);
          this.onVisibilityChange = bind(this.onVisibilityChange, this);
          this.attachVisibilityEventListeners();
      },
  
      tick: function (time, delta) {
          var currentPosition;
          var data = this.data;
          var el = this.el;
          var movementVector;
          var position = this.position;
          var velocity = this.velocity;
  
          if (!velocity[data.adAxis] &amp;&amp; !velocity[data.wsAxis] &amp;&amp;
              isEmptyObject(this.keys)) { return; }
  
          // Update velocity.
          delta = delta / 1000;
          this.updateVelocity(delta);
  
          if (!velocity[data.adAxis] &amp;&amp; !velocity[data.wsAxis]) { return; }
  
          // Get movement vector and translate position.
          currentPosition = el.getAttribute('position');
          movementVector = this.getMovementVector(delta);
          position.x = currentPosition.x + movementVector.x;
          position.y = currentPosition.y + movementVector.y;
          position.z = currentPosition.z + movementVector.z;
          el.setAttribute('position', position);
      },
  
      remove: function () {
          this.removeKeyEventListeners();
          this.removeVisibilityEventListeners();
      },
  
      play: function () {
          this.attachKeyEventListeners();
      },
  
      pause: function () {
          this.keys = {};
          this.removeKeyEventListeners();
      },
  
      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;
  
          adAxis = data.adAxis;
          wsAxis = data.wsAxis;
  
          // If FPS too low, reset velocity.
          if (delta &gt; 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]) &lt; CLAMP_VELOCITY) { velocity[adAxis] = 0; }
          if (Math.abs(velocity[wsAxis]) &lt; 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.data.head || this.el).getAttribute('rotation');
              var velocity = this.velocity;
              var xRotation;
  
              directionVector.copy(velocity);
              directionVector.multiplyScalar(delta);
  
              // Absolute.
              if (!rotation) { return directionVector; }
  
              xRotation = this.data.fly ? rotation.x : 0;
  
              // Transform direction relative to heading.
              rotationEuler.set(THREE.Math.degToRad(xRotation), THREE.Math.degToRad(rotation.y), 0);
              directionVector.applyEuler(rotationEuler);
              return directionVector;
          };
      })(),
  
      attachVisibilityEventListeners: function () {
          window.addEventListener('blur', this.onBlur);
          window.addEventListener('focus', this.onFocus);
          document.addEventListener('visibilitychange', this.onVisibilityChange);
      },
  
      removeVisibilityEventListeners: function () {
          window.removeEventListener('blur', this.onBlur);
          window.removeEventListener('focus', this.onFocus);
          document.removeEventListener('visibilitychange', this.onVisibilityChange);
      },
  
      attachKeyEventListeners: function () {
          window.addEventListener('keydown', this.onKeyDown);
          window.addEventListener('keyup', this.onKeyUp);
      },
  
      removeKeyEventListeners: function () {
          window.removeEventListener('keydown', this.onKeyDown);
          window.removeEventListener('keyup', this.onKeyUp);
      },
  
      onBlur: function () {
          this.pause();
      },
  
      onFocus: function () {
          this.play();
      },
  
      onVisibilityChange: function () {
          if (document.hidden) {
              this.onBlur();
          } else {
              this.onFocus();
          }
      },
  
      onKeyDown: function (event) {
          var code;
          if (!shouldCaptureKeyEvent(event)) { return; }
          code = event.code || KEYCODE_TO_CODE[event.keyCode];
          if (KEYS.indexOf(code) !== -1) { this.keys[code] = true; }
      },
  
      onKeyUp: function (event) {
          var code;
          code = event.code || KEYCODE_TO_CODE[event.keyCode];
          delete this.keys[code];
      }
  });
  
  function isEmptyObject (keys) {
      var key;
      for (key in keys) { return false; }
      return true;
  }
  </code></pre>