Collisions in SpriteKit - ios

I have an object kept between two bars. I have collisions set up but I'm not getting the print message "We have Contact" when making contact with either the top or bottom bar and I'm not sure why? I think it might have something to do with the boundary physicsBody. The image below is to give an example of the bars, where the object is kept between them.
var gameOver = false
override func didMoveToView(view: SKView) {
scene?.scaleMode = SKSceneScaleMode.AspectFill
self.physicsWorld.gravity = CGVectorMake(0.0, 2.0)
physicsWorld.contactDelegate = self
object = SKSpriteNode( imageNamed: "object")
object.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
object.physicsBody = SKPhysicsBody(circleOfRadius: object.size.width / 0.32)
object.physicsBody?.dynamic = true
object.physicsBody?.allowsRotation = true
self.addChild(object)
object.zPosition = 2
struct ColliderType {
static let object: UInt32 = 0x1 << 0
static let boundary: UInt32 = 0x1 << 1
}
let boundary = SKPhysicsBody(edgeLoopFromRect: CGRect(x: 253, y: 138, width:515, height:354))
self.physicsBody = boundary
self.physicsBody?.friction = 0
let push = CGVectorMake(10, 10)
object.physicsBody?.applyImpulse(push)
object.physicsBody?.categoryBitMask = ColliderType.object
object.physicsBody?.contactTestBitMask = ColliderType.boundary
object.physicsBody?.collisionBitMask = ColliderType.boundary
boundary.contactTestBitMask = ColliderType.object
boundary.categoryBitMask = ColliderType.boundary
boundary.collisionBitMask = ColliderType.object
func didBeginContact(contact:SKPhysicsContact) {
print("We have Contact")
gameOver = true
self.speed = 0
}

Try browsing the web and stackoverflow before asking a question because there is a lot of tutorials on the web and even stack overflow about this. This question might even be closed because collision detection is one of the basics in SpriteKit and has been asked a lot about on SO.
Anyway there is some errors/omissions in your code, so lets have a look.
1) You need to set the physics world contact delegate in your scenes "didMoveToView" method, so that it is actually listening for contacts. This is the number 1 thing people, including me, forget and than wonder for hours why nothing is working.
physicsWorld.contactDelegate = self
2) You are also not giving your sprites a physicsBody
object.physicsBody = SKPhysicsBody(...)
You have to always give them a physics body first and than set properties such as contactTestBitMasks etc.
3) Try using ? instead of ! when dealing with optionals to avoid nil crashes. Try to always do this even though you know that it is not nil. So say
...physicsBody?.contactTestBitMask...
instead of
...physicsBody!.contactTestBitMask...
You are sometimes doing it, and sometimes not.
In your current code you could get a crash because object has no physicsBody but you are force unwrapping it, essentially telling it that it does have a physics body, which is dangerous.
For example that line in your code should not work
object.physicsBody?.applyImpulse(push)
as there is no physics body on the object, but because it is not force unwrapping (?) nothing happens. Try changing that line using a ! and you should get a crash.
So always use ? when dealing with optionals and when the compiler allows you too.
Also that line should be after you have set up the object sprite not before.
4) Try to write your physics categories differently, your way will get confusing very quick when adding more categories because you cannot just increment the last number. So in your example the next collider type will have to be 4, 8, 16 etc.
Try this instead where you only need to increment the last number by 1.
struct ColliderType {
static let object: UInt32 = 0x1 << 0
static let boundary: UInt32 = 0x1 << 1
....
}
Than use it like so
...contactTestBitMask = ColliderType.object
...
You whole code should look like this
struct ColliderType {
static let object: UInt32 = 0x1 << 0
static let boundary: UInt32 = 0x1 << 1
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var gameOver = false
var object: SKSpriteNode!
override func didMoveToView(view: SKView) {
scene?.scaleMode = SKSceneScaleMode.AspectFill
self.physicsWorld.gravity = CGVectorMake(0.0, 2.0)
physicsWorld.contactDelegate = self
object = SKSpriteNode( imageNamed: "Spaceship")
object.zPosition = 2
object.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
object.physicsBody = SKPhysicsBody(circleOfRadius: object.size.width / 0.32)
object.physicsBody?.dynamic = true
object.physicsBody?.allowsRotation = true
object.physicsBody?.categoryBitMask = ColliderType.object
object.physicsBody?.contactTestBitMask = ColliderType.boundary
object.physicsBody?.collisionBitMask = ColliderType.boundary
self.addChild(object)
let boundary = SKPhysicsBody(edgeLoopFromRect: CGRect(x: 253, y: 138, width:515, height:354))
self.physicsBody = boundary
self.physicsBody?.friction = 0
boundary.contactTestBitMask = ColliderType.object
boundary.categoryBitMask = ColliderType.boundary
boundary.collisionBitMask = ColliderType.object
let push = CGVectorMake(10, 10)
object.physicsBody?.applyImpulse(push)
}
func didBeginContact(contact:SKPhysicsContact) {
print("We have Contact")
gameOver = true
self.speed = 0
}
}
Hope this helps

