I picked the the classic video game Pong to provide a working example of the recommendation in the Sprite Kit programming guide to use Double Dispatch to farm out work associated with node physics body contacts in the simulation.
You can download the source from my GitHub repo.
Scene and Node Setup
Our game consists of a PlayfieldScene with a PhysicsBody edge loop, a BallNode, and two PlayerNodes each comprised of a PaddleNode and ScoreNode. The BallNode and PaddleNodes are configured with physics bodies to allow for contact detection.
To replicate the classic gameplay velocities of the BallNode are manually altered on contact with other bodies, rather than relying on the physics collision capabilities of Sprite Kit. The ball’s vertical velocity is simply reflected when it hits the top or bottom edge of the PlayFieldScene.
When the ball contacts the PaddleNode the horizontal velocity is reflected within an interpolated 90 degree arc based on the contact point. The magnitude of the reflected BallNode velocity is proportional to the speed of the PaddleNode on contact.
Handling Physics Body Contacts Naively
The PlayfieldScene is assigned as the contact delegate for the physics world and implements the contact delegate method
The main observation to make is that the bodies in a contact can appear in any order. For example the first two clauses in the conditional below are essectially checking for the same thing, that the ball is contacting the PlayfieldScene edges.
The problem with the approach above is two fold.
Firstly embedding all the logic in the scene’s contact delegate method will result in a long unreadable method as the number of nodes in the simulation increases. Farming this work out to contact handlers would be much better.
Secondly the code required to handle the outcome of each contact, needs to be written in or referenced twice. This could be addressed by by combining the double up with an OR operation, not that great for legibility.
Another approach to counter the double up is the bitwise sorting approach in the programming guide’s rocket contact example code example, reproduced below.
This approach will still require further nested conditionals to determine the outcome based on both bodies in the contact, which for all but the most trivial of simulations will most likely be the case.
Physics Body Contacts with Double Dispatch
Using the Visitor pattern we can double dispatch the outcome of the contact based on both bodies. The contact delegate no longer discerns the categroies of the physics body, its implementation shrinks to just the following.
VisitablePhysicsBody class is a simple wrapper for an SKPhysicsBody instance. It implements a method called accept, which accepts the visitor, and which in turn invokes the actual visit. A wrapper is used as acceptVisitor cannot be implemented as a category on
SKPhysicsBody given the physics bodies carried by the
SKPhysicsContact instance are actually instances of private class
Out ContactVisitor base class implements convenience constructor
contactVisitorWithBody:forContact which constructs one of its subclasses based on the physics body category of it’s first argument. This is the first part of the double dispatch, we have an instance of a class which is named after one of the bodies in the contact e.g.
visit method in our
ContactVisitor implements the second part of the double dispatch by sending a message named after the second physics body in the contact to the newly constructed
ContactVisitor subclass instance which is named after the first body in the contact e.g.
We now have a class
BallNodeContactVisitor which is solely concenred with handling contacts for nodes of class
BallNode. The methods within the class follow a naming convention determined by the
visit method and allows us to determine the outcome of the contact with other node types.
On the flip side we have another class named
PaddleNodeContactVisitor which handles contacts for nodes of class
You can handle the same contact in two seperate places, but you only really want to do it in one place. In the examples above the same contact between the
BallNode and the
PaddleNode will be dispatched to both the instances of
PaddleNodeVisitor if they implement the respective methods
visitBallNode. These methods don’t have to be implemneted as the
ContactVisitor base class
visit method checks if they respond to the selectors firstly. In practice you’d only implement either
Visitor Pattern and Double Dispatch in Ruby by Neeraj Singh.
iOS Sprite Kit Pong source code.