Monday, January 28, 2013

Constrained Motion in Physijs

‹prev | My Chain | next›

Up tonight, I switch to a new Three.js / Physijs potential game for 3D Game Programming for Kids. This will be another 2D game, so I copy bits and pieces of last night's platform game.

In this game, the player will click, drag and rotate platforms to help the player reach a goal, normally near the top of the screen. The player itself can only move forward or backward, so ramps will be needed to move up.

The player is a simple Physijs object, shaped like a disc:
  var player = new Physijs.ConvexMesh(
    new THREE.CylinderGeometry(30, 30, 5, 16),
      Physijs.createMaterial(
        new THREE.MeshBasicMaterial({color:0xbb0000}), 0.2, 0.5
      )
  );

  player.setAngularFactor(new THREE.Vector3( 0, 0, 0 )); // don't rotate
  player.setLinearFactor(new THREE.Vector3( 1, 1, 0 )); // only move on X and Y axis
Since this is a 2D, not three dimensional game, I constrain movement to the X-Y plane. As with the platformer game, I use simple keyboard events to move the player:
  document.addEventListener("keydown", function(event) {
    var code = event.keyCode;
    if (code == 37) left();  // left arrow
    if (code == 39) right(); // right arrow
  });
The left and right functions set a maximum velocity in the appropriate direction.

All of that is more or less a copy from the previous game. What is new is that I need four borders that constrain the player to the current screen. Since the player is restricted to the X-Y plane, all four borders are added with Z=0. What changes is the X-Y position of each and the size:
  function makeBorder(x, y, w, h)  {
    var border = new Physijs.BoxMesh(
      new THREE.CubeGeometry(w, h, 100),
      Physijs.createMaterial(
        new THREE.MeshBasicMaterial({color: 0x000000}), 0.2, 1.0    
      ),
      0 
    );
    border.position.set(x, y, 0);
    return border;
  }
I think I might do this chapter after the introduction to JavaScript objects, in which case I might fiddle with default options. For now I require all four arguments.

With that function, and the browser width and height already calculated, I have my borders with:
  scene.add(makeBorder(width/-2, 0,         50,    height));
  scene.add(makeBorder(width/2,  0,         50,    height));
  scene.add(makeBorder(0,        height/-2, width, 50));
  scene.add(makeBorder(0,        height/2,  width, 50));
As for the ramps, I define a similar generator function. In this case, the shape will be fixed, but the rotation can change:
  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;
  }
  scene.add(makeRamp(0.4*width/2, -height/2 + 25, -Math.PI/4));
  scene.add(makeRamp(-0.3*width/2, 0, Math.PI/3));
The last thing that I do tonight is to create a goal for the game. I add a hoop to the top-left corner of the window:
  var goal = new Physijs.ConvexMesh(
    new THREE.TorusGeometry(90, 5, 5, 16),
    Physijs.createMaterial(
      new THREE.MeshBasicMaterial({color:0x00bb00}), 0.2, 0.5
    ),
    0
  );
  goal.position.set(width/-2, height/2, 0);
  goal.isGoal = true;
  scene.add(goal);
The isGoal property is an easy way for the collision detection to determine how (or if) to handle the event:
  player.addEventListener('collision', function(object) {
    console.log('Collision!');
    if (object.isGoal) gameOver();
  });
With that, I have a fairly functional game:


The code so far is available if anyone cares to try their luck. Up tomorrow, I will get started trying to figure out how best to expose the ability to move those ramps around the game area.


Day #644

No comments:

Post a Comment