Why is there multiple collision calls Sprite Kit Swift - swift

I am building an iOS swift game with collision. The hero is being bombarded by small stars coming from the right side of the screen. Every time a star hits the hero, the score is iterated and the star is removed. But, more than one point is added to the score.
Node Set-up:
var star = SKSpriteNode(imageNamed: "star")
star.size = CGSizeMake(30, 30)
star.zPosition = 10
var starPhysicsRect = CGRectMake(star.position.x - star.frame.size.width / 2, star.position.y - star.frame.size.width / 2, star.frame.size.width, star.frame.size.height)
star.physicsBody = SKPhysicsBody(edgeLoopFromRect: starPhysicsRect)
star.physicsBody?.restitution = 0
star.name = "star"
star.physicsBody?.mass = 0
return star
didBeginContact Function:
func didBeginContact(contact: SKPhysicsContact) {
//collision variables
var firstBodyNode = contact.bodyA.node as! SKSpriteNode
var secondBodyNode = contact.bodyB.node as! SKSpriteNode
var firstNodeName = firstBodyNode.name
var secondNodeName = secondBodyNode.name
//other variables
var scoreLabel: SKLabelNode = constantsInstance.scoreLabel(position: CGPointMake(self.frame.size.width * 0.86, self.frame.size.height * 0.928))
//check if hero hit star
if firstNodeName == "hero" && secondNodeName == "star" {
println("star hit")
secondBodyNode.removeFromParent()
//iterate score
childNodeWithName("scoreLabel")?.removeFromParent()
scoreLabel.text = "\(counterForScore)"
counterForScore++
self.addChild(scoreLabel)
}
}
I get "star hit" printed to the console several times as well. Why would the didBeginContact function be called more than once if I remove the star on the first call of the function?
I have run simulations on a smaller scale and found that the star goes fairly deep into the hero (because of the SKAction), if there is no remove command. Could it be that the didBeginContact function is called many times while the star is going into the hero and before it is removed?
All help is greatly appreciated.

You can check if parent of the SKSpriteNode is nil before continuing.
func didBeginContact(contact: SKPhysicsContact) {
var firstBodyNode : SKSpriteNode!
var secondBodyNode : SKSpriteNode!
// Assuming that the contact test bit mask of star is greater than hero. If it's not you should reverse the condition.
if contact.bodyA.contactTestBitMask < contact.bodyB.contactTestBitMask {
firstBodyNode = contact.bodyA.node as! SKSpriteNode
secondBodyNode = contact.bodyB.node as! SKSpriteNode
} else {
firstBodyNode = contact.bodyB.node as! SKSpriteNode
secondBodyNode = contact.bodyA.node as! SKSpriteNode
}
var firstNodeName = firstBodyNode.name
var secondNodeName = secondBodyNode.name
if firstNodeName == "hero" && secondNodeName == "star" {
if secondBodyNode.parent != nil {
// Your code.
}
}
}

Could it possibly be due to the physicsBody:
var star = SKSpriteNode(imageNamed: "star")
star.size = CGSizeMake(30, 30)
star.zPosition = 10
var starPhysicsRect = CGRectMake(star.position.x - star.frame.size.width / 2, star.position.y - star.frame.size.width / 2, star.frame.size.width, star.frame.size.height)
star.physicsBody = SKPhysicsBody(edgeLoopFromRect: starPhysicsRect)
Have a look at the logic creating the starPhysicsRect:
(star.position.x - star.frame.size.width / 2, star.position.y - star.frame.size.width / 2, star.frame.size.width, star.frame.size.height)
From what I can see this translates to the following values:
(-15, -15, 30, 30) those negative position-coordinates seem odd. Are you sure you don't just want: SKPhysicsBody(rectangleOfSize: star.size) ?

Related

Inconsistent contact detection in Swift 3 using SpriteKit

