Sunday, February 3, 2013

Working with JavaScript Prototype and Events

‹prev | My Chain | next›

I may have found my strategy to introduce kids to object-oriented programming in 3D Game Programming for Kids. Sticking with just the prototypical nature of JavaScript looks promising. There are still a few concerns, however. The concern that I would like to address today is event handling.

I continue to work with "ramps" in a game that I am prototyping:


So far, I allow the mouse to click and drag these ramp, though the event handlers are a bit bulky. The onClick handler, for instance, looks like:
  var ramp = {
    // ...
    onClick: function(event) {
      this.mouse_x = event.clientX;
      this.mouse_y = event.clientY;
      var position = new THREE.Vector3(
        event.clientX - width/2,
        height/2 - event.clientY,
        500
      );
      var vector = new THREE.Vector3(0, 0, -1);
      var ray = new THREE.Ray(position, vector);
      var intersects = ray.intersectObject(this.mesh);
      this.isActive = (intersects.length > 0);
    },
    // ...
  };
Nearly all of that code is responsible for determining if a click has occurred. The only thing that needs to occur on a click is to mark it as active. So ideally, my onClick handler would look like:
  var ramp = {
    // ...
    onClick: function(event) {
      this.isActive =  true;
    },
    // ...
  };
This property can then be used in the onMouseMove handler to decide whether or not to move the current ramp. Really, I could even move isActive into a library that supports draggging, but I will leave that for another day.

I define a function that adds event listeners to the prototype of the supplied object:
  var mouse_objects = [];
  function addMouseHandlers(object) {
    mouse_objects.push(object);
    if (mouse_objects.length > 1) return;
    
    function rendererXY(event) {
      return {
        x: event.clientX - window.innerWidth/2,
        y: window.innerHeight/2 - event.clientY
      };
    }
    
    document.addEventListener("mousedown", function(e){  
      var click_pos = rendererXY(e),
          position = new THREE.Vector3(click_pos.x, click_pos.y, 500),
          vector = new THREE.Vector3(0, 0, -1),
          ray = new THREE.Ray(position, vector);

      mouse_objects.forEach(function(o){
        if (!o.onClick) return;
        if (!o.mesh) return;
        if (ray.intersectObject(this.mesh).length === 0) return;
        o.onClick({x:x, y:y});
      });
    });

    document.addEventListener("mousemove", function(e){
      // ...
    });
    document.addEventListener("mouseup", function(e){
     // ...
    });
  }
That more or less works, but it requires the supplied object to conform to some fairly strict properties. The object must have a mesh (to see if the click intersected with the Three.js object) and it must define an old-style onClick handler.

I don't know that I can make this much cleaner — at least without switching to a more classical inheritance. I call it a night here so that I can reconsider this approach.


Day #650

2 comments:

  1. How about a library method that takes an object and an event and determines intersection. The object would be required to support a "boundingBox" method. Then any object can say:

    onClick: function(event) {
    this.isActive = intersects(this, event);
    }

    Although, a better approach may be to have a ClickWatcher object that has a reference to the objects on the screen. It handles clicks and then looks for the object that was clicked and calls its onClick. That's more like how the dom works.

    ReplyDelete
    Replies
    1. I tried toying with that idea, but I find I'm fighting a losing battle in my attempts to keep `this` out of the book: http://japhr.blogspot.com/2013/02/think-of-children-avoid-this.html. I may need to abandon the idea of using objects for these ramps (or anything that uses events).

      Delete