Double Dispatch Sprite Kit Physics Contacts
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 didBeginContact
.
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.
The 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 PKPhysicsBody
.
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. BallNodeContactVisitor
PaddleNodeContactVisitor
etc.
The 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. visitBallNode
visitPaddleNode
etc.
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 PaddleNode
.
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 BallNodeContactVisitor
and PaddleNodeVisitor
if they implement the respective methods visitPaddleNode
and 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 visitPaddleNode
or visitBallNode
.
References
Sprite Kit Programming Guide and in particular the Simulating Physics chapter.
Visitor Pattern and Double Dispatch in Ruby by Neeraj Singh.
iOS Sprite Kit Pong source code.