I'm having an issue with contact detection in Swift 3 using SpriteKit. The contact detection is working...sometimes. It seems purely random as to when it fires and when it doesn't.
I have a yellow "bullet" that moves up on the screen to hit a red sprite named targetSprite. The desired behavior is to have the bullet removed when it hits the target, but sometimes it just passes through underneath.
I've found many questions about contact detection not working at all, but I haven't found any dealing with inconsistent detection.
What can I do to fix this?
Here's the code:
import SpriteKit
import GameplayKit
enum PhysicsCategory:UInt32 {
case bullet = 1
case sprite1 = 2
case targetSprite = 4
// each new value should double the previous
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// Create sprites
let sprite1 = SKSpriteNode(color: SKColor.blue, size: CGSize(width:100,height:100))
let targetSprite = SKSpriteNode(color: SKColor.red, size: CGSize(width:100,height:100))
let bullet = SKSpriteNode(color: SKColor.yellow, size: CGSize(width: 20, height: 20))
// show the bullet?
var isShowingBullet = true
// Timers
//var timer:Timer? = nil
var fireBulletTimer:Timer? = nil
// set up bullet removal:
var bulletShouldBeRemoved = false
let bulletMask = PhysicsCategory.bullet.rawValue
override func didMove(to view: SKView) {
// Physics
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
targetSprite.physicsBody?.affectedByGravity = false
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.centerRect.size)
bullet.physicsBody?.affectedByGravity = false
// Contact Detection:
targetSprite.physicsBody?.categoryBitMask = PhysicsCategory.targetSprite.rawValue
targetSprite.physicsBody?.contactTestBitMask =
//PhysicsCategory.sprite1.rawValue |
PhysicsCategory.bullet.rawValue
targetSprite.physicsBody?.collisionBitMask = 0 // no collision detection
// bullet physics
bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet.rawValue
bullet.physicsBody?.contactTestBitMask =
PhysicsCategory.targetSprite.rawValue
bullet.physicsBody?.collisionBitMask = 0 // no collision detection
// execute once:
fireBulletTimer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(self.fireBullet),
userInfo: nil,
repeats: false)
// Add sprites to the scene:
self.addChild(sprite1)
self.addChild(bullet)
self.addChild(targetSprite)
// Positioning
targetSprite.position = CGPoint(x:0, y:300)
// Note: bullet and sprite1 are at 0,0 by default
// Delegate
self.physicsWorld.contactDelegate = self
}
func didBegin(_ contact: SKPhysicsContact) {
print("didBegin(contact:))")
//let firstBody:SKPhysicsBody
// let otherBody:SKPhysicsBody
// Use 'bitwise and' to see if both bits are 1:
if contact.bodyA.categoryBitMask & bulletMask > 0 {
//firstBody = contact.bodyA
//otherBody = contact.bodyB
print("if contact.bodyA....")
bulletShouldBeRemoved = true
}
else {
//firstBody = contact.bodyB
//otherBody = contact.bodyA
print("else - if not contacted?")
}
/*
// Find the type of contact:
switch otherBody.categoryBitMask {
case PhysicsCategory.targetSprite.rawValue: print(" targetSprite hit")
case PhysicsCategory.sprite1.rawValue: print(" sprite1 hit")
case PhysicsCategory.bullet.rawValue: print(" bullet hit")
default: print(" Contact with no game logic")
}
*/
} // end didBegin()
func didEnd(_ contact: SKPhysicsContact) {
print("didEnd()")
}
func fireBullet() {
let fireBulletAction = SKAction.move(to: CGPoint(x:0,y:500), duration: 1)
bullet.run(fireBulletAction)
}
func showBullet() {
// Toggle to display or not, every 1 second:
if isShowingBullet == true {
// remove (hide) it:
bullet.removeFromParent()
// set up the toggle for the next call:
isShowingBullet = false
// debug:
print("if")
}
else {
// show it again:
self.addChild(bullet)
// set up the toggle for the next call:
isShowingBullet = true
// debug:
print("else")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if bulletShouldBeRemoved {
bullet.removeFromParent()
}
}
}
Sorry for the inconsistent indentation, I can't seem to find an easy way to do this...
EDIT:
I have found that using 'frame' instead of 'centerRect' makes the collision area the size of the sprite. For example:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.centerRect.size)
should be:
targetSprite.physicsBody = SKPhysicsBody(rectangleOf: targetSprite.frame.size)
First advice - Do not use NSTimer (aka Timer) in SpriteKit. It is not paired with a game loop and can cause different issues in a different situations. Read more here ( answer posted by LearnCocos2D)
So, do this:
let wait = SKAction.wait(forDuration: 1)
run(wait, completion: {
[unowned self] in
self.fireBullet()
})
What I have noticed is that if I run your code in Simulator, I get the behaviour you have described. didBegin(contact:) is being fired randomly. Still, this is not happening on a device for me, and device testing is what matters.
Now, when I have removed Timer and did the same thing with SKAction(s) everything worked, means contact were detected every time.
Have you tried adding
.physicsBody?.isDynamic = true
.physicsBody?.usesPreciseCollisionDetrction =true
SpriteKit physics engine will calculate collision correctly if you do following:
1) set "usesPreciseCollisionDetection" property to true for bullet's physics body. This will change collision detection algorithm for this body. You can found more information about this property here, chapter "Working with Collisions and Contacts".
2) move your bullet using applyImpulse or applyForce methods. Collision detection will not woking correctly if you move body by changing it's position manually. You can find more information here, chapter "Making Physics Bodies Move".

