Make particle system only be affected by certain categories of physics fields in SceneKit? - swift

In SceneKit, it is possible to make a SCNPhysicsBody be unaffected by a SCNPhysicsField by giving them categoryBitMasks that when compared using bitwise AND, result in a value of zero.
Bodies who's category bit mask produce a non zero value when compared with that of the field are still affected by it.
Is it possible to do the same for the particles in a SCNParticleSystem, so that the particles will be unaffected by a SCNPhysicsField (edit: and still have the particles affected by other physics fields), but certain physics bodies or even other particle systems are affected by it?
To clarify:
What I want: A SCNParticleSystem that is affected by a SCNPhysicsField (specifically a linear gravity field). This can be done by setting the particle system's affectedByPhysicsFields property to true. However, this will make the particle system affected by all physics fields in the scene. I have another field (also a linear gravity field) that I want to only affect a SCNPhysicsBody which I don't want to be affected by the particle systems's field.
As it stands both the particle system and the physics body will be affected by both fields. Unless I give them categoryBitMasks to tell them which fields to interact with. Except particle systems don't have categoryBitMasks, and if a physics field's categoryBitMask is anything other than the default, it will be ignored by any particles.
So I can set it so that one of the fields only affects the body by giving them the same category. But I can't do the same for the particles. Is there any way to accomplish this? (Using the world gravity as one of the fields doesn't work because it doesn't have a categoryBitMask either.)
I've actually figured out a workaround (for now) by just using the particle system's acceleration property instead of a physics field. But I curious to see if it actually is possible to do it using a physics field.
edit: As requested here is some sample code. This code results in a sphere affected by two physics fields, and a particle system affected by one physics field. The comments explain the problem.
let PhysicsCategorySphere = 1 << 0
let PhysicsCategoryParticles = 1 << 1
scene.physicsWorld.gravity = SCNVector3(x: 0, y: 0, z: 0)
let sphere = SCNNode(geometry: SCNSphere(radius: 1))
sphere.physicsBody = SCNPhysicsBody.dynamicBody()
sphere.physicsBody?.categoryBitMask = PhysicsCategorySphere
//only physics fields with categoryBitMasks that match the sphere's can affect it
scene.rootNode.addChildNode(sphere)
let field = SCNPhysicsField.linearGravityField()
field.strength = 9.8
field.direction = SCNVector3(x: 0, y: 1, z: 0)
field.categoryBitMask = PhysicsCategorySphere
//only things with categories that match the field's will be affected by it
let fieldNode = SCNNode()
fieldNode.physicsField = field
scene.rootNode.addChildNode(fieldNode)
let particleSystem = SCNParticleSystem(named: "particles", inDirectory: "")
particleSystem.affectedByPhysicsFields = true
//there is no way that I know of to give the particle system a physics category
let particleNode = SCNNode()
particleNode.addParticleSystem(particleSystem)
scene.rootNode.addChildNode(particleNode)
let particleField = SCNPhysicsField.linearGravityField()
particleField.strength = 20
particleField.direction = SCNVector3(x: 0, y: 0, z: 1)
//particleField.categoryBitMask = PhysicsCategoryParticles
//if the field is given a category, it will no longer affect the particles
//the default is to match all categories, thus the particle field will also affect the sphere
let particleFieldNode = SCNNode()
particleFieldNode.physicsField = particleField
scene.rootNode.addChildNode(particleFieldNode)

Ok, I don't know how I overlooked this, but the answer is right in the documentation for SCNPhysicsField's categoryBitMask property.
To determine whether a field affects the particles spawned by an
SCNParticleSystem object, SceneKit performs the same check using the
categoryBitMask property of the node containing the particle system.

Related

Rejecting gravity effect SceneKit

This's my first project using SpriteKit.
I'm using these nodes
let physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
physicsBody.isAffectedByGravity = true
physicsBody.mass = 1
geometryNode.physicsBody = physicsBody
let force = SCNVector3(0, 9.8, 0)
let position = SCNVector3(0, 0, 0)
geometryNode.physicsBody?.applyForce(force, at: position, asImpulse: false)
scnScene.rootNode.addChildNode(geometryNode)
My goal is to see the object stuck in the centre of my scene.
As read within SceneKit's documentation, SceneKit uses SI so Mass 1 means 1 kg.
Gravity force applied to the mass centre of this object is -9.8N ( 1kg * 9.8 m/s^2 ) on the Y-axis.
Applying 9.8N to the mass centre should bring the resultant force equal to 0 so no one force is applied and the object should be stuck in the centre but in my project, it falls down.
Where am I wrong?
It looks to me like you apply the force when you create the node.
From the developer docs on applyForce:
Discussion This method accelerates the body without imparting any
angular acceleration to it. The acceleration is applied for a single
simulation step (one frame).
I think you need to move your applyForce call to your update method in the scene. so that the force is constantly applied.

Swift SceneKit — physical blocks do not stick to each other

