Stop Sprites from Pushing Each Other in SpriteKit - ios

I'm coding a SpriteKit game and I'm trying to add the equivalent of a Cocos2D sensor. My problem is that my sensor keeps pushing the other sprites, but this isn't what I want. I want the sensor to be able to overlap with other sprites and not push them around. How do I fix this? Here is the code I've added for my sensor:
bubble.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(50))
bubble.physicsBody?.dynamic = false
bubble.physicsBody?.allowsRotation = false
bubble.physicsBody?.affectedByGravity = false
And here is my collision method:
func didBeginContact(contact: SKPhysicsContact) {
let collision:UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
if collision == (playerCategory | crateCategory) {
NSLog("Game Over")
var myLabel = SKLabelNode(fontNamed: "Arial")
myLabel.text = "GAME OVER"
myLabel.fontSize = 50
myLabel.color = SKColor.blueColor()
myLabel.position = (CGPointMake(self.frame.size.width*0.5, self.frame.size.height*0.5))
self.addChild(myLabel)
fisherman.physicsBody?.dynamic = false
fisherman.removeFromParent()
fish.physicsBody?.dynamic = false
fish.removeFromParent()
crate.physicsBody?.dynamic = false
crate.removeFromParent()
crateDuplicate.physicsBody?.dynamic = false
crateDuplicate.removeFromParent()
}
if collision == (playerCategory | bubbleCategory) {
NSLog("Bubble Contact")
bubbleDuplicate.removeAllActions()
bubbleDuplicate.removeFromParent()
bubble.removeAllActions()
bubble.removeFromParent()
bubbleDuplicate.setScale(0)
bubble.setScale(0)
bubbleDuplicate.alpha = 0
}
}

Set a collisionBitMask to 0, so all collisions of this particular object to others will be ignored.

Related

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

Deleting SKNode on contact with SKPhysicsWorld Boundaries in Swift 3

I am looking to create a unique opening screen for an IOS app, and I would like to have a background of balls dropping to create visual interest. I am creating the balls in the didMove() function, and they are working fine, but only some of the time. I believe the frame is too large for the display, which is making the random X and Y coordinates sometimes occur outside of the screen.
self.physicsWorld.gravity = CGVector.init(dx: 0, dy: -9.8)
let sceneBody = SKPhysicsBody(edgeLoopFrom: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
let minXValue: CGFloat = 0
let maxXValue: CGFloat = self.frame.size.width
let minYValue: CGFloat = 0
let maxYValue: CGFloat = self.frame.size.height
let wait = SKAction.wait(forDuration: 0.4)
let run = SKAction.run {
let randomXNumber = (CGFloat(arc4random()).truncatingRemainder(dividingBy: maxXValue)) + minXValue
let randomYNumber = (CGFloat(arc4random()).truncatingRemainder(dividingBy: maxYValue)) + minYValue
print("Random Y Number: \(randomYNumber)")
print("Random X Number: \(randomXNumber)")
let ball = SKShapeNode(circleOfRadius: 20)
ball.fillColor = SKColor(colorLiteralRed: 212.0, green: 217.0, blue: 128.0, alpha: 1)
ball.position = CGPoint.init(x: randomXNumber, y: randomYNumber)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 20)
ball.physicsBody?.affectedByGravity = true
self.addChild(ball)
}
self.run(SKAction.repeatForever(SKAction.sequence([wait, run])))
Here is my code for the random balls.
I would also like to have these balls get removed from the view when it hits the bottom of the container, in order to increase performance on all devices and not have redundant nodes.
Which function or block of code would I need to implement this concept?
Setting the scene size
In GameViewController.swift find the function viewDidLoad and add the following line right above skView.presentScene(scene)
scene.size = skView.bounds.size
Remove balls from view
To remove balls from the scene you can detect when there is a collision between the edgeLoop around the scene and the balls. Then call removeFromParent on the ball. First, change the class declaration like so:
class GameScene: SKScene, SKPhysicsContactDelegate
and then add
physicsWorld.contactDelegate = self in didMoveToView.
Second, above the class declaration in GameScene.swift, create the physics categories:
enum PhysicsCategory:UInt32
{
case edge = 1
case ball = 2
}
Next, assign those categories to the edge and the balls. (Add the first two lines under sceneBody and insert the third line where you defined the other ball physics properties):
self.physicsBody?.categoryBitMask = PhysicsCategory.edge.rawValue
self.physicsBody?.contactTestBitMask = PhysicsCategory.ball.rawValue
ball.physicsBody?.categoryBitMask = PhysicsCategory.ball.rawValue
And finally, check for collisions:
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == PhysicsCategory.edge.rawValue && secondBody.categoryBitMask == PhysicsCategory.ball.rawValue || firstBody.categoryBitMask == PhysicsCategory.ball.rawValue && secondBody.categoryBitMask == PhysicsCategory.edge.rawValue
{
if firstBody.categoryBitMask == PhysicsCategory.ball.rawValue {
firstBody.node?.removeFromParent()
} else {
secondBody.node?.removeFromParent()
}
}

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