Change texture of individual nodes in didBeginContact

I've created a simple game where I have a match hover over candles (the odd description lends itself to my question) and the player scores a point when the match comes in contact with the wick. However, if it comes into contact with the anything else (like the 'wax' part of the candle), the game is over. The player controls the match by tapping on the screen.
My candle, being the wick and the coloured part, is created as follows (I have removed irrelevant parts, like the series of random textures):
func makeCandles() {
//Node properties and randomisation
let candle = SKNode()
let randomCandle = Int(arc4random_uniform(UInt32(candleTexture.count)))
let randomTexture = candleTexture[randomCandle] as SKTexture
let random = arc4random_uniform(17)
candle.position = CGPoint(x: self.frame.size.width, y: CGFloat(random * 12) - 120)
//Candle
let chosenCandle = SKSpriteNode(texture: randomTexture)
chosenCandle.position = CGPoint(x: 0, y: self.frame.size.height / 2)
chosenCandle.physicsBody = SKPhysicsBody(rectangleOfSize: chosenCandle.size)
chosenCandle.physicsBody?.dynamic = false
chosenCandle.physicsBody?.categoryBitMask = self.candleCategory
chosenCandle.physicsBody?.contactTestBitMask = self.matchCategory
chosenCandle.physicsBody?.collisionBitMask = 0
chosenCandle.physicsBody?.restitution = 0
candle.addChild(chosenCandle)
//Wick
let wickArea = SKSpriteNode(texture: wickTexture)
wickArea.name = "wickNode"
wickArea.position = CGPoint(x: 0, y: self.frame.size.height / 1.3)
wickArea.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: wickArea.size.width / 4, height: wickArea.size.height))
wickArea.physicsBody?.dynamic = false
wickArea.physicsBody?.categoryBitMask = self.wickCategory
wickArea.physicsBody?.contactTestBitMask = self.matchCategory
wickArea.physicsBody?.collisionBitMask = 0
wickArea.zPosition = 11
wickArea.physicsBody?.restitution = 0
candle.addChild(wickArea)
//Add the node and zPosition
self.partsMoving.addChild(candle)
chosenCandle.zPosition = 12
}
The candles are then created in a runBlock:
let createCandles = SKAction.runBlock({() in self.makeCandles()})
let briefPause = SKAction.waitForDuration(averageDelay, withRange: randomDelay)
let createAndPause = SKAction.sequence([createCandles, briefPause])
let createAndPauseForever = SKAction.repeatActionForever(createAndPause)
self.runAction(createAndPauseForever)
This is my function that changes the texture which is called in didBeginContact:
func updateFlame() {
if let newNode: SKNode = self.childNodeWithName("//wickNode") {
let updateTexture = SKAction.setTexture(flameTexture, resize: true)
newNode.runAction(updateTexture)
}
}
This is my didBeginContact function:
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == wickCategory || contact.bodyB.categoryBitMask == wickCategory {
score += 1
scoreLabel.text = "\(score)"
updateFlame()
} else {
runGameOverScene()
}
My problem is that it only changes the first node to a flame, and doesn't change any others. Even if it is the second or third wick on which contact is detected, only the first created wick is changed (the first one that comes across the screen). I know that contact is being detected on each node and that that works fine, because the score updates every time the match comes into contact with a wick.
What am I doing wrong that is stopping the texture of each node that individually comes into contact with the match from changing? Everything else is working just fine, but this part has had me beat for a week and everything I've tried doesn't work. This is the closest I've gotten.
After much trial and error, I have finally figured out how to make each node change texture when contact occurs! This is my code for that part:
func didBeginContact(contact: SKPhysicsContact) {
let collision : UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
if collision == (matchCategory | candleCategory | cakeCategory) {
runGameOverScene()
}
if (contact.bodyA.categoryBitMask == wickCategory) {
let newWick = contact.bodyA.node
let updateTexture = SKAction.setTexture(flameTexture, resize: true)
newWick!.runAction(updateTexture)
} else if (contact.bodyB.categoryBitMask == wickCategory) {
let newWick = contact.bodyB.node
let updateTexture = SKAction.setTexture(flameTexture, resize: true)
newWick!.runAction(updateTexture)
}
}
I followed the logic of this question (even though I wanted to set the texture, not remove it) and it worked perfectly: removeFromParent() Doesn't Work in SpriteKit.