Blocks just crumble apart.
How can this problem be solved?
Initializing blocks:
var boxNode = SCNNode(geometry: SCNBox(width: 0.75, height: 0.15, length: 0.25, chamferRadius: 0))
boxNode.position = SCNVector3(x: x1, y: y, z: z1)
boxNode.geometry?.firstMaterial = SCNMaterial()
boxNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "wood.jpg")
boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
boxNode.eulerAngles.y = Float(Double.pi / 2) * rotation
boxNode.physicsBody?.friction = 1
boxNode.physicsBody?.mass = 0.5
boxNode.physicsBody?.angularDamping = 1.0
boxNode.physicsBody?.damping = 1
picture
video
full code
I won't be able to tell you how to fix it as I have the exact same problem which I wasn't able to solve. However, as I played around I figured out a couple of things (which you may find useful):
The same problem hasn't happened to me in pure SceneKit, hence I think it's a bug in ARKit
Node with physics has to be added to the rootNode of the scene, otherwise odd stuff happens (elements passing through each other, gravity behaving in an inconsistent way)
If you pass nil as shape parameter, SceneKit will figure bounding box based on the geometry of the node. This hasn't worked properly for me so what I've done (using SceneKit editor) was to duplicate the geometry and then set it as a custom shape for the bounding box (have a look at the attached image)
Overall I've found physics simulation in SceneKit when used with ARKit to be extremely buggy and I spent a lot of time "tricking" it into working more-or-less how I wanted it to work.

Swift 3 (SpriteKit): Locking the x axis of a SKSpriteNode and its physicsBody

