Menu

Box2D Game Objects Part 3

In this 3rd part, we discuss the receiver of the World events generated from the box2d callbacks ContactListener and BoundaryListener.

Here is where it gets into the game logic. This is taken from a game we are porting, which is a "bashing" game where contacts with certain objects score points, and if certain objects exit the world, they are conditionally re-spawned.

We have the following filter flags:

  • FIELD: these are (stationary) "targets" that may score points, or otherwise affect the gameplay, e.g. award extra time.
  • BALL: these are "projectiles" that are used to contact the targets.
  • OBSTACLE: these are objects that impede projectiles contacting targets.
  • ORIGINAL3: these are the "original" projectiles you start with, and they can respawn up to a maximum number of times. These also have the BALL filter flag set.

We have the following game-specific Body classes:

  • Ball: represents a projectile. Uses filter flags BALL, ORIGINAL3.
  • Ground: represents everything else. Uses filter flags FIELD, OBSTACLE.

Also recall that this GO is set up as the route for the World GO to send all its notifications via the Event Pipeline.

Let's get started!

public class WorldUpdate extends GameObject implements EventCallback {
public static final String NAME = "WorldUpdate";

This method is primarily for determining what two game objects are in contact, and forwards the event along to that GO via the Event Pipeline.

Notice we must check both contacts, since box2d does not order them in any particular way.

Any additional game action, e.g. scoring points, adding extra time, is handled in that GOs EventCallback.

void evaluateContact(Contact cx, Locator lc, Installer in) {
    Ground wall = null;
    if(cx.body1 instanceof Ground && (cx.body1.filterFlags & GameWorld.BodyFlags.FIELD) != 0) {
        wall = (Ground) cx.body1;
    }
    else if(cx.body2 instanceof Ground && (cx.body2.filterFlags & GameWorld.BodyFlags.FIELD) != 0) {
        wall = (Ground) cx.body2;
    }
    if(wall != null) {
        // wall-? contact
        Ball ball = null;
        if(cx.body1 instanceof Ball && (cx.body1.filterFlags & GameWorld.BodyFlags.BALL) != 0) {
            ball = (Ball)cx.body1;
        }
        else if(cx.body2 instanceof Ball && (cx.body2.filterFlags & GameWorld.BodyFlags.BALL) != 0) {
            ball = (Ball)cx.body2;
        }
        try {
            if (ball != null) {
                // wall-ball contact
                in.event(cx, new String[] { wall.name });
            } else {
                // wall-? contact
                Ground obs = null;
                if ((cx.body1.filterFlags & GameWorld.BodyFlags.OBSTACLE) != 0) {
                    obs = (Ground) cx.body1;
                } else if ((cx.body2.filterFlags & GameWorld.BodyFlags.OBSTACLE) != 0) {
                    obs = (Ground) cx.body2;
                }
                if (obs != null) {
                    // wall-obstacle contact
                    in.event(cx, new String[] { obs.name });
                }
            }
        } catch (Exception ex) {
            Log.e(name, "evaluateContact", ex);
        }
    }
}

This method handles the remaining bookkeeping for a Body when it exits the World bounds. Depending on what object has left, the appropriate action is taken, based on the filter flags:

  • a BALL or OBSATACLE is sent to the World via Uninstall Pipeline and removed from play.
  • an ORIGINAL3 is sent to the World via Uninstall Pipeline for removal, and the GameCycle via Event Pipeline, to check whether it should re-spawn or not.

Remember: box2d already released all Body resources before sending this event.

Remember: all pipeline requests are queued and executed in order.

void evaluateBounds(BoundsViolation bv, Locator lc, Installer in) {
    final Box2DBody target = (bv.body1.filterFlags & GameWorld.BodyFlags.BALL) != 0 ? bv.body1 : null;
    if(target != null && target instanceof Ball) {
        try {
            in.uninstall(target.name, GameWorld.Routes.WORLD);
            if((target.filterFlags & GameWorld.BodyFlags.ORIGINAL3) != 0) {
                in.event(new CheckForRespawn((Ball)target), null);
            }
        } catch (Exception ex) {
        }
    }
    else {
        final Box2DBody gtarget = (bv.body1.filterFlags & GameWorld.BodyFlags.OBSTACLE) != 0 ? bv.body1 : null;
        if(gtarget != null) {
            try {
                in.uninstall(gtarget.name, GameWorld.Routes.WORLD);
            } catch (Exception ex) {
            }
        }
    }
}
public WorldUpdate() {
    super(NAME, true);
}

Finally, the EventCallback routes according to the GO received.

@Override
public void event(GameObject go, Locator lc, Installer in) {
    if(go instanceof Contact) {
        evaluateContact((Contact)go, lc, in);
    }
    else if(go instanceof BoundsViolation) {
        evaluateBounds((BoundsViolation)go, lc, in);
    }
}
}
Posted by g-dollar 2013-06-06

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.