Detecting when two of the same colors collide

I have a square consisting of four different colors in the middle of my scene. At the same time, I have smaller squares of the same color randomly generated from each sides of the scene with the intention of colliding with the square in the middle. (Blue to blue, yellow to yellow, etc).
My goal is to have it set up so that when a blue square collides with a blue square or any of the like, it will .removeFromParent(). How should I go about doing this? Will post code if necessary.
Edit:
enum BodyType: UInt32 {
case blueSquare = 1
case redSquare = 2
case yellowSquare = 4
case greenSquare = 8
}
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
didBeginContact() {
switch(contactMask) {
case BodyType.redSquare.rawValue | BodyType.redSquare.rawValue:
let scoreLabel = childNodeWithName("scores") as! Points
scoreLabel.increment()
let firstNode = contact.bodyB.node
firstNode?.removeFromParent()
default:
return
}
}
First thing you should do is set up the contactTestBitMasks & categoryBitMasks on all of your SKSpriteNodes, like this -
struct PhysicsCatagory {
static let FirstPerson : UInt32 = 0x1 << 1
static let SecondPerson : UInt32 = 0x1 << 2
}
override func didMoveToView(view: SKView) {
...
firstPerson.SKPhysicsBody?.catagoryBitMask = PhysicsCatagory.FirstPerson
firstPerson.SKPhysicsBody?.contactTestBitMask = PhysicsCatagory.SecondPerson
...
secondPerson.SKPhysicsBody?.catagoryBitMask = PhysicsCatagory.SecondPerson
secondPerson.SKPhysicsBody?.contactTestBitMask = PhysicsCatagory.FirstPerson
...
}
This is just setting up the catagoryBitMask and the contactTestBitMask. The categoryBitMask will be equal to the object you are currently editing, whereas, the contactTestBitMask will be equal to the object you want the object to collide with.
Also, before we move on, we want to add the Contact Delegate to our scene.
class GameScene: SKScene, SKPhysicsContactDelegate{...
And then add the delegate to our scene -
override func didMoveToView(view: SKView) {
...
self.physicsWorld.contactDelegate = self
...
Next, you add the didBeginContact
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA.node as! SKSpriteNode!
let secondBody = contact.bodyB.node as! SKSpriteNode!
}
Lastly inside of that, test...
func didBeginContact(contact: SKPhysicsContact) {
let firstBody = contact.bodyA.node as! SKSpriteNode!
let secondBody = contact.bodyB.node as! SKSpriteNode!
if firstBody.color == secondBody.color{
firstBody.removeFromParent()
secondBody.removeFromParent()
}
}
Hope that helps! :D
Once you detect a collision, compare the colors of the colliding squares and if equal, call .removeFromParent(). If you post code I could try to give the specific methods that would help.
If you want to get fancy you could create a subclass for your squares with a colorTag property (1 = blue, 2 = yellow ect.) and then compare the tags of the colliding squares. Although I doubt the cost of comparing the colors is much.

How to randomly choose a SKSpriteNode?

I want to choose from 4 enemies using random and present it on scene. For that purpose I've made this:
func enemyPicker() -> SKSpriteNode {
var enemyArray = [mouse, robot, drone, block, bird]
var countArray = UInt32(enemyArray.count)
var pickOneEneny = arc4random_uniform(countArray)
var randomElement = Int(pickOneEnemy)
return enemyArray.randomElement
}
But Xcode says to me that SKSpriteNode does not have a member named randomElement. And it surely doesn't, but how would I say to my function that I need it to pick and assign that random Int to an actual enemy from array?
I tried to use this answer but it's not working for me. I also tried to change -> SKSpriteNode to SKTexture, String and "T" and had not any luck with it.
My SpriteNodes are declared like:
var mouse = SKSpriteNode()
let mouseAtlas = SKTextureAtlas(named: "mouse")
var mouseArray = [SKTexture]()
mouseArray.append(mouseAtlas.textureNamed("mouse_0"));
mouseArray.append(mouseAtlas.textureNamed("mouse_1"));
mouseArray.append(mouseAtlas.textureNamed("mouse_2"));
mouse = SKSpriteNode(texture: mouseArray[0]);
self.mouse.position = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMidY(self.frame) - 138)
self.mouse.size = CGSizeMake(self.mouse.size.width, self.mouse.size.height + mouse.size.height / 2)
self.mouse.name = "mouse"
self.addChild(mouse)
func enemyPicker() -> SKSpriteNode {
let enemyArray = [mouse, robot, drone, block, bird]
return enemyArray[Int(arc4random_uniform(UInt32(enemyArray.count)))]
}

