The main things to get about collision detection in SceneKit:
- it's based on bit masks, which together form a table.
- a contact delegate is how you respond to collisions.
Making objects collide
For example, you might state a bit of game design in plain English like this:
Asteroids hit each other (and make smaller asteroids). Missiles should pass through each other, but destroy rockets and asteroids. Rockets shouldn't do anything to missiles (only the other way around), but if one gets too close to another or to an asteroid you are having a bad problem and you will not go to space today.
The first step to realizing that with collision detection is to codify that design in terms of which pairs interact. You can do this with a table:
| Missile | Rocket | Asteroid
--------------------------------------
Missile | No | Yes | Yes
Rocket | No | Yes | Yes
Asteroid | No | No | Yes
Then you can turn the headers of the table into a set of category constants for use in your code.
typedef NS_OPTIONS(NSUInteger, CollisionCategory) {
CollisionCategoryMissile = 1 << 0,
CollisionCategoryRocket = 1 << 1,
CollisionCategoryAsteroid = 1 << 2,
};
missile.physicsBody.categoryBitMask = CollisionCategoryMissile;
rocket.physicsBody.categoryBitMask = CollisionCategoryRocket;
asteroid.physicsBody.categoryBitMask = CollisionCategoryAsteroid;
Use bitwise OR on these constants to create collisionBitMask
values that fill in the table.
missile.physicsBody.collisionBitMask =
CollisionCategoryRocket | CollisionCategoryAsteroid;
rocket.physicsBody.collisionBitMask =
CollisionCategoryRocket | CollisionCategoryAsteroid;
asteroid.physicsBody.collisionBitMask = CollisionCategoryAsteroid;
That's all you need to make SceneKit resolve collisions for you (that is, bounce objects off each other).
Responding to collisions
If you also want to be notified of collisions (so you can make missiles blow stuff up, and running your ship into an asteroid end the game), you'll need to set a contact delegate on your scene's physics world and implement one or more of the contact delegate methods that get called when a contact happens.
In your contact delegate method (say, physicsWorld:didBeginContact:
), you'll need to find out which categories of bodies were involved in the contact, and which was which, so you can get to your code that does whatever your game does for the collision:
- (void)physicsWorld:(SCNPhysicsWorld *)world didBeginContact:(SCNPhysicsContact *)contact
{
CollisionCategory contactMask =
contact.nodeA.physicsBody.categoryBitMask | contact.nodeB.physicsBody.categoryBitMask;
// first, sort out what kind of collision
if (contactMask == (CollisionCategoryMissile | CollisionCategoryRocket)) {
// next, sort out which body is the missile and which is the rocket
// and do something about it
if (contact.nodeA.physicsBody.categoryBitMask == CollisionCategoryMissile) {
[self hitRocket:contact.nodeB withMissile:contact.nodeA];
} else {
[self hitRocket:contact.nodeA withMissile:contact.nodeB];
}
} else if (contactMask == (CollisionCategoryMissile | CollisionCategoryAsteroid)) {
// ... and so on ...
}
}
Put this code in one of your classes (a view controller, maybe — wherever you keep your game logic is good), and make that class declare conformance to the SCNPhysicsContactDelegate
protocol.
@interface ViewController: UIViewController <SCNPhysicsContactDelegate>
Then assign that object to your scene's physics world as the contact delegate:
// in initial setup, where presumably you already have a reference to your scene
scene.physicsWorld.contactDelegate = self
Learning more
There's a little bit about collision resolution in the SCNPhysicsBody reference documentation. And Apple has some sample code that uses collision detection — it's part of the smorgasbord of demos in the WWDC slides and demo sample apps, and in the vehicle physics demo, too.
Beyond that, SceneKit's collision handling model is almost exactly the same as SpriteKit's, so almost everything in the SpriteKit programming guide is also useful for understanding the same stuff in SceneKit.
println
just insidephysicsWorld:didBeginContact:
. That way you'll know whether your problem is the method not being called or yourcontactMask
logic not working right. – rickster