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:
We have the following game-specific Body classes:
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:
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); } } }