Friday, February 1, 2013

OO Click and Drag Three.js Ramps

‹prev | My Chain | next›

As the the beta for 3D Game Programming for Kids draws near, I have been reviewing the content. One thing that I realized was that that the game that I have been building is tentatively scheduled to go after the JavaScript objects introduction. I do not plan on going heavy with the object oriented coding for this book, but it still seems like something that I have to at least touch on.

With that in mind, I should try to find some opportunity for object oriented coding in the platformer game that I have been trying to build:


My initial thought is to convert the ramps from their current functional approach:
  function makeRamp(x, y, rotation) {
    var ramp = new Physijs.ConvexMesh(
      new THREE.CubeGeometry(50, 500, 10),
      Physijs.createMaterial(
        new THREE.MeshBasicMaterial({color:0x0000cc}), 0.2, 1.0
      ),
      0
    );
    ramp.rotation.set(0, 0, rotation);
    ramp.position.set(x, y, 0);
    return ramp;
  }
  var ramp1 = makeRamp(0.4*width/2, -height/2 + 25, -Math.PI/4);
  scene.add(ramp1);
  var ramp2 = makeRamp(-0.3*width/2, 0, Math.PI/3);
  scene.add(ramp2);
This can be rewritten as:
  var Ramp = function(x, y, rotation) {
    this.mesh = new Physijs.ConvexMesh(
      new THREE.CubeGeometry(50, 500, 10),
      Physijs.createMaterial(
        new THREE.MeshBasicMaterial({color:0x0000cc}), 0.2, 1.0
      ),
      0
    );
    this.setPosition(x,y);
    this.setRotation(rotation);
  };
  
  Ramp.prototype.setRotation = function(rotation) {
    this.mesh.rotation.set(0,0,rotation);  
  };
  
  Ramp.prototype.setPosition = function(x, y) {
    this.mesh.position.set(x, y, 0);  
  };
This class can then be invoked in much the same way as the factory function:
  var ramp1 = new Ramp(0.4*width/2, -height/2 + 25, -Math.PI/4);
  scene.add(ramp1.mesh);
  var ramp2 = new Ramp(-0.3*width/2, 0, Math.PI/3);
  scene.add(ramp2.mesh);
Admittedly, that is not a significant reduction in the amount of code. It is a bit cleaner. What can really improve in this approach is the event handling. Previously, I had to rely on document event handlers:
  var active_ramp, mouse_x, mouse_y;
  document.addEventListener("mousedown", function(event) {
    //console.log(event.clientX);
    //console.log(event.clientY);

    mouse_x = event.clientX;
    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(ramp1);
    if (intersects.length > 0) active_ramp = ramp1;
    intersects = ray.intersectObject(ramp2);
    if (intersects.length > 0) active_ramp = ramp2;    
  });
The disadvantage of this approach is the need to manually check all ramps. With an OO approach, each ramp is responsible for checking if it has been clicked:
  Ramp.prototype.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);
  };
That's not a heck of a lot cleaner, but it does accomplish the goal of forcing each instance to check if it is being clicked. Really, I need to move just about all of that out into a more general click handler (or get the DOM event library working again). The only thing in there that is specific to the ramps being moved around is the isActive setting. I will worry about generalizing another night.

For now, I get it working with some simple event document event listeners in the Ramp constructor:
  var Ramp = function(x, y, rotation) {
    // ...
    var that = this;
    document.addEventListener("mousedown", function(e){that.onClick(e);});
    document.addEventListener("mousemove", function(e){that.onDrag(e);});
    document.addEventListener("mouseup", function(e){that.onDrop(e);});
  };
Ew. I am definitely going to need to rethink this. It does work—I can again click and drag ramps around the screen. But I had hoped to avoid a scoping discussion in a kid's introduction book, but here I am forced to fall back to a that = this scope hack.

I am unsure if this means that I should be writing my own DOM events library or if I ought to go back to retry some of the existing solutions. For now, I call it a night. At least it's a night with a working implementation.

(the code so far)


Day #648

No comments:

Post a Comment