IOS Swift Spritekit Collision Detection

I am a very new programmer currently using Swift to make a simple brick breaker like game. I am trying to create a label that dynamically shows the score depending on how many times the ball collides with the paddle. Here are two different parts of my code I have so far.
bottom.physicsBody!.categoryBitMask = BottomCategory
ball.physicsBody!.categoryBitMask = BallCategory
paddle.physicsBody!.categoryBitMask = PaddleCategory
ball.physicsBody!.contactTestBitMask = BottomCategory
I know this may not be much help but I am wondering what type of bitmask I will have to make. Here is a part of my code where I want it to create the label
func didBeginContact(contact: SKPhysicsContact) {
// Make variables for the two physics bodies
var score: Int
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
let label = SKLabelNode(fontNamed: "Chalkduster")
label.text = String(score)
label.fontSize = 40
label.fontColor = SKColor.whiteColor()
label.position = CGPoint (x: 1136, y: 600)
addChild(label)
}
Any help would be appreciated.
To detect collisions you have to set collisionBitMask and to test contact you have to set the contactTestBitMask. The Ball has to detect collision with the paddle and detect contact with the bottom and the paddle. So you have to set the contactTestBitMask as
ball.physicsBody?.contactTestBitMask = BottomCategory | PaddleCategory
And the collisionBitMask as
ball.physicsBody?.collissionBitMask = PaddleCategory
So your code should be
bottom.physicsBody?.categoryBitMask = BottomCategory
bottom.physicsBody?.contactTestBitMask = BallCategory
paddle.physicsBody?.categoryBitMask = PaddleCategory
paddle.physicsBody?.contactTestBitMask = BallCategory
paddle.physicsBody?.collissionBitMask = BallCategory
ball.physicsBody?.categoryBitMask = BallCategory
ball.physicsBody?.contactTestBitMask = BottomCategory | PaddleCategory
ball.physicsBody?.collissionBitMask = PaddleCategory

didBeginContact not being invoked

I have 2 SKSpriteNodes whose contact needs to be detected. I've tried various methods and looked up a lot of stuff but can't seem to get an answer. Below is my code. controlCircle is a class level variable because it needs to be used in other methods. The objects are generated fine.
class GameScene: SKScene, SKPhysicsContactDelegate {
var controlCircle = SKSpriteNode()
var mainCategory : UInt32 = 1 << 0
var dropCategory: UInt32 = 1 << 1
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVectorMake(0,0)
//CREATING FIRST OBJECT AND ADDING PHYSICS BODY
var mainRadius = 20.0;
var controlCircle = SKSpriteNode(color: UIColor.clearColor(), size:CGSizeMake(mainRadius * 2, mainRadius * 2))
var circleBody = SKPhysicsBody(circleOfRadius: mainRadius)
circleBody.dynamic = false
circleBody.usesPreciseCollisionDetection = true
controlCircle.physicsBody = circleBody
var bodyPath = CGPathCreateWithEllipseInRect(CGRectMake((controlCircle.size.width/2), controlCircle.size.height/2, controlCircle.size.width, controlCircle.size.width),
nil)
var circleShape = SKShapeNode()
circleShape.fillColor = UIColor.brownColor()
circleShape.lineWidth = 0
circleShape.path = bodyPath
controlCircle.addChild(circleShape)
controlCircle.position = CGPointMake(self.frame.width/2, self.frame.height/2)
self.addChild(controlCircle)
controlCircle.physicsBody.categoryBitMask = mainCategory
controlCircle.physicsBody.contactTestBitMask = dropCategory
//CREATING SECOND OBJECT AND ADDING PHYSICS BODY
var radius = 10.0;
var drop = SKSpriteNode(color: UIColor.clearColor(), size:CGSizeMake(radius * 2, radius * 2))
var dropBody = SKPhysicsBody(circleOfRadius: radius)
dropBody.dynamic = false
dropBody.usesPreciseCollisionDetection = true
drop.physicsBody = dropBody
var dropPath = CGPathCreateWithEllipseInRect(CGRectMake((drop.size.width/2), drop.size.height/2, drop.size.width, drop.size.width),
nil)
var dropShape = SKShapeNode()
dropShape.fillColor = UIColor.blackColor()
dropShape.lineWidth = 0
drop.name = "dropMask"
dropShape.path = dropPath
drop.addChild(dropShape)
drop.position = CGPointMake(CGFloat(xValue), self.frame.height-5)
self.addChild(drop)
drop.physicsBody.categoryBitMask = dropCategory
drop.physicsBody.contactTestBitMask = mainCategory
}
func didBeginContact(contact: SKPhysicsContact) -> Void{
NSLog("Hello")
}
}
Good news everyone,
SKPhysicsBody documentation:
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.
So here is an idea to accomplish stationary objects when not dragging, yet receive collision events. On touchesBegan on a SKSpriteNode, set its physicsBody.dynamic = true, on touchUp physicsBody.dynamic = false
Or, you could just remove gravity from your system and have all nodes with physicsBody.dynamic = true

Resources