How To Setup Multiple Physics Bodies in SpriteKit - swift

I need to have two physics bodies on my playerSprite. I need one body to be a smaller contact area and to only detect collisions on certain bodies. My code is inside my PlayerNode class is:
let walkingBitMask = PhysicsCategory.Door | PhysicsCategory.Wall | PhysicsCategory.SlideTerminator | PhysicsCategory.Shootable
let contactBitMask = PhysicsCategory.Monster | PhysicsCategory.Door | PhysicsCategory.Item | PhysicsCategory.Slide | PhysicsCategory.Teleport
let mainBody = SKPhysicsBody(rectangleOf: CGSize(width: 40, height: 50))
mainBody.collisionBitMask = walkingBitMask
mainBody.contactTestBitMask = contactBitMask
let pitBody = SKPhysicsBody(rectangleOf: CGSize(width: 8, height: 8))
pitBody.contactTestBitMask = PhysicsCategory.Pit
pitBody.collisionBitMask = 0
self.physicsBody = SKPhysicsBody(bodies: [mainBody,pitBody])
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = PhysicsCategory.Player
self.name = "player"
self.physicsBody?.allowsRotation = false
The Pit category is all I want to interact with the pitBody. My code in setting up the Pit sprites is:
sprite.physicsBody=SKPhysicsBody(rectangleOf: CGSize(width: 8, height: 8))
sprite.physicsBody?.affectedByGravity = false
sprite.physicsBody?.isDynamic = false
sprite.name = "pit"
sprite.physicsBody?.allowsRotation = false
sprite.physicsBody?.categoryBitMask = PhysicsCategory.Pit
sprite.physicsBody?.collisionBitMask = 0
The problem I'm running into is that the player sprite stops moving when it encounters a pit sprite. Everything else works fine.

When you create a physics body using SKPhysicsBody.init(bodies:), the properties on the children are ignored. Only the shapes of the child bodies are used, according to the docs.
So the test bit masks specified on mainBody and pitBody are ignored, and the compound body of the player that is part of the simulation only specifies the categoryBitMask:
self.physicsBody?.categoryBitMask = PhysicsCategory.Player
This means it will collide with other bodies, since it's collisionBitMask default value is 0xFFFFFFFF (all bits set).
Configure the collisionBitMask and contactTestBitMask on the player body.
If you need to use separate collisions, consider using a SKPhysicsJointFixed to fuse the two physics bodies together.

Related

Restitution only for specific objects in SpriteKit

I'm currently making a football (soccer) game using SpriteKit, but I've run into a little problem. When the characters (players) jump, I don't want them to bounce when they hit the ground after (that looks really weird and isn't very realistic).
Fine! Just set the restitution of the ground to zero!
But not so fast ... that means the ball won't bounce either.
Here are the important parts of my code:
struct cType {
static let p1:UInt32 = 0
static let ball:UInt32 = 1
static let ground:UInt32 = 2
}
Moving on, in the didMove(to view: SKView) i define some properties of my objects:
ball.physicsBody?.categoryBitMask = cType.ball
ball.physicsBody?.collisionBitMask = cType.p1 | cType.p2 | cType.ground
ball.physicsBody?.contactTestBitMask = cType.p1 | cType.p2 | cType.ground
p1.physicsBody?.categoryBitMask = cType.p1
p1.physicsBody?.collisionBitMask = cType.p2 | cType.ball | cType.ground
p1.physicsBody?.contactTestBitMask = cType.p2 | cType.ball | cType.ground
As for the collision detection itself, I just use the didMove(_ contact: SKPhysicsContact) and it works perfectly.
Now, of course I have a player 2 (p2), but that sprite has the exact same code as player 1 (p1) except cType.p2 is changed to cType.p1 and vice versa.
I also have some code ground.physicsBody?.restitution = 0.5 but this of course sets the restitution to 0.5 for any sprite who collides with the ground. This is where I'm really unsure. So my question is: How can I make sure the ball bounces when it hits the ground (with restitution 0.5), but my players (p1, p2) don't?

How to use SceneKit vortex field to create a tornato effect