How to pause an SKSpriteNode, Swift

I created this game using sprite kit. During the game sprite nodes are moving. When the "Game Over" Label pops up, I would like the monster sprite node to stop moving or pause, but the rest of the scene to still move on. I know where to put the code, I just don't know how to write it. This is my monster code.
func addMonster() {
// Create sprite
let monster = SKSpriteNode(imageNamed: "box")
monster.setScale(0.6)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size)
monster.physicsBody?.dynamic = true
monster.physicsBody?.categoryBitMask = UInt32(monsterCategory)
monster.physicsBody?.contactTestBitMask = UInt32(laserCategory)
monster.physicsBody?.collisionBitMask = 0
monster.name = "box"
var random : CGFloat = CGFloat(arc4random_uniform(320))
monster.position = CGPointMake(random, self.frame.size.height + 20)
self.addChild(monster)
}
EDIT
override func update(currentTime: CFTimeInterval) {
if isStarted == true {
if currentTime - self.lastMonsterAdded > 1 {
self.lastMonsterAdded = currentTime + 3.0
self.addMonster()
}
self.moveObstacle()
} else {
}
self.moveBackground()
if isGameOver == true {
}
}
If I understand your question correctly, you want to pause a single (or small number of) nodes. In that case...
monster.paused = true
Is probably what you want.