You must be sure that the two collision shapes are in the same collision space, if they are not in the same space, then collision detection algorithm will be never invoked. About the comment of Seed12, the two objects do not need to be a rigid body (physicsBody) to be checked for the presence of collisions between them.

Related

How to make collision between a node that was hard-coded, (in swift file), and an node that was placed in the scene editor? - Spritekit

We are trying to create a collision between the player, who's physics body (CategoryBitMask, CollisionBitMask, ContactTestBitMask) was programmed in the swift file, and the enemy that was placed in the scene editor and its physics body (CategoryBitMask = 2, CollisionBitMask = 4294967295 'default', ContactBitMask = 1). The code shown below shows what was tested but resulted in no collision.
import SpriteKit
import GameplayKit
class ClassicLevelScene: SKScene, SKPhysicsContactDelegate {
// Physics Bodies Setup
let enemy:UInt32 = 2
// Player Constant
let player = SKSpriteNode(imageNamed: "car1")
override func didMove(to view: SKView) {
// Physics World
physicsWorld.contactDelegate = self
// Set up player
player.position = CGPoint(x: 30, y: -245)
player.zPosition = Layers.cars
player.size = CGSize(width: 60, height: 80)
player.name = "player"
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: CGSize(width: player.size.width, height: player.size.height))
player.physicsBody?.allowsRotation = false
player.physicsBody?.categoryBitMask = 1
player.physicsBody?.collisionBitMask = 7
player.physicsBody?.contactTestBitMask = 1
addChild(player)
}
func didBegin(_ contact: SKPhysicsContact) {
/* Player touches anything, game over */
let contactA:SKPhysicsBody = contact.bodyB
let contactB:SKPhysicsBody = contact.bodyB
if contactA.categoryBitMask == 2 || contactB.categoryBitMask == 2 {
// execute code to respond to object hitting ground
minusHeart()
spikeSparkExplosion()
}
}
You likely have various issues. First this is probably a typo:
let contactA:SKPhysicsBody = contact.bodyB // you meant bodyA?
let contactB:SKPhysicsBody = contact.bodyB
Second, you don't seem overly clear about contacts vs collisions. Collisions = objects bounce off each other. Contacts = you get notified when objects touch. From the code it seems like you want the latter, and you may not want the collisions at all. Anyway you should have categories 1 and 2 for the category bitmasks (that part looks OK) and contact bit masks of 2 and 1 (respectively). That is, your player has category 1, and you want to know when it touches something in category 2. So the player's contact test bit mask should be 2 (you have it set to 1).

Change SKSpriteNode.isHidden to false when hit by raycast, otherwise set it to true