In the SceneKit WWDC 2014, they have an example of a vortex field with this effect:
The particle system looks much like a tornato, as it spins inward with a hollow center.
However, the documentation for vortex fields have no information on how to achieve this effect. Right now, I have this:
// create the particle system
let exp = SCNParticleSystem()
exp.loops = true
exp.particleMass = 5
exp.birthRate = 10000
exp.emissionDuration = 10
exp.emitterShape = SCNTorus(ringRadius: 5, pipeRadius: 1)
exp.particleLifeSpan = 15
exp.particleVelocity = 2
exp.particleColor = UIColor.white
exp.isAffectedByPhysicsFields = true
scene.addParticleSystem(exp, transform: SCNMatrix4MakeRotation(0, 0, 0, 0))
// create the field
let field = SCNPhysicsField.vortex()
field.strength = -5
field.direction = SCNVector3(x: 0, y: 1, z: 0)
let fieldNode = SCNNode()
fieldNode.physicsField = field
scene.rootNode.addChildNode(fieldNode)
This creates this effect:
Where I am looking down at the particles rotating clockwise with a really big radius outwards. It looks nothing like a tornato effect. How can I create this effect?
You say tornato, I say tornado, let’s call the whole thing off...
The SceneKit WWDC 2014 demo/slides is a sample code project, so you can see for yourself how they made any of the effects you see therein. In this case, it looks like the “vortex” demo isn’t actually using the vortexField API, but instead the custom field API that lets you supply your own math in an evaluator block. (See the link for the code in that block.)
You might be able to get similar behavior without a custom field by combining a vortex (causes rotation only) with radial gravity (attracts inward) with linear gravity (attracts downward), or some other combination (possibly something involving electric charge). But you’d probably have to experiment with tweaking the parameters quite a bit.
If anyone is still interested in this topic - here is a Swift 5 implementation of that legendary tornado effect.
Here is an example function that will create your tornado.
func addTornadoPhysicsField() {
// Tornado Particles Field Example
guard let tornadoSystem = SCNParticleSystem(named: "tornado.scnp", inDirectory: nil) else { return }
let emitterGeometry = SCNTorus(ringRadius: 1.0, pipeRadius: 0.2)
emitterGeometry.firstMaterial?.transparency = 0.0
let fieldAndParticleNode = SCNNode(geometry: emitterGeometry)
fieldAndParticleNode.position = SCNVector3(0.0, 0.0, -20.0)
tornadoSystem.emitterShape = emitterGeometry
fieldAndParticleNode.addParticleSystem(tornadoSystem)
yourScene.rootNode.addChildNode(fieldAndParticleNode)
// Tornado
let worldOrigin = SCNVector3Make(fieldAndParticleNode.worldTransform.m41,
fieldAndParticleNode.worldTransform.m42,
fieldAndParticleNode.worldTransform.m43)
let worldAxis = simd_float3(0.0, 1.0, 0.0) // i.Ex. the Y axis
// Custom Field (Tornado)
let customVortexField = SCNPhysicsField.customField(evaluationBlock: { position, velocity, mass, charge, time in
let l = simd_float3(worldOrigin.x - position.x, 1.0, worldOrigin.z - position.z)
let t = simd_cross(worldAxis, l)
let d2: Float = l.x * l.x + l.z * l.z
let vs: Float = 27 / sqrt(d2) // diameter, the bigger the value the wider it becomes (Apple Default = 20)
let fy: Float = 1.0 - Float((min(1.0, (position.y / 240.0)))) // rotations, a higher value means more turn arounds (more screwed, Apple Default = 15.0))
return SCNVector3Make(t.x * vs + l.x * 10 * fy, 0, t.z * vs + l.z * 10 * fy)
})
customVortexField.halfExtent = SCNVector3Make(100, 100, 100)
fieldAndParticleNode.physicsField = customVortexField // Attach the Field
}
Additional Configuration Options:
Finally all this can result in something like that:
Note: if you would like to move your static tornado almost like a real tornado, you will have to find a way to re-apply the physics field for each rendererd frame. If you don't, the world origin used in the evaluation block will not move and it will distort your tornado.
Note: You can also split the particle/field node into two different nodes that moves independently from each other. Constrain the field node to the position of the particle node and play around with the influence factor (still need to re-apply the field each frame)
For more information on Custom Fields check out here.

Detect overlaping if enumerating nodes

