Search code examples
aframe

Handling GearVR Controller events in AFrame


So, I should start out with the warning that I'm new to AFrame, (though not to programming, game programming, or Javascript). I'm currently trying to put together a simple scene with basic GearVR controller interaction.

When I say "basic interaction", I mean I have the little starter scene that you get when you follow the official tutorial, and I'm just trying to expand that out, such that when you press the trigger, (and specifically the trigger, not just any button), the little text component has it's text value changed.

To make a long story short -- my code isn't working. Despite the fact that I add the event listener in the component init function, (and I've confirmed that init is being called), the listener callback never seems to be invoked. If I had to guess, I'd say that the entity I've attached 'laser-controls' to isn't emitting the 'triggerdown' event, but I'm not sure how to confirm that. Here are the relevant pieces:

AFRAME.registerComponent('gearvr-trigger', {
  schema: {
    textValue: {
      default: "Hello WebVR!"
    }
  },

  init: function() {
    var message_box = document.querySelector('#message');
    var el = this.el;

    el.addEventListener('triggerdown', function(evt) {
      message_box.setAttribute('value', this.data.textValue);
    });

  }


});
  <a-text id='message' value="Hello, A-Frame!" color="#BBB"
    position="-0.9 0.2 -3" scale="1.5 1.5 1.5">
  </a-text>


<a-box gearvr-trigger='textValue: "New Text Value";' src='#boxTexture' position='0 2 -5' rotation='0 45 45' scale='2 2 2'>
  <a-animation attribute='position' to='0 2.5 -5' direction='alternate' dur='2000' repeat='indefinite'></a-animation>
  <a-animation attribute="scale" begin="mouseenter" dur="300" to="2.3 2.3 2.3"></a-animation>
  <a-animation attribute="scale" begin="mouseleave" dur="300" to="2 2 2"></a-animation>
  <a-animation attribute="rotation" begin="click" dur="2000" to="360 405 45"></a-animation>
</a-box>
<a-entity raycaster='far: 100; showLine: true;' line='color: red;' laser-controls></a-entity>

If someone with more experience than me sees what I'm doing wrong, that would be great, but I'd also be happy if you could just point me at some decent code examples of responding to GearVR controller events.


Solution

  • As others have noted, the reason you're not picking up the 'triggerdown' events in your custom component is that these events are emitted by the controller entity (a-entity[laser-controls] in this case), and they bubble just as in the standard DOM, (which would be next to a-scene in this case), so a-box is not seeing them.

    When you combine information about controller buttons and the controller's relation to other entities in the scene (e.g. pointing with a laser or collisions), I like to call that a gesture. The laser-controls component provides basic gesture interpretation, but I have created the super-hands package for rich gesture interpretation including what you require: button discrimination.

    <html>
    <head>
    <script src="https://aframe.io/releases/0.7.1/aframe.min.js"></script>
    <script src="https://unpkg.com/super-hands/dist/super-hands.min.js"></script>
    <script>
    AFRAME.registerComponent('gearvr-trigger', {
      schema: {
        textValue: {
          default: "Hello WebVR!"
        }
      },
    
      init: function() {
        var message_box = document.querySelector('#message');
        var el = this.el;
        var triggerResponse = function (evt) {
          if (evt.detail.state === 'clicked') {
            message_box.setAttribute('value', this.data.textValue);
          }
        }
        el.addEventListener('stateadded', triggerResponse.bind(this));
    
      }
    
    });
    </script>
    </head>
    <body>
    <a-scene>
    <a-assets>
      <a-mixin id="point-laser" raycaster='far: 100; showLine: true;' line='color: red;'></a-mixin>
    </a-assets>
    <a-text id='message' value="Hello, A-Frame!" color="#BBB"
        position="-0.9 0.2 -3" scale="1.5 1.5 1.5">
      </a-text>
    
    
    <a-box clickable="startButtons: triggerdown, mousedown; endButtons: triggerup, mouseup" gearvr-trigger='textValue: "New Text Value";' src='#boxTexture' position='0 2 -5' rotation='0 45 45' scale='2 2 2'>
      <a-animation attribute='position' to='0 2.5 -5' direction='alternate' dur='2000' repeat='indefinite'></a-animation>
      <a-animation attribute="scale" begin="hover-start" dur="300" to="2.3 2.3 2.3"></a-animation>
      <a-animation attribute="scale" begin="hover-end" dur="300" to="2 2 2"></a-animation>
      <a-animation attribute="rotation" begin="grab-end" dur="2000" to="360 405 45"></a-animation>
    </a-box>
    <a-entity progressive-controls="maxLevel: point; pointMixin: point-laser" ></a-entity>
    </a-scene>
    </body>
    </html>

    For the controller, progressive-controls auto-detects the controller and sets up the laser pointer like laser-controls, but using super-hands gesture interpretation (it also provides cursor-like input for desktop/cardboard users).

    <a-entity progressive-controls="maxLevel: point; pointMixin: point-laser"></a-entity>
    

    For the target entity, clickable activates it as a gesture receiving component and defines which button events it will accept (you can remove the mouse ones if you don't want cross-platform support). The animation trigger events were also updatedaa to work with super-hands.

    <a-box clickable="startButtons: triggerdown, mousedown; endButtons: triggerup, mouseup" gearvr-trigger='textValue: "New Text Value";' ...>
    ...
    <a-animation attribute="scale" begin="hover-start" dur="300" to="2.3 2.3 2.3"></a-animation>
    <a-animation attribute="scale" begin="hover-end" dur="300" to="2 2 2"></a-animation>
    <a-animation attribute="rotation" begin="grab-end" dur="2000" to="360 405 45"></a-animation>
    

    For your custom reaction component, I changed it to react to state changes, which is how clickable communicates that is has received an acceptable gesture. I also resolved a scoping issue that you would have caught once you had the events triggering this.

        var triggerResponse = function (evt) {
          if (evt.detail.state === 'clicked') {
            message_box.setAttribute('value', this.data.textValue);
          }
        }
        el.addEventListener('stateadded', triggerResponse.bind(this));