I am attempting to create a line of sight/field of view on a 2d game and am having some issues. I currently am trying a raycast as a solution. The idea is that by default all my enemy nodes have isHidden = true. When the raycast hits them it should change that value to be false and when the raycast is no longer hitting them it should change it back to true.
I've messed around with a lot of different options here. I have tried keeping track of the raycast hit variable like suggested here.
I currently have it set up in an if-else statement to handle it, but this ends up having the sprites flash in and out if the raycast is hitting and then will leave isHidden = false if it was true originally.
scene?.physicsWorld.enumerateBodies(alongRayStart: rayStart, end: rayEnd) { (body, point, normal, stop) in
let sprite = body.node as? SKSpriteNode
if body.categoryBitMask == 2 {
sprite?.isHidden = true
}else if body.categoryBitMask == 1 {
} else { sprite?.isHidden = true }
}
I have the above code as part of a function that I am then calling in the update() function.
I expect the sprite to have the value of isHidden = false only when currently being hit by the raycast. Unfortunately it isn't working that way.
I would first have all my enemies in a special node container in the scene, then before casting a ray, reset the enemies isHidden property by using a forloop.
enemyContainer.children.forEach{$0.isHidden = false}
scene?.physicsWorld.enumerateBodies(alongRayStart: rayStart, end: rayEnd) { (body, point, normal, stop) in
if let sprite = body.node as? SKSpriteNode,sprite.categoryBitMask & ~0b1 \\Replace 0b1 with your category bit mask constant if you created one
sprite.isHidden = true
}
}
This will allow you to immediately figure out if a light is touching an enemy, as opposed to E.Coms answer, which will only tell you when an enemy is hit during the physics update phase
if you do not want your enemies in a special container, then you can create an array and store only the ones that were touched the last time the ray was cast
//Global to class
var nodesInLight = [SKNode]()
.......
//in func
nodesInLight.forEach{$0.isHidden = false}
nodesInLight = [SKNode]() //Allow ARC to remove the previous container, instead of calling clear on it
scene?.physicsWorld.enumerateBodies(alongRayStart: rayStart, end: rayEnd) { (body, point, normal, stop) in
if let sprite = body.node as? SKSpriteNode,sprite.categoryBitMask & ~0b1 \\Replace 0b1 with your category bit mask constant if you created one
sprite.isHidden = true
nodesInLight.append(sprite)
}
}
An advance technique I would do is create an array of weak references, this way you do not accidentally create a retain cycle
In this case, you may give the ray a real physicsbody, and implement the contact delegate. In the DidBegin func, to turn the enemy on. and DidEnd func, turn the enemy off.
func didBegin(_ contact: SKPhysicsContact) {
print("begin")
}
func didEnd(_ contact: SKPhysicsContact) {
print("end")
}
override func didMove(to view: SKView) {
view.showsPhysics = true
let ray = SKShapeNode()
ray.path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 1000, height: 20)).cgPath
ray.fillColor = UIColor.red
addChild(ray)
ray.physicsBody = SKPhysicsBody(rectangleOf:(ray.path?.boundingBoxOfPath)!.size)
ray.physicsBody?.affectedByGravity = false
ray.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(Float.pi * 2), duration: 3)))
ray.physicsBody?.contactTestBitMask = 1
ray.physicsBody?.collisionBitMask = 0
let enemy = SKSpriteNode(color: UIColor.green, size: CGSize.init(width: 100, height: 100))
enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.frame.size)
enemy.physicsBody?.affectedByGravity = false
enemy.position = CGPoint(x: 200, y: 200)
enemy.physicsBody?.contactTestBitMask = 1
enemy.physicsBody?.collisionBitMask = 0
addChild(enemy)
enemy.isHidden = true
physicsWorld.contactDelegate = self

Collision Detection In Sprite Kit Swift