I would like to know how should I detect overlapping nodes while enumerating them? Or how should I make that every random generated position in Y axis is at least some points higher or lower.
This is what I do:
1 - Generate random number between -400 and 400
2 - Add those into array
3 - Enumerate and add nodes to scene with generated positions like this:
var leftPositions = [CGPoint]()
for _ in 0..<randRange(lower: 1, upper: 5){
leftPositions.append(CGPoint(x: -295, y: Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)))
}
leftPositions.enumerated().forEach { (index, point) in
let leftSparkNode = SKNode()
leftSparkNode.position = point
leftSparkNode.name = "LeftSparks"
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}
But like this sometimes they overlap each other because the generated CGPoint is too close to the previous one.
I am trying to add some amount of triangles to the wall and those triangles are rotated by 90°
To describe in image what I want to achieve:
And I want to avoid thing like this:
Your approach to this is not the best, i would suggest only storing the Y values in your position array and check against those values to make sure your nodes will not overlap. The following will insure no two sparks are within 100 points of each other. You may want to change that value depending on your node's actual height or use case.
Now, obviously if you end up adding too many sparks within an 800 point range, this just will not work and cause an endless loop.
var leftPositions = [Int]()
var yWouldOverlap = false
for _ in 0..<randRange(lower: 1, upper: 5){
//Moved the random number generator to a function
var newY = Int(randY())
//Start a loop based on the yWouldOverlap Bool
repeat{
yWouldOverlap = false
//Nested loop to range from +- 100 from the randomly generated Y
for p in newY - 100...newY + 100{
//If array already contains one of those values
if leftPosition.contains(p){
//Set the loop Bool to true, get a new random value, and break the nested for.
yWouldOverlap = true
newY = Int(randY())
break
}
}
}while(yWouldOverlap)
//If we're here, the array does not contain the new value +- 100, so add it and move on.
leftPositions.append(newY)
}
func randY() -> CGFloat{
return Helper().randomBetweenTwoNumbers(firstNumber: leftSparkMinimumY, secondNumber: leftSparkMaximumY)
}
And here is a different version of your following code.
for (index,y) in leftPositions.enumerated() {
let leftSparkNode = SKNode()
leftSparkNode.position = CGPoint(x:-295,y:CGFloat(y))
leftSparkNode.name = "LeftSparks\(index)" //All node names should be unique
let leftSparkTexture = SKTexture(imageNamed: "LeftSpark")
LeftSpark = SKSpriteNode(texture: leftSparkTexture)
LeftSpark.name = "LeftSparks"
LeftSpark.physicsBody = SKPhysicsBody(texture: leftSparkTexture, size: LeftSpark.size)
LeftSpark.physicsBody?.categoryBitMask = PhysicsCatagory.LeftSpark
LeftSpark.physicsBody?.collisionBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.contactTestBitMask = PhysicsCatagory.Bird
LeftSpark.physicsBody?.isDynamic = false
LeftSpark.physicsBody?.affectedByGravity = false
leftSparkNode.addChild(LeftSpark)
addChild(leftSparkNode)
}

Crash on launch, error with SKphysics Joint

I am creating a game and i am trying to create a character that has 2 different nodes, the legs and the torso. I tried to link them using a fixed type joint, however when i do that the torso slides off of the legs sometimes while moving around. Now i am trying to create the character using a distance limit between the 2 nodes hoping that they won't slide away from each other now,however it keeps crashing on launch any ideas?
here is my code
func CreateHero (){
soldierLegs.position = CGPoint(x: 405 , y: 139)
soldierLegs.zPosition = 1
soldierLegs.anchorPoint.x = 0.6
soldierLegs.anchorPoint.y = 0.7
let soldierLegsBody:SKPhysicsBody = SKPhysicsBody(rectangleOfSize:
soldierLegs.size)
soldierLegsBody.dynamic = true
soldierLegsBody.affectedByGravity = true
soldierLegsBody.allowsRotation = false
//body.restitution = 0.4
soldierLegsBody.categoryBitMask = BodyType.soldierL.rawValue
soldierLegsBody.contactTestBitMask = BodyType.enemy1.rawValue |
BodyType.enemy2.rawValue | BodyType.enemy3.rawValue |
BodyType.desertForegroundCase.rawValue
soldierLegs.physicsBody = soldierLegsBody
soldierTorso.position = soldierLegs.position
soldierTorso.zPosition = 2
soldierTorso.anchorPoint.x = 0.25
soldierTorso.anchorPoint.y = 0.1
let soldierTorsoBody:SKPhysicsBody = SKPhysicsBody(rectangleOfSize:
soldierTorso.size)
soldierTorsoBody.dynamic = true
soldierTorsoBody.affectedByGravity = true
soldierTorsoBody.allowsRotation = false
soldierTorsoBody.categoryBitMask = BodyType.soldierT.rawValue
soldierTorsoBody.contactTestBitMask = BodyType.enemy1.rawValue |
BodyType.enemy2.rawValue | BodyType.enemy3.rawValue |
BodyType.desertForegroundCase.rawValue
soldierTorso.physicsBody = soldierTorsoBody
let Joint =
SKPhysicsJointLimit.jointWithBodyA(soldierLegs.physicsBody!, bodyB:
soldierTorso.physicsBody!, anchorA: soldierLegs.position, anchorB:
soldierTorso.position)
Joint.maxLength = 0.01
self.addChild(soldierTorso)
self.addChild(soldierLegs)
self.physicsWorld.addJoint(Joint)
}
According to the Apple official document :
Creating a Limit Joint
class func joint(withBodyA: SKPhysicsBody, bodyB: SKPhysicsBody, anchorA: CGPoint, anchorB: CGPoint)
Creates a new limit joint.
anchorA
A connection point on the first body in the scene’s coordinate system.
anchorB
A connection point on the second body in the scene’s coordinate system.
In your case anchorA is equal to anchorB (the same point), there is no limit, this one cause the crash.

