How to Set Up Scenekit Collision Detection

How to set up SceneKit collision detection

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.

SceneKit collision not detected in ARkit

Your nodes don't interact with each other because they are in different categories.
Here is how the collsionBitMask works according to the documentation:

When two physics bodies contact each other, a collision may occur. SceneKit compares the body’s collision mask to the other body’s category mask by performing a bitwise AND operation. If the result is a nonzero value, then the body is affected by the collision.

The contactBitMask and categoryBitMask work the same.

When SceneKit checks your physicsBody for a contact this is what happens:

object1.physicsBody.contactTestBitMask = 4 = 0100b
object2.physicsBody.categoryBitMask = 8 = 1000b
-----
(bitwise AND) 0000b = 0 -> no collision

The best way to define your categories is with an OptionSet, Apple actually provides you with default categories in SCNPhysicsCollisionCategory.

If you want two nodes or physicsBody's to interact they need to share at least one category. Otherwise they will not collide.

Here is how your example should probably look like:

struct CollisionCategory: OptionSet {
let rawValue: Int

static let moltenBullet = CollisionCategory(rawValue: 4)
static let iceWall = CollisionCategory(rawValue : 8)
}

Then you assign your categories like this:

class Bullet: SCNNode {
...

// The physics body-category is a bullet
self.physicsBody?.categoryBitMask = CollisionCategory.moltenBuller.rawValue
// This contacts with both other bullets and ice walls!
self.physicsBody?.contactBitMask = [CollsionCategory.moltenBullet, CollisionCategory.iceWall].rawValue
self.physicsBody?.collisionBitMask = self.physicsBody!.contactBitMask
}

Can't detect collision between rootNode and pointOfView child nodes in SceneKit / ARKit

Regarding the positioning of your cyliner:

instead to use the render update at time, you better use a position constraint for your cylinder node to move with the point of view. the result will be the same, as if it were a child of the point of view, but collisions will be detected, because you add it to the main rootnode scenegraph.

let constraint = SCNReplicatorConstraint(target: pointOfView) // must be a node
constraint.positionOffset = positionOffset // some SCNVector3
constraint.replicatesOrientation = false
constraint.replicatesScale = false
constraint.replicatesPosition = true

cylinder.constraints = [constraint]

There is also an influence factor you can configure. By default the influence is 100%, the position will immediatly follow.



Related Topics



Leave a reply



Submit