This is my first time making a game in iOS so naturally I came across a couple of problems. I've managed to solve them but now that it comes to the collision part between mario and the pipes I'm having some trouble:
What I would like to do:
When mario collides with the pipes, he will have to jump over them to get to the other side. If he does not then the pipes will continue to 'push' him to the edge of the screen where the game will end.
What actually happens:
No collision is detected whatsoever.
Here is the code below:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var mario = SKSpriteNode()
enum ColliderType: UInt32 {
case Mario = 1
case Object = 2
}
func addPipes() {
let pipetexture = SKTexture(imageNamed: "pipes")
let pipes = SKSpriteNode(texture: pipetexture)
let setScale = CGFloat(arc4random_uniform(19) + 15)
pipes.setScale(setScale) //min 15, max 19
pipes.position = CGPoint(x: self.frame.maxX, y: self.frame.minY)
let movePipes = SKAction.move(by: CGVector(dx: -5 * self.frame.width, dy: 0), duration: TimeInterval(self.frame.width/100))
let removePipes = SKAction.removeFromParent()
let moveAndRemovePipes = SKAction.sequence([movePipes, removePipes])
pipes.zPosition = +1
pipes.physicsBody = SKPhysicsBody(rectangleOf: pipes.size)
pipes.physicsBody!.isDynamic = false
pipes.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
pipes.physicsBody!.categoryBitMask = ColliderType.Object.rawValue
pipes.physicsBody!.collisionBitMask = ColliderType.Mario.rawValue
self.addChild(pipes)
pipes.run(moveAndRemovePipes)
}
func didBegin(_ contact: SKPhysicsContact) {
print("contact")
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
_ = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.addPipes), userInfo: nil, repeats: true)
let marioTexture = SKTexture(imageNamed: "mariorun0.png")
let marioTexture2 = SKTexture(imageNamed: "mariorun1.png")
let marioTexture3 = SKTexture(imageNamed: "mariorun2.png")
let animation = SKAction.animate(with: [marioTexture, marioTexture2, marioTexture3], timePerFrame: 0.1)
let makeMarioRun = SKAction.repeatForever(animation)
mario = SKSpriteNode(texture: marioTexture)
mario.setScale(10)
mario.position = CGPoint(x: -(self.frame.size.width/3.5), y: -(self.frame.size.height/2.8))
mario.run(makeMarioRun)
mario.physicsBody = SKPhysicsBody(rectangleOf: mario.size)
mario.physicsBody!.isDynamic = false
mario.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
mario.physicsBody!.categoryBitMask = ColliderType.Mario.rawValue
mario.physicsBody!.collisionBitMask = ColliderType.Object.rawValue
self.addChild(mario)
addPipes()
mario.zPosition = +2
}
}
I have also implemented a jump function that works perfectly. My problem is that I cannot get a collision detection between the pipes and mario.
I have tried setting the z position for both sprites to the same value without any luck.
My initial worry was that the images were to small and hence a collision would be near impossible to detect. But since I am scaling up both nodes and using the physics body of the scaled up images then there wouldn't be a problem, but that doesn't help either.
Thanks for your help in advance.
physicsBody!.isDynamic = false means that no physics activity will happen to this body. Other bodies can interact with it, but it will not interact with anything.
This makes sense for a pipe to have, because a pipe never moves, so there is no reason to be dynamic. (Your world should be moving, not the pipe, if you are doing a flappy bird-esc game)
Now for Mario, this does not make sense. Mario is a moving entity, so he will be interacting with the physics world around in. To fix your issue, you need to make him dynamic with mario.physicsBody!.isDynamic = true

SKPhysicsBodies not colliding after addition of SKPhysicsJointLimit

I currently have two SKSpriteNodes that I have added SKPhysicsBodies to. When they have no SKJoint attached, they collide as expected. As soon as I add the SKPhysicsJoint, they just pass right through each other. Any joint I add functions properly, but the SKPhysicsJointLimit only limits the extent to which the nodes can travel apart from each other, not how close they can get. How can I fix this?
Here is code I am using for the joint:
let joint = SKPhysicsJointLimit.joint(withBodyA: object1.physicsBody!, bodyB: object2.physicsBody!, anchorA: CGPoint(x: object1.position.x + iconController.position.x, y: object1.position.y + iconController.position.y), anchorB: CGPoint(x: object2.position.x + iconController.position.x, y: object2.position.y + iconController.position.y))
joint.maxLength = screen.height * 0.4
physicsWorld.add(joint)
PhysicsBody of both nodes:
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
self.physicsBody?.allowsRotation = false
self.physicsBody?.friction = 0
self.physicsBody?.mass = 0.1
I have tested it with different values for the above modifications of the SKPhysicsBody and it performs the same.
An SKPhysicsJoint object connects two physics bodies so that they are simulated together by the physics world.
You can use also SKPhysicJointPin:
A pin joint allows the two bodies to independently rotate around the
anchor point as if pinned together.
If your objects work well before the SKPhysicsJoint addition with the physic engine so they fired the didBeginContact as you wish and as you have setted, I think your problem is simply a wrong anchor. Try to add:
let skView = self.view as! SKView
skView.showsPhysics = true
to your scene initialization code: you will see an outline of the physic bodies and maybe you'll see the issue immediatly.
To help you I'll try to make an example of elements configured to collide each other:
enum CollisionTypes: UInt32 {
case Boundaries = 1
case Element = 2
}
class GameScene: SKScene,SKPhysicsContactDelegate {
private var elements = [SKNode]()
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
let boundariesFrame = CGRectMake(20, 20, 200, 400)
let boundaries = SKShapeNode.init(rect: boundariesFrame)
boundaries.position = CGPointMake(350,150)
let boundariesBody = SKPhysicsBody.init(edgeLoopFromRect: boundariesFrame)
boundariesBody.dynamic = false
boundariesBody.categoryBitMask = CollisionTypes.Boundaries.rawValue
boundariesBody.contactTestBitMask = CollisionTypes.Element.rawValue
boundaries.physicsBody = boundariesBody
addChild(boundaries)
for index in 0..<5 {
let element = SKShapeNode(circleOfRadius: 10)
let body = SKPhysicsBody(circleOfRadius: 10)
body.linearDamping = 0
// body.mass = 0
body.dynamic = true
body.categoryBitMask = CollisionTypes.Element.rawValue
body.contactTestBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
body.collisionBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
element.physicsBody = body
element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
elements.append(element)
addChild(element)
}
}
}
Hope it can help you to find your issue.