How to randomly pick a node at a position?

I have five different colored circles, each called in the same function but with different physicsBodies and names. I would like them all to move upwards from the bottom of the screen with only one node on the screen at a time from one set position (i.e, CGPointMake(100, -500)) Like after a randomly colored node is generated, after 4 seconds a different randomly picked one will appear?
Is there a way to keep all of them individually identified and do this?
Code:
func circles() {
let sprites = SKSpriteNode(imageNamed: "Circle1")
sprites.position = CGPointMake(45, -200)
sprites.physicsBody = SKPhysicsBody(circleOfRadius: 40)
sprites.physicsBody?.dynamic = true
sprites.physicsBody?.affectedByGravity = false
sprites.physicsBody?.categoryBitMask = BodyType.player.rawValue
sprites.physicsBody?.contactTestBitMask = BodyType.enemy.rawValue | BodyType.other.rawValue | BodyType.seven.rawValue | BodyType.nine.rawValue | BodyType.five.rawValue
sprites.size = CGSizeMake(45, 45)
all.addChild(sprites)
let sprite2 = SKSpriteNode(imageNamed: "Circle2")
sprite2.position = CGPointMake(60, -750)
sprite2.physicsBody = SKPhysicsBody(circleOfRadius: 40)
sprite2.physicsBody?.categoryBitMask = BodyType.eight.rawValue
sprite2.physicsBody?.contactTestBitMask = BodyType.nine.rawValue | BodyType.other.rawValue | BodyType.five.rawValue | BodyType.enemy.rawValue | BodyType.seven.rawValue
sprite2.physicsBody?.dynamic = true
sprite2.physicsBody?.affectedByGravity = false
sprite2.size = CGSizeMake(45, 45)
addChild(sprite2)
let sprite3 = SKSpriteNode(imageNamed: "circle3")
sprite3.position = CGPointMake(100, -1200)
sprite3.physicsBody = SKPhysicsBody(circleOfRadius: 40)
sprite3.physicsBody?.categoryBitMask = BodyType.six.rawValue
sprite3.physicsBody?.contactTestBitMask = BodyType.seven.rawValue | BodyType.other.rawValue | BodyType.five.rawValue | BodyType.enemy.rawValue | BodyType.nine.rawValue
sprite3.physicsBody?.dynamic = true
sprite3.physicsBody?.affectedByGravity = false
sprite3.size = CGSizeMake(45, 45)
addChild(sprite3)
let sprite4 = SKSpriteNode(imageNamed: "Circle4")
sprite4.position = CGPointMake(150, -1400)
sprite4.physicsBody = SKPhysicsBody(circleOfRadius: 40)
sprite4.physicsBody?.categoryBitMask = BodyType.ground.rawValue
sprite4.physicsBody?.contactTestBitMask = BodyType.other.rawValue | BodyType.player.rawValue | BodyType.five.rawValue | BodyType.enemy.rawValue | BodyType.seven.rawValue
sprite4.physicsBody?.dynamic = true
sprite4.physicsBody?.affectedByGravity = false
sprite4.size = CGSizeMake(45, 45)
addChild(sprite4)
let sprite5 = SKSpriteNode(imageNamed: "Circle5")
sprite5.position = CGPointMake(200, -850)
sprite5.physicsBody = SKPhysicsBody(circleOfRadius: 40)
sprite5.physicsBody?.categoryBitMask = BodyType.ya.rawValue
sprite5.physicsBody?.contactTestBitMask = BodyType.five.rawValue | BodyType.player.rawValue | BodyType.other.rawValue | BodyType.seven.rawValue | BodyType.nine.rawValue
sprite5.physicsBody?.dynamic = true
sprite5.physicsBody?.affectedByGravity = false
sprite5.size = CGSizeMake(45, 45)
addChild(sprite5)
}
Help much appreciated.
You can use the name property of SKNode.
From the documentation:
Used to assign a name to a node for later reference. When choosing a
name for a node, you should decide whether each node gets a unique
name or whether some nodes will share a common name. If you give the
node a unique name, you can find the node later by calling the
childNodeWithName: method. If a name is shared by multiple nodes, the
name usually means that these are all a similar object type in your
game. In this case, you can iterate over those objects by calling the
enumerateChildNodesWithName:usingBlock: method.