Handling collision detection

In this task, we detect if the ball passes through the hoop and we reward the player with scores. We will also create a world boundary and remove balls that are out of bounds.

Prepare for lift off

The detection is done by placing a sensor between the hoop squares. The following figure shows how the sensor is placed:

Prepare for lift off

For the boundary, it will be a long body that is placed at the bottom of the world, as shown in the following figure:

Prepare for lift off

Engage thrusters

Let's add the sensor and handle the collision in the following steps:

  1. First, we append the createHoop method to create a body between the two little hoop squares. We set it as a sensor so that the balls can pass through it:
    physics.createHoop = function() {
      // existing hoop code goes here.
    
      // hoop sensor
      bodyDef.type = b2Body.b2_staticBody;
      bodyDef.position.x = (hoopX+20)/pxPerMeter;
      bodyDef.position.y = hoopY/pxPerMeter;
      bodyDef.angle = 0;
    
      fixDef.isSensor = true;
      fixDef.shape = new b2PolygonShape();
      fixDef.shape.SetAsBox(20/pxPerMeter, 3/pxPerMeter);
    
      body = this.world.CreateBody(bodyDef);
      body.CreateFixture(fixDef);
    };
  2. Then, we set up the contact listener in the following method definition. It registers out its own beginContact method to the Box2D engine:
    physics.setupContactListener = function() {
      // contact
      var contactListener = new Box2D.Dynamics.b2ContactListener;
      contactListener.BeginContact = function(contact, manifold) {
        if (contact.GetFixtureA().IsSensor() || contact.GetFixtureB().IsSensor()) {
          game.increaseScore();
        }
      };
      this.world.SetContactListener(contactListener);
    };
  3. Besides detection collision between the hoop and ball, we will also detect the collision between the balls and world boundary. We remove the balls that hit the boundary. To remove bodies in the physics world, we first need to store the reference of the bodies that we are going to remove. So, we define an array in the physics object:
    physics.bodiesToRemove = [];
  4. Next, we code the following method to create a large body as a boundary:
    physics.createWorldBoundary = function() {
      var bodyDef = new b2BodyDef;
      var fixDef = new b2FixtureDef;
    
      bodyDef.type = b2Body.b2_staticBody;
      bodyDef.position.x = -800/pxPerMeter;
      bodyDef.position.y = 300/pxPerMeter;
      bodyDef.angle = 0;
    
      fixDef.shape = new b2PolygonShape();
      fixDef.shape.SetAsBox(2000/pxPerMeter, 10/pxPerMeter);
    
      body = this.world.CreateBody(bodyDef);
      body.CreateFixture(fixDef);
    
      body.SetUserData({isBoundary:true}); // distinguish this object from others.
    };
  5. It's time to invoke our two new methods:
    physics.createLevel = function() {
    
      this.createWorldBoundary();
    
      this.setupContactListener();
    
      // existing code goes here
    };
  6. In the contact-handling method, we add checking between the balls and boundary. We add those ball bodies to the list:
    physics.setupContactListener = function() {
      // contact
      var contactListener = new Box2D.Dynamics.b2ContactListener;
      contactListener.BeginContact = function(contact, manifold) {
        if (contact.GetFixtureA().IsSensor() || contact.GetFixtureB().IsSensor()) {
          game.increaseScore();
        }
    
        // world boundary.
        var userDataA = contact.GetFixtureA().GetBody().GetUserData();
        var userDataB = contact.GetFixtureB().GetBody().GetUserData();
        if (userDataA !== null && userDataA.isBoundary ||
            userDataB !== null && userDataB.isBoundary) {
    
          // which one is boundary?
          var boundary = contact.GetFixtureA().GetBody();
          var body = contact.GetFixtureB().GetBody();
    
          if (userDataB !== null && userDataB.isBoundary) {
            boundary = contact.GetFixtureB().GetBody();
            body = contact.GetFixtureA().GetBody();
          }
    
          physics.bodiesToRemove.push(body);
    
        }
      };
      this.world.SetContactListener(contactListener);
    };
  7. Remember the array for body removal? We now use it in the update function. We iterate the list and destroy each body in that array:
    physics.update = function() {
      
      // existing code goes here.
    
      // remove bodies
      for (var i=0, len=this.bodiesToRemove.length; i<len; i++) {
        var body = this.bodiesToRemove[i];
        var sprite = body.GetUserData();
        if (sprite) {
          sprite.parent.removeChild(sprite);
        }
        this.world.DestroyBody(body);
      }
      this.bodiesToRemove.length = 0;
    };
  8. We can now move to the game.js file where we init the game score:
    game.score = 0;
  9. We define the following method to increase the game score:
    game.increaseScore = function() {
      game.score += 1;
      console.log(game.score); // out to console until we display it in interface.
    };

Objective complete – mini debriefing

A sensor fixture allows a body to pass through it. We may not be able to see the sensor, but the contact listener tells us when a fixture collides with the sensor.

Contact listener

The contact is made with ContactListener. Box2D informs us that there is contact between the two fixtures. In the BeginContact function, we get the two fixtures that collide with each other. We need to check which bodies they are and code our custom logic. By using GetBody on the fixture, we can get the body. With the GetUserData method, we have access to the custom data that we set to the body. We can only determine what action is to be taken after knowing which objects collide.

Tip

The BeginContact function provides the two contacting fixtures: fixtureA and fixtureB. We guarantee to obtain the two fixtures when the contact occurs, but the order of the fixtures is not guaranteed.

The contact listener, by default, is triggered by every body's collision. We need to filter the result and only focus on what we are interested in. Here, we are interested in two collision pairs: the ball/hoop and balls/boundary pairs.

There are more controls on the object's contacts. The following URL explains the anatomy of a Box2D collision. The post targets the C++ edition but the same concept also works in the JavaScript version: http://www.iforce2d.net/b2dtut/collision-anatomy.

User data

The Box2D engine knows that eventually we need to attach our own data to each body. That's why it provides a User data on each object. We can use the SetUserData or GetUserData methods to access custom data. We set an object in the boundary so that we can identify it from other bodies when we handle the collision. Otherwise, we cannot tell which bodies cause the collisions.

Note

In JavaScript, we can attach properties to any object. The reason the SetUserData and GetUserData methods exist is because these methods were ported from strictly typed OOP languages—C++ and ActionScript. These languages require a dedicated user data property to be predefined in the class body and expose a get/setter function for the programmer to use.

Classified intel

You may notice that we have placed the boundary much higher than the designed place. That's for our debugging approach. By putting the boundary inside the sight, we can see that the balls are removed once they collide with the boundary. After we confirm that the balls-removal logic works, we can place the boundary at a lower level and make it invisible to the players.

Object removal

Destroying bodies within a physics step may cause issues in the Box2D engine. That's why we always destroy bodies after each step is completely calculated. In the update method, we execute the object removal code after the step function.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.199.19