I'm looking for the best way (performace-wise) to detect contact between two objects that do not collide (not bounce off each other) in a SceneKit physics world.
I saw that SpriteKit has a contactTestBitMask and a collisionBitMask for physics bodies while SceneKit only has the latter. So there must to be another preferred way to get notified when objects have contact in SceneKit. I guess that calling contactTestBetweenBody:andBody:options: in each frame for each object is not the best way to do it?
UPDATE
With iOS 9.0, Apple has added contactTestBitMask to SCNPhysicsBody. So this question will become obsolete soon.
This question deserves a full tutorial. In short, each physics body has a categoryBitMask and collisionBitMask.
categoryBitMask
This is the bit that represents the body.
collisionBitMask
This is the bit that represents which bodies collide with it.
By default (when assigned a physicsBody) all bodies collide with the exception of a kinematicBody, which act special. Objects don't collide with it. But don't get confused, it can collide with other objects when manually moved (such as by dragging it with a finger).
How to handle!!!
Assign your bodies whether it be static or dynamic. Assign there category and collision mask. If the mask match up they collide. How about an example!
These bodies collide with each other
bodyA.physicsBody.categoryBitMask = 4;
bodyB.physicsBody.categoryBitMask = 8;
bodyA.physicsBody.collisionBitMask = 8;
bodyB.physicsBody.collisionBitMask = 4;
These don't collide
bodyA.physicsBody.categoryBitMask = 4;
bodyB.physicsBody.categoryBitMask = 8;
bodyA.physicsBody.collisionBitMask = 0;
bodyB.physicsBody.collisionBitMask = 0;
Noticed I used categories by power of two. This is because the contactDelegate uses bitwise AND to compare. Read up on bitwise if you don't understand. It will really help.
These bodies collide without physics
EXAMPLE: You have a hero that runs through a ghost and you want to know that it happened without either one being effected. Such as bouncing off each other.
bodyA.physicsBody.categoryBitMask = 4;
bodyB.physicsBody.categoryBitMask = 8;
bodyA.physicsBody.collisionBitMask = 0;
bodyB.physicsBody.collisionBitMask = 0;
So the following code is designed so that bodyA(your hero) runs through bodyB(ghost).
bodyB.physicsField.categoryBitMask = 4
Noticed that the above is a physicsField.catagoryBitMask. It is assigned a 4 so that it matches up with bodyA.
let collisionField = SCNPhysicsField.customFieldWithEvaluationBlock(){
(_, _, _, _, _) in
WHAT U WANT TO DO ON CONTACT
}
bodyB.physicsField = collisionField
The physicsField is not the shape of your geometry unfortunately. Its default is the shape of its bounding box or sphere/elliptical if you set the physicsField property usesEllipsoidalExtent to "true/yes"
The area of effect can be changed using the physicsFields halfExtent property like so...
bodyB.physicsField.halfExtent = SCNVector3Make(.5, .5, .5)
The above will extend the field of a box the size of (2, 2, 2) to (2.5, 2.5, 2.5).
EDIT: If they still collide, try removing the physicsbody from bodyB (this will remove all physics simulation from the node). I don't think it will be a problem as long as the catagory and collision bitmask are assigned properly.
This is the best method I could find. All coding is from my head, with the support of Apple docs, so may not be exactly right but I hope this helps!
I have found the best way is to just set the mass so small on a kinematic body that the other one will pass through it.
sensorBox.physicsBody.mass = 0.00000000001; // super tiny mass makes it a "sensor"
Don't forget your scene's physics world can call a "contactTestBetweenBody: andBody: withOptions... this works great.
Usage
[myScene.physicsWorld contactTestBetweenBody:(SCNPhysicsBody *) andBody:(SCNPhysicsBody *) options:(NSDictionary *)];
Related
I am evaluating the iOS SpriteKit physics engine, and for a test I have created a simple scene with Xcode that contains two circle-shaped nodes:
Both nodes have physics bodies of circular shape and 5 kg of mass.
I am connecting both nodes with a SKPhysicsJointSpring. Here is the entire setup (in viewDidLoad()):
let path = NSBundle.mainBundle().pathForResource("MyScene", ofType: "sks")!
let scene = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! SKScene
let border = SKPhysicsBody(edgeLoopFromRect: scene.frame)
border.restitution = 0;
border.friction = 0;
scene.physicsBody = border
let player = scene.childNodeWithName("player")! // large ball
let tail1 = scene.childNodeWithName("tail1")! // smaller ball
player.physicsBody!.usesPreciseCollisionDetection = true
tail1.physicsBody!.usesPreciseCollisionDetection = true
let spring1 = SKPhysicsJointSpring.jointWithBodyA(player.physicsBody!, bodyB: tail1.physicsBody!, anchorA: player.position, anchorB: tail1.position)
spring1.damping = 1
spring1.frequency = 3
scene.physicsWorld.addJoint(spring1)
spriteKitView.presentScene(scene)
Note: The gravity is set to (0,0) in the scene editor.
When I move the bigger node (say, by capturing touches with touchesBegan() and setting the node's position to the touch position), the other follows according to spring parameters.
However, if I move the node fast enough, so that the spring forces become extreme, both nodes overlap when the spring is contracting. I am expecting them to collide with each other since their collisionBitMask is by default set to -1 (all bits set). I have enabled usesPreciseCollisionDetection but the effect is still visible.
When I add an edge loop around the scene they do collide with that edge as expected. Same with additional nodes that have a physics body (but no joints attached).
I have the impression that the presence of the spring somehow makes the engine ignore collisions between nodes that are connected with joints.
Did anyone else observe this, too?
Did I forget anything? Or is this working as intended?
Attempting to answer my own question.
I found out that other joints (such as SKPhysicsJointLimit) disable collision between the joined nodes as well. My only conclusion is that all SpriteKit joints somehow consider two objects as one, where collisions are deemed undesirable. Though in my app I would prefer to have collisions enabled. Maybe I should file an enhancement at Apple developer feedback.
So far my workarounds include:
Create spring behaviour by adding a SKFieldNode.springField plus a SKFieldNode.dragField as children to the sprite node. Pro: as children these fields automatically follow their parent position; Con: this spends 2 precious bits of fieldBitMask which puts a natural limit to the number of such nodes.
Manually apply some spring + drag force at every frame. Pro: does not waste bits of fieldBitMask, Con: compute spring + drag forces manually.
So I tend to favor option 2.
To summarize: If you want spring behaviour between 2 nodes while maintaining collisions, implement forces manually and do not use a joint.
Is it possible to cast a static shadow in SceneKit? Don't know if static is the right word. I would only like a smooth black circle underneath my object when it falls. The object moves in y- and x-direction. I know that I can use sampleRadius property but that has a significant impact on performance. I have seen such thing in other game engines and I am wondering if I can achieve it in SceneKit too.
EDIT:
I used this, but I only ge black scene with very little lightning. It looks like that floor is completely black. I have tried different gobo images, but no luck. What have I missed?
let spotNode = scene.rootNode.childNodeWithName("spot", recursively: true)
let spotlight = spotNode?.light
spotlight?.categoryBitMask = 1
spotlight!.shadowMode = SCNShadowMode.Modulated
spotlight?.gobo?.contents = UIImage(named: "goboImage")
floorNode?.categoryBitMask = 1
//Apple code:
// Use modulated mode
light.shadowMode = SCNShadowModeModulated;
// Configure the projected shadow
light.gobo.contents = aShadowImage;
// Use bit masks to specify receivers
light.categoryBitMask = kProjectorLightMask;
floor.categoryBitMask = kProjectorLightMask;
you'll want to use the SCNShadowModeModulated shadow mode. The different techniques for shadows are explained in depth in the Building a Game with SceneKit presentation from WWDC 2014.
General on this:
The categoryBitMask represents "categories".
So when you set a categoryBitMask on a light you are saying "This light will only hit objects in this category"
Ex:
static let WorldCategory: Int = 1 << 0 // Category for background objects
static let GameObjectsCat: Int = 1 << 1 // Category for monsters in the game :)
...
// When setting up the lights
spotlight.categoryBitMask = GameObjectsCat // Light will only affect game objects
spotlight.castsShadow = true
...
// Pretend this is a SCNNode representing a 3d fortress...
// Will not be affected the by spotlight or project it's shadows
fortress.categoryBitMask = WorldCategory
// Pretend this is a SCNNode representing a evil 3d monster...
// Affected by spotlight and projects shadows
orc.categoryBitMask = GameObjectsCat
orc.castsShadow = true
That was the general take on categoryBitMask.
In your case we want to:
Create a modulated light (SCNShadowMode.Modulated)
Set the gobo image
Give light and floor the same categoryBitMask but make sure it is not set for any other node (like game characters or whatever). Tip would be to create a new category like static let SimpleShadow: Int = 1 << 2
This light will not illuminate anything in the scene (not even the floor), it will only project your gobo where ever it is pointed. So a second light source is needed to see something (Ambient will be easiest).
I have not had the possibility to test this out, so I am writing from what I remember. :)
But please note. With proper use of categoryBitMask you could easily create a "special light" designed to project a real shadow of your main character against the floor node. This would be very cheap, as the shadow would only be calculated for one single node, projected against one single node.
Hope this will help.
Recently I have found that some sprite nodes can't test the collision with others. After some days trying and googling finally I made them works as usual. I have realized that there seems to be some relations between the dynamic and category bit mask. My guess is that:
player.dynamic = false
enemy.dynamic = true
player.category = player
enemy.collision = player
In the conditions above, enemy can't test the collision with player, but I wanna to know more details, any help will be appreciated.
update:
In my game, I want some of the sprites fixed in the scene, whose dynamic should be set to false, but can be collided by the players or enemies. For example the ground, a tree, or some buildings. What should I do to deal with these properties correctly ?
Like
tree.dynamic = false
enemy.collision = tree //can't works
tree.collision = enemy //should I do this? Is there another way to do this?
Non-dynamic bodies don't collide. From Apple's docs:
The dynamic property controls whether a volume-based body is affected
by gravity, friction, collisions with other objects, and forces or
impulses you directly apply to the object.
I'm working on a piece of code, and I want there to be gravity in the view but I want specific projectiles to defy gravity and fly across the screen. I know to get rid of the gravity on the whole view its just:
self.physicsWorld.gravity = CGVectorMake(0, 0);
But as stated I want gravity on the scene.
So I'm wondering if there is a way to take the gravity off one specific item? (i.e. the SKSpriteNode _debris item in my case)
By setting the physicsBody to not be affected by gravity.
E.g.
myNoGravityObject.physicsBody.affectedByGravity = NO;
See the documentation of SKPhysicsBody.
If you don't want a node to interact with physics calculations at all, but still want it to have a physicsBody (i.e. to make it begin to interact with things later on, or to check for collisions with other nodes), you can set
node.physicsBody.dynamic = NO;
This will cause the node to ignore gravity, as well as collisions, impulses, and the like. If you are setting up the contact delegate, note that at least one node in any given contact must be dynamic for the contact delegate to be notified of the event.
I have two SKNode objects. Their positions change when they collide.
How can I prevent that? At the same time, I still want to be able to respond to them contacting via - (void)didBeginContact;
I tried setting both their mass property to 0.0f but that didn't work.
You can achieve that by setting category, collision and contact bit masks.
uint32_t bodyABitMask = 1<<0;
uint32_t bodyBBitMask = 1<<1;
//A mask that defines which categories this physics body belongs to.
[bodyA setCategoryBitMask:bodyABitMask];
[bodyB setCategoryBitMask:bodyBBitMask];
//A mask that defines which categories of physics bodies
//can collide with this physics body.
[bodyA setCollisionBitMask:0];
[bodyB setCollisionBitMask:0];
//A mask that defines which categories of bodies cause
//intersection notifications with this physics body.
[bodyA setContactTestBitMask:bodyBBitMask];
[bodyB setContactTestBitMask:bodyABitMask];
In above case bodyA and bodyB cannot collide, but you will receive didBeginContact once they are in contact.