Setting up a Ground node using SpriteKit?

I've been designing a game where I want the game to stop as soon as the ball makes contact with the ground. My function below is intended to set up the ground.
class GameScene: SKScene, SKPhysicsContactDelegate {
var ground = SKNode()
let groundCategory: UInt32 = 0x1 << 0
let ballCategory: UInt32 = 0x1 << 1
//Generic Anchor coordinate points
let anchorX: CGFloat = 0.5
let anchorY: CGFloat = 0.5
/*Sets the background and ball to be in the correct dimensions*/
override init(size: CGSize) {
super.init(size: size)
//Create the Phyiscs of the game
setUpPhysics()
setUpGround()
setUpBall()
}
func setUpPhysics() -> Void {
self.physicsWorld.gravity = CGVectorMake( 0.0, -5.0 )
self.physicsWorld.contactDelegate = self
}
func setUpGround() -> Void {
self.ground.position = CGPointMake(0, self.frame.size.height)
self.ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width, self.frame.size.height)) //Experiment with this
self.ground.physicsBody?.dynamic = false
self.ground.physicsBody?.categoryBitMask = groundCategory //Assigns the bit mask category for ground
self.ball.physicsBody?.contactTestBitMask = ballCategory //Assigns the contacts that we care about for the ground
/*Added in*/
self.ground.physicsBody?.dynamic = true
self.ground.physicsBody?.affectedByGravity = false
self.ground.physicsBody?.allowsRotation = false
/**/
self.addChild(self.ground)
}
func setUpBall() -> Void {
ball.anchorPoint = CGPointMake(anchorX, anchorY)
self.ball.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2)
self.ball.name = "ball"
self.ball.userInteractionEnabled = false
ball.physicsBody?.usesPreciseCollisionDetection = true
self.ball.physicsBody?.categoryBitMask = ballCategory //Assigns the bit mask category for ball
self.ball.physicsBody?.collisionBitMask = wallCategory | ceilingCategory //Assigns the collisions that the ball can have
self.ball.physicsBody?.contactTestBitMask = groundCategory //Assigns the contacts that we care about for the ball
addChild(self.ball) //Add ball to the display list
}
The problem I am noticing is that when I run the iOS Simulator, the Ground node is not being detected. What am I doing wrong in this case?
Is your ground node not showing up at all? I believe your ground should be an SKSpriteNode. Add that SKSpriteNode as a child of the SKNode object you created.
The SKNode is an empty node and the SKSpriteNode has an actual visual representation. And instead of doing it in a separate function, it would be much simpler if you did it in the DidMoveToView itself.
var objects = SKNode()
addChild(objects)
let ground = SKNode()
ground.position = .......
let colorsprite1 = SKSpriteNode(imageNamed: "yourimageoftheground")
ground.addChild(colorsprite1)
ground.physicsBody?.dynamic = false
Yes you were right to set ground's physics definition as not dynamic. I don't know why people are telling you otherwise. You don't want it to move upon contact with your ball thingies or whatever it is you have moving around in your scene.
Add all other physics definitions you need. Consider using a ContactTestBitMask as you want SpriteKit to let you know when a collision with the ground occurs, instead of SpriteKit handling it by itself. Details about that mask is easily available in StackOverFlow with examples.
objects.addChild(ground)
Hope this helps
Your ballCategory should be assigned to self.ground.contactBitMask not the ball.
You need to set the dynamic property to true in order for contact detection to work.
At the same time, you can set the following properties.
ground.affectedByGravity = false
ground.allowsRotation = false

Resources