I really need to know how to lock the x axis of an SKSpriteNode and its physicsBody. I need to keep the SKSpriteNode dynamic and affectedByGravity. The node is on a slope, so this is why it's x axis is moved due to gravity. However, I don't want the x axis of this SKSpriteNode to move due to gravity. Is there a way to lock the x axis in order to achieve this?
Thanks for any help :D
Edit: I have tried to apply a constraint to the x value like this:
let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints?.append(xConstraint)
However this doesn't work and I'm not sure why and how to fix it. Anyone know of a solution?
Edit 2: SKPhysicsJointPin is actually looking more promising. In the comments of the first response/answer to this question, I have been trying to figure how to properly use it in my situation.
An example of my code:
let node = SKSpriteNode(imageNamed: "node")
enum collisionType: UInt32 {
case node = 1
case ground = 2
case other = 4 //the other node is unrelated to the ground and node
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
//Setup node physicsBody
node.physicsBody = SKPhysicsBody(rectangleOf: node.size)
node.physicsBody?.categoryBitMask = collisionType.node.rawValue
node.physicsBody?.collisionBitMask = //[other node that isn't the ground or the node]
node.physicsBody?.contactTestBitMask = //[other node that isn't the ground or the node]
node.physicsBody?.isDynamic = true
node.physicsBody?.affectedByGravity = true
addChild(node)
//Physics Setup
physicsWorld.contactDelegate = self
}
The node is on top of the ground, and the ground is composed of various SKSpriteNode lines that have a volumeBased physicsBody. The ground keeps adding new lines at the front, and removing the ones at the back, and changing the x value of each line by a negative (so the ground appears to moving - this process is performed in an SKAction). These lines (the parts of the ground) are on an angle which is why the node's x axis moves. I want the node to always be at the front of the ground (e.g. always on top of the newly created line). Currently, setting the position of the node like this locks the x axis (solving my issue):
override func didSimulatePhysics() {
//Manage node position
node.position.x = 195
node.position.y = CGFloat([yPosition of the first line of the ground - the yPosition keeps changing])
}
Note: ^This^ function is inside the GameScene class
The x axis actually stays the same like this. However, the issue is that now the physicsBody of the node is lower than the centre of the node (which didn't happen before).
A node's constraints property is nil by default. You'll need to create an array of one or more constraints and assign it to the property. For example
let xConstraint = SKConstraint.positionX(SKRange(constantValue: 195))
node.constraints = [xConstraint]
Update
You may want to use a camera node instead of moving the ground in the scene. With a camera node, you move the main character and the camera instead of the ground.
I think you could set the linearDamping property to 0.0
The linearDamping is a property that reduces the body’s linear velocity.
This property is used to simulate fluid or air friction forces on the
body. The property must be a value between 0.0 and 1.0. The default
value is 0.1. If the value is 0.0, no linear damping is applied to the
object.
You should pay attention also to the other forces applied to your SKSpriteNode. The gravitational force applied by the physics world for example where dx value, as you request , should be setted to 0.0:
CGVector(dx:0.0, dy:-4.9)
Remember also that when you apply other forces vectors like velocity you should maintain the dx property to 0.0 as constant if you want to block the x axis.
You could find more details to the official docs
Update (after your details to the comments below):
You could also anchored your sprite to the ground with an SKPhysicsJoint (I don't know your project so this is only for example):
var myJoint = SKPhysicsJointPin.joint(withBodyA: yourSprite.physicsBody!, bodyB: yourGround.physicsBody!, anchor: CGPoint(x: yourSprite.frame.minX, y: yourGround.frame.minY))
self.physicsWorld.add(myJoint)
You can work with the anchor property to create a good joint as you wish or adding more joints.

SceneKit collisionBitMask not behaving as expected

The documentation for SceneKit's collisionBitMask property of SCNPhysicsBody states the following:
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. Each body
independently chooses whether it wants to be affected by the other
body.
That last line indicates that if I have two objects, I can set it up so that when they collide, only one of them should be affected by the collision.
let CollisionCategoryPlane = 1 << 0
let CollisionCategorySphere1 = 1 << 1
let CollisionCategorySphere2 = 1 << 2
let plane = SCNNode(geometry: SCNPlane(width: 10, height: 10))
plane.position = SCNVector3(x: 0, y: -10, z: 0)
plane.eulerAngles = SCNVector3(x: Float(-M_PI/2), y: 0, z: 0)
plane.physicsBody = SCNPhysicsBody.staticBody()
plane.physicsBody?.categoryBitMask = CollisionCategoryPlane
plane.physicsBody?.collisionBitMask = CollisionCategorySphere1 | CollisionCategorySphere2
// the plane should be affected by collisions with both spheres (but the plane is static so it doesn't matter)
scene.rootNode.addChildNode(plane)
let sphere1 = SCNNode(geometry: SCNSphere(radius: 1))
sphere1.physicsBody = SCNPhysicsBody.dynamicBody()
sphere1.physicsBody?.categoryBitMask = CollisionCategorySphere1
sphere1.physicsBody?.collisionBitMask = CollisionCategoryPlane
// sphere1 should only be affected by collisions with the plane, not with sphere2
scene.rootNode.addChildNode(sphere1)
let sphere2 = SCNNode(geometry: SCNSphere(radius: 1))
sphere2.position = SCNVector3(x: 1, y: 10, z: 0)
sphere2.physicsBody = SCNPhysicsBody.dynamicBody()
sphere2.physicsBody?.categoryBitMask = CollisionCategorySphere2
sphere2.physicsBody?.collisionBitMask = CollisionCategoryPlane | CollisionCategorySphere1
// sphere2 should be affected by collisions with the plane and sphere1
scene.rootNode.addChildNode(sphere2)
Sphere1 should fall onto the plane, then sphere2 should fall onto sphere1 and bounce off, and sphere1 should be unaffected by the collision with sphere2. However, the observed behaviour is both spheres falling onto the plane and coming to rest inside each other - no collision event between the two spheres is registered.
What is going on here?
On related notes, some even stranger behaviour is observed when I make a couple small modifications to the above code.
If remove the line that defines the plane's collsionBitMask, leaving it as the default SCNPhysicsCollisionCategoryAll, sphere1 no longer collides with the plane.
If I move the lines that define the objects' physics bodies, categoryBitMasks, and collisionBiMasks to after the objects have each been added to the the scene, all the objects will collide with every other object. Even if I set every collisionBitMask to zero.

Turning off detection between sprites

I have two phyicsbodies in my spritekit game which are colliding and I am having a little difficulty getting them to stop.
I am posting the physics code for them below.
Problem is when the swarm touches my coins it pushes it, not exactly sure why since as far as my know my enemy is told to only contact with the player and my coin is told to only contact with the wall and the player (correct me if im wrong)
(P.S I commented out the collisionBitMask for the coin because when I dont my coins fall thru my walls )
Thanks
1St sprite
enemy = [SKSpriteNode spriteNodeWithImageNamed:#"Swarm"];
enemy.physicsBody =
[SKPhysicsBody bodyWithRectangleOfSize:enemy.size];
enemy.physicsBody.dynamic=NO;
enemy.name=#"Eagle";
enemy.physicsBody.categoryBitMask = PCFallersCategory;
// enemy.physicsBody.collisionBitMask =
// PCPlayerCategory;
enemy.physicsBody.contactTestBitMask = PCPlayerCategory;
enemy.physicsBody.restitution=0;
enemy.physicsBody.friction=0;
2nd Sprite.
self.name = #"coin";
CGFloat minDiam = MIN(self.size.width, self.size.height);
minDiam = MAX(minDiam-8, 8);
self.physicsBody =
[SKPhysicsBody bodyWithCircleOfRadius:minDiam/2.0];
self.physicsBody.dynamic=YES;
self.physicsBody.restitution =0;
self.physicsBody.friction = 0;
self.physicsBody.linearDamping = 0;
self.physicsBody.categoryBitMask = PCCollectableCategotry;
//self.physicsBody.collisionBitMask =PCPlayerCategory;
self.physicsBody.contactTestBitMask = PCPlayerCategory|PCWallCategory;
You need to explicitly set the collisionBitMask to 0.
enemy.physicsBody.collisionBitMask = 0;
According to the documentation
When two physics bodies contact each other, a collision may occur.
This body’s collision mask is compared to the other body’s category
mask by performing a logical AND operation. If the result is a
non-zero value, then this body is affected by the collision. Each body
independently chooses whether it wants to be affected by the other
body. For example, you might use this to avoid collision calculations
that would make negligible changes to a body’s velocity.
The default value is 0xFFFFFFFF (all bits set).
This means that by default, an SKPhysicsBody is configured to collide with all objects.