didBeginContact() called without contact - ios

I have a game that is ready to publish, but my fiancé who is still on iOS 8.1 had this issue. It's fine with iOS 9 but I recreated the problem on the simulator with iOS 8.1.
When I click start game and the scene is loaded, enemies begin to appear at the edges of the screen. They use actions to push them to the other edge of the screen and then they are removed from their parent. Only, didBeginContact() is called as soon as they are added to the screen.
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let fishy : UInt32 = 0b1 // 1
static let enemy : UInt32 = 0b10 // 2
static let powerup : UInt32 = 0b11 // 3
}
Good guy (fishy) set up and movement)
fishy.physicsBody = SKPhysicsBody(texture:fishy.texture!, size:fishy.size)
fishy.physicsBody?.dynamic = true
fishy.physicsBody?.categoryBitMask = PhysicsCategory.fishy
fishy.physicsBody?.contactTestBitMask = PhysicsCategory.enemy
fishy.physicsBody?.collisionBitMask = PhysicsCategory.None
fishy.physicsBody?.usesPreciseCollisionDetection = true
Enemy set up and movement
enemy.physicsBody = SKPhysicsBody(texture:enemy.texture!, size:enemy.size)
enemy.physicsBody?.dynamic = true // 2
enemy.physicsBody?.categoryBitMask = PhysicsCategory.enemy // 3
enemy.physicsBody?.contactTestBitMask = PhysicsCategory.fishy // 4
enemy.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
self.addChild(enemy)
let action1 = SKAction.rotateToAngle(angle, duration: 0.0, shortestUnitArc: true)
let action = SKAction.moveTo(finishPos,duration:Double(random(min: 5,max: 13)))
let action2 = SKAction.removeFromParent()
let sequence = SKAction.sequence([action1,action,action2])
enemy.runAction(sequence)
This is called for most of the enemy nodes that are added, but not all. I only want it called when they contact, but it is running at their creation. (It calls even when the path of the enemy is not in contact with the fishy)
func didBeginContact(contact: SKPhysicsContact) {
var enemyContact: SKPhysicsBody
var fishyContact: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
fishyContact = contact.bodyA
enemyContact = contact.bodyB
} else {
fishyContact = contact.bodyB
enemyContact = contact.bodyA
}
if ((enemyContact.categoryBitMask & PhysicsCategory.enemy != 0) && (fishyContact.categoryBitMask & PhysicsCategory.fishy != 0)) {
if (enemyContact.node != nil && fishyContact.node != nil) {
projectileDidCollide(fishyContact.node as! SKSpriteNode, badGuy:enemyContact.node as! SKSpriteNode)
}
}
}
Again, this doesn't occur in iOS 9.0 and above. Thanks in advance.

Related

Spritekit Detecting Sprite Overlap with Collisions off

Currently I have a Swift/Spritekit app that drops a sprite from the sky, they have collisions off so they can fall through the floor, however I am trying to make a statement that will detect when the player sprite touches the sprite falling from the sky then deletes that child and adds a point to the score var.
Update: 04/29/18 2:10 CST
As of now this is what I have
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var activePlayer:SKSpriteNode! = SKSpriteNode() //Sets active character
var bg:SKSpriteNode! = SKSpriteNode()
var bananaCollected = 0 //Defines banana var
var timer: Timer?
let bananaCat : UInt32 = 0x1 << 1
let playerCat : UInt32 = 0x1 << 2
func bananaDrop() { //Defines bananaDrop
let banana = SKSpriteNode(imageNamed:"banana")
//print("- Debug: [bananaDrop] successfully initiated -")
banana.name = "banana"
//Size of banana
banana.xScale = 0.25
banana.yScale = 0.25
//Defines physics properties
let physicsBody = SKPhysicsBody(circleOfRadius: 15)
//let physicsBody = SKPhysicsBody()
//physicsBody.pinned = true //Suspend in air
physicsBody.allowsRotation = false
physicsBody.affectedByGravity = true
//physicsBody.collisionBitMask = 0
banana.physicsBody?.categoryBitMask = bananaCat
banana.physicsBody?.collisionBitMask = 0
banana.physicsBody?.contactTestBitMask = playerCat
//categoryBitMask is what the physics category of the object is
//banana.physicsBody!.categoryBitMask = playerCategory
//collisionBitMask is what the physics category of objects that this cannot pass through are...multiple categories would be typed like... `cat1 | cat2`
//banana.physicsBody!.collisionBitMask = 0
//contactTestBitMask is what the physics category of the objects that we get alerted to upon contact
//activePlayer.physicsBody!.contactTestBitMask = obstacleCategory
banana.physicsBody = physicsBody
//Starting Location Defined
var x: CGFloat = 0 //Defines X
let y: CGFloat = 400 //Defines how high up banana drops
let bananaDrop = GKShuffledDistribution(lowestValue: 1, highestValue: 11)
//Drop locations defined (relation to X)
switch bananaDrop.nextInt() {
case 1:
x = -170
case 2:
x = -160
case 3:
x = -120
case 4:
x = -80
case 5:
x = -40
case 6:
x = 0
case 7:
x = 40
case 8:
x = 80
case 9:
x = 120
case 10:
x = 160
case 11:
x = 170
default:
fatalError("Case num outside range")
}
banana.position = CGPoint(x: x, y: y)
//Adds banana
self.addChild(banana)
}
func bDropF() { //Defintes Banana Drop Final
Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] timer in
self?.timer = timer
self?.timerTime()
})
}
//Stop droping bananas
deinit {
self.timer?.invalidate()
}
func timerTime() {
bananaDrop()
}
override func didMove(to view: SKView) {
print("- Debug: Game Scene Loaded -")
bDropF() //Calls banana drop
if let setupBG:SKSpriteNode = self.childNode(withName: "bg") as? SKSpriteNode {
bg = setupBG
bg.name = "bg"
bg.physicsBody?.affectedByGravity = false
bg.zPosition = -1
}
func didBeginContact(_ contact: SKPhysicsContact){ //Banana Collect
if let firstNode = contact.bodyA.node as? SKSpriteNode, let secondNode = contact.bodyB.node as? SKSpriteNode {
let object1: String = firstNode.name!
let object2: String = secondNode.name!
if (object1 == "player") || (object2 == "banana") {
print("colliding!")
}
}
}
//Player Definitions
if let randoPlayer:SKSpriteNode = self.childNode(withName: "player") as? SKSpriteNode { //Test for Char type
activePlayer = randoPlayer //Char Set
activePlayer.physicsBody?.isDynamic = true //Set dynamic
activePlayer.physicsBody?.affectedByGravity = true //Set dynamic gravity
activePlayer.name = "player"
print("Player Initiated")
print("Physics set :: Dynamic(true):AffectedByGravity(true)")
} else {
print("Failed to initiate player")
}
}
func moveActivePlayerR() {//Right Touch Player Movements Defined
let walkAnimation:SKAction = SKAction(named: "WalkRight")!
let moveAction:SKAction = SKAction.moveBy(x: 100, y: 0, duration: 0.5) //Move Right Side
//let moveRight:SKAction = SKAction.group([walkAnimation, moveAction]) //Depricated
let sound = SKAction.playSoundFileNamed("walk.wav", waitForCompletion: false)
let finalWalkR:SKAction = SKAction.group([walkAnimation, moveAction, sound])
activePlayer.run(finalWalkR)
}
func moveActivePlayerL() { //Left Touch Player Movements Defined
let walkAnimation:SKAction = SKAction(named: "WalkLeft")!
let moveAction:SKAction = SKAction.moveBy(x: -100, y: 0, duration: 0.5) //Move Left Side
//let moveRight:SKAction = SKAction.group([walkAnimation, moveAction]) //Depricated
let sound = SKAction.playSoundFileNamed("walk.wav", waitForCompletion: false)
let finalWalkL:SKAction = SKAction.group([moveAction, sound, walkAnimation])
activePlayer.run(finalWalkL)
}
func touchDown(atPoint pos : CGPoint) {
print("touch \( pos.x),\(pos.y)") //Debug print
if(pos.x > 0) { //if touched right side of screen
print("Right touch")
moveActivePlayerR()
} else if (pos.x < 0) { //if touched left side of screen
print("Left touch")
moveActivePlayerL()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
self.touchDown(atPoint: t.location(in: self))
}
}
}
However even with these categories setup it still appears that I am unable to get them to collide properly. When they touch each other there is nothing in my console indicating that they have collided. Please help!
You do not have to have a collisionBitMask set in order to detect contact, you just need to set the contactTestBitMask to the category of the obstacle you want to detect the collision with. You then check for the collision in the didBegin func. Ensure that you have the SKPhysicsContactDelegate set on your scene.
class GameScene: SKScene, SKPhysicsContactDelegate
self.physicsWorld.contactDelegate = self
when setting up your objects
//categoryBitMask is what the physics category of the object is
object.physicsBody!.categoryBitMask = playerCategory
//collisionBitMask is what the physics category of objects that this cannot pass through are...multiple categories would be typed like... `cat1 | cat2`
object.physicsBody!.collisionBitMask = 0
//contactTestBitMask is what the physics category of the objects that we get alerted to upon contact
object.physicsBody!.contactTestBitMask = obstacleCategory
the didBegin func in your scene
func didBeginContact(_ contact: SKPhysicsContact) {
if let firstNode = contact.bodyA.node as? SKSpriteNode, let secondNode = contact.bodyB.node as? SKSpriteNode {
let object1: String = firstNode.name!
let object2: String = secondNode.name!
if (object1 == "obstacle") || (object2 == "obstacle") {
//run some code because these 2 have collided
}
}
}
...or...
func didBeginContact(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == PhysicsCategory.obstacleCategory || contact.bodyB.categoryBitMask == PhysicsCategory.obstacleCategory {
//obstacle has hit player do something
}
}

Detecting multiple collisions in SpriteKit

I'm still a beginner in Swift and I'm having some trouble with collision detections in SpriteKit. I used this question from StackOverFlow which was great in showing my how to construct things neatly. But I'm having problems with my didBegin function, which does not even get called to at all. I'm hoping I missed something simple out that you guys can take a look at for me.
Thanks in advance.
Here is my PhysicsCatagoies struct:
import Foundation
import SpriteKit
struct PhysicsCatagories: OptionSet {
let rawValue: UInt32
init(rawValue: UInt32) { self.rawValue = rawValue }
static let None = PhysicsCatagories(rawValue: 0b00000) // Binary for 0
static let Player = PhysicsCatagories(rawValue: 0b00001) // Binary for 1
static let EnemyBullet = PhysicsCatagories(rawValue: 0b00010) // Binary for 2
static let PlayerBullet = PhysicsCatagories(rawValue: 0b00100) // Binary for 4
static let Enemy = PhysicsCatagories(rawValue: 0b01000) // Binary for 8
static let Boss = PhysicsCatagories(rawValue: 0b10000) // Binary for 16
}
extension SKPhysicsBody {
var category: PhysicsCatagories {
get {
return PhysicsCatagories(rawValue: self.categoryBitMask)
}
set(newValue) {
self.categoryBitMask = newValue.rawValue
}
}
}
And here is how I assigned my nodes in GameScene:
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
player.physicsBody!.affectedByGravity = false
player.physicsBody!.categoryBitMask = PhysicsCatagories.Player.rawValue
player.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
player.physicsBody!.category = [.Enemy, .EnemyBullet, .Boss]
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.categoryBitMask = PhysicsCatagories.PlayerBullet.rawValue
bullet.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
bullet.physicsBody!.category = [.Enemy, .Boss]
enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.size)
enemy.physicsBody!.affectedByGravity = false
enemy.physicsBody!.categoryBitMask = PhysicsCatagories.Enemy.rawValue
enemy.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
enemy.physicsBody!.category = [.Player, .PlayerBullet]
enemyBullet.physicsBody = SKPhysicsBody(rectangleOf: enemyBullet.size)
enemyBullet.physicsBody!.affectedByGravity = false
enemyBullet.physicsBody!.categoryBitMask = PhysicsCatagories.EnemyBullet.rawValue
enemyBullet.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
enemyBullet.physicsBody!.category = [.Player]
boss.physicsBody = SKPhysicsBody(rectangleOf: boss.size)
boss.physicsBody!.affectedByGravity = false
boss.physicsBody!.categoryBitMask = PhysicsCatagories.Boss.rawValue
boss.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
boss.physicsBody!.category = [.Player, .PlayerBullet]
bulletSpecial.physicsBody = SKPhysicsBody(rectangleOf: bulletSpecial.size)
bulletSpecial.physicsBody!.affectedByGravity = false
bulletSpecial.physicsBody!.categoryBitMask = PhysicsCatagories.PlayerBullet.rawValue
bulletSpecial.physicsBody!.collisionBitMask = PhysicsCatagories.None.rawValue
bulletSpecial.physicsBody!.category = [.Enemy, .Boss]
Finally, this is my didBegin function, which does not seem to work at all:
func didBegin(_ contact: SKPhysicsContact) {
let contactCategory: PhysicsCatagories = [contact.bodyA.category, contact.bodyB.category]
switch contactCategory {
case [.Player, .Enemy]:
print("player has hit enemy")
case [.PlayerBullet, .Enemy]:
print("player bullet has hit enemy")
case [.PlayerBullet, .Boss]:
print("player bullet has hit boss")
case [.Player, .Boss]:
print("player has hit boss")
case [.Player, .EnemyBullet]:
print("player has hit enemy bullet")
default:
preconditionFailure("Unexpected collision type: \(contactCategory)")
}
}
I've not used the OptionSet technique for cagegoryBitMasks, so here's how I'd do it:
Define unique categories, ensure your class is a SKPhysicsContactDelegate and make yourself the physics contact delegate:
//Physics categories
let PlayerCategory: UInt32 = 1 << 0 // b'00001'
let EnemyBulletCategory: UInt32 = 1 << 1 // b'00010'
let PlayerBulletCategory: UInt32 = 1 << 2 // b'00100'
let EnemyCategory: UInt32 = 1 << 3 // b'01000'
let BossCategory: UInt32 = 1 << 4 // b'10000'
class GameScene: SKScene, SKPhysicsContactDelegate {
physicsWorld.contactDelegate = self
Assign the categories (usually in didMove(to view:) :
player.physicsBody.catgeoryBitMask = PlayerCategory
bullet.physicsBody.catgeoryBitMask = BulletCategory
enemy.physicsBody.catgeoryBitMask = EnemyCategory
enemyBullet.physicsBody.catgeoryBitMask = EnemyBulletCategory
boss.physicsBody.catgeoryBitMask = BossCategory
(not sure about bulletSpecial - looks the same as bullet)
Set up contact detection:
player.physicsBody?.contactTestBitMask = EnemyCategory | EnemyBulletCategory | BossCategory
bullet.physicsBody?.contactTestBitMask = EnemyCategory | BossCategory
enemy.physicsBody?.contactTestBitMask = PlayerCategory | PlayerBulletCategory
enemyBullet.physicsBody?.contactTestBitMask = PlayerCategory
boss.physicsBody?.contactTestBitMask = PlayerCategory | PlayerBulletCategory
Turn off collisions: (on by default)
player.physicsBody?.collisionBitMask = 0
bullet.physicsBody?.collisionBitMask = 0
enemy.physicsBody?.collisionBitMask = 0
enemyBullet.physicsBody?.collisionBitMask = 0
boss.physicsBody?.collisionBitMask = 0
Implement didBegin:
func didBegin(_ contact: SKPhysicsContact) {
print("didBeginContact entered for \(String(describing: contact.bodyA.node!.name)) and \(String(describing: contact.bodyB.node!.name))")
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case PlayerCategory | EnemyCategory:
print("player has hit enemy")
case PlayerBulletCategory | EnemyCategory:
print("player bullet has hit enemy")
case PlayerBulletCategory | BossCategory:
print("player bullet has hit boss")
case PlayerCategory | BossCategory:
print("player has hit boss")
case PlayerCategory | EnemyBulletCategory:
print("player has hit enemy bullet")
default:
print("Undetected collision occurred")
}
}
It's a bit late here, so hopefully I haven't made any stupid mistakes.
=======================
You could also include this function and then call it via checkPhysics() once you have set up all your physics bodies and collisions and contact bit masks. It will go through every node and print out what collides with what and what contacts what (it doesn't check the isDynamic property, so watch out for that):
//MARK: - Analyse the collision/contact set up.
func checkPhysics() {
// Create an array of all the nodes with physicsBodies
var physicsNodes = [SKNode]()
//Get all physics bodies
enumerateChildNodes(withName: "//.") { node, _ in
if let _ = node.physicsBody {
physicsNodes.append(node)
} else {
print("\(String(describing: node.name)) does not have a physics body so cannot collide or be involved in contacts.")
}
}
//For each node, check it's category against every other node's collion and contctTest bit mask
for node in physicsNodes {
let category = node.physicsBody!.categoryBitMask
// Identify the node by its category if the name is blank
let name = node.name != nil ? node.name : "Category \(category)"
if category == UInt32.max {print("Category for \(String(describing: name)) does not appear to be set correctly as \(category)")}
let collisionMask = node.physicsBody!.collisionBitMask
let contactMask = node.physicsBody!.contactTestBitMask
// If all bits of the collisonmask set, just say it collides with everything.
if collisionMask == UInt32.max {
print("\(name) collides with everything")
}
for otherNode in physicsNodes {
if (node != otherNode) && (node.physicsBody?.isDynamic == true) {
let otherCategory = otherNode.physicsBody!.categoryBitMask
// Identify the node by its category if the name is blank
let otherName = otherNode.name != nil ? otherNode.name : "Category \(otherCategory)"
// If the collisonmask and category match, they will collide
if ((collisionMask & otherCategory) != 0) && (collisionMask != UInt32.max) {
print("\(name) collides with \(String(describing: otherName))")
}
// If the contactMAsk and category match, they will contact
if (contactMask & otherCategory) != 0 {print("\(name) notifies when contacting \(String(describing: otherName))")}
}
}
}
}
It will produce output like:
Optional("shape_blueSquare") collides with Optional("Screen_edge")
Optional("shape_redCircle") collides with Optional("Screen_edge")
Optional("shape_redCircle") collides with Optional("shape_blueSquare")
Optional("shape_redCircle") notifies when contacting Optional("shape_purpleSquare")
Optional("shape_redCircle") collides with Optional("shape_greenRect")
Optional("shape_redCircle") notifies when contacting Optional("shape_greenRect")
Optional("shape_purpleSquare") collides with Optional("Screen_edge")
Optional("shape_purpleSquare") collides with Optional("shape_greenRect")
Category for Optional("shape_greenRect") does not appear to be set correctly as 4294967295
Optional("shape_greenRect") collides with Optional("Screen_edge")
Optional("shape_yellowTriangle") notifies when contacting Optional("shape_redCircle")
Optional("shape_yellowTriangle") collides with Optional("shape_greenRect")
Optional("shape_yellowTriangle") notifies when contacting Optional("shape_greenRect")
etc.

Collide type source error - spritekit swift game

So I have adjusted my games ball type from a shape to an image which forced me to redo the physics of my game. I am new to swift and have struggled with fixing my collisions in my swift game.
class GameScene: SKScene, GameDelegate, SKPhysicsContactDelegate {
var ball: Ball!
let ballSpeedX = CGFloat(500)
//ball = Ball(imageNamed:"colorBall.png")
enum CollisionTypes: UInt32 {
case Floor = 1
case Ball = 2
}
// board
let boards = Boards.make(CollideType.BoardStart.rawValue)
var lastBoard: SKNode?
var boardSpeedY: CGFloat { get { return CGFloat(160) * accelerate }}
let boardDistanceY = CGFloat(300)
let boardYDistanceHide = CGFloat(30)
override func didMoveToView(view: SKView) {
self.physicsWorld.gravity = CGVectorMake(0, 0)
self.physicsWorld.contactDelegate = self
let flooeBody = SKPhysicsBody.init(edgeLoopFromRect: self.frame)
self.physicsBody = floorBody
self.physicsBody!.affectedByGravity = false
self.physicsBody!.usesPreciseCollisionDetection = true
self.physicsBody!.dynamic = true
self.physicsBody!.mass = 0
self.physicsBody!.friction = 0
self.physicsBody!.linearDamping = 0
self.physicsBody!.angularDamping = 0
self.physicsBody!.restitution = 1
self.physicsBody!.categoryBitMask = CollisionTypes.Floor.rawValue
self.physicsBody!.contactTestBitMask = CollisionTypes.Ball.rawValue
// Prepare the ball - physics engine.
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.frame.width/2)
ball.physicsBody!.affectedByGravity = true
ball.physicsBody!.restitution = 0.8
ball.physicsBody!.linearDamping = 0
ball.physicsBody!.friction = 0.3
ball.physicsBody!.dynamic = true
ball.physicsBody!.mass = 0.5
ball.physicsBody!.allowsRotation = true
ball.physicsBody!.categoryBitMask = CollisionTypes.Ball.rawValue
ball.physicsBody!.contactTestBitMask = CollisionTypes.Floor.rawValue
ball.physicsBody!.collisionBitMask = CollisionTypes.Floor.rawValue
ball.hidden = false
self.addChild(ball)
// scene
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody!.categoryBitMask = CollideType.Scene.toMask()
self.physicsBody!.dynamic = false
self.physicsBody!.friction = 0
// ceil
let ceil = SKShapeNode(rectOfSize: CGSize(width: self.frame.width, height: 2))
ceil.position.x = CGRectGetMidX(self.frame)
ceil.position.y = self.frame.height - CGRectGetMidY(ceil.frame)
ceil.physicsBody = SKPhysicsBody(rectangleOfSize: ceil.frame.size)
ceil.physicsBody!.categoryBitMask = CollideType.Ceil.toMask()
ceil.physicsBody!.dynamic = false
ceil.alpha = 0
self.addChild(ceil)
// floor
let floor = SKShapeNode(rectOfSize: CGSize(width: self.frame.width, height: 2))
floor.position.x = CGRectGetMidX(self.frame)
floor.position.y = CGRectGetMidY(floor.frame)
floor.physicsBody = SKPhysicsBody(rectangleOfSize: floor.frame.size)
//floor.physicsBody!.categoryBitMask = CollideType.Floor.toMask() two
floor.physicsBody!.dynamic = false
floor.alpha = 0
self.addChild(floor)
}
That is the scene and physics I attempted to set up and was directed with. Below is the collide errors which cause the app to crash upon touch.
func didBeginContact(contact: SKPhysicsContact) {
let bitMaskAAndB = contact.bodyA.categoryBitMask == CollisionTypes.Floor.rawValue && contact.bodyB.categoryBitMask == CollisionTypes.Ball.rawValue
let ballAndBoardMask = CollideType.Ball.toMask() | boards.usedCollideMasks
// ball and board error: CANNOT CONVERT VALUE OF TYPE BOOL TO EXPCTD ARG TYPE UIINT32
if bitMaskAAndB | ballAndBoardMask == ballAndBoardMask {
let boardNode: SKNode! = contact.bodyA.categoryBitMask == CollideType.Ball.toMask() ? contact.bodyB.node : contact.bodyA.node
let board = boardNode.bind as! BoardDelegate
board.didBeginContact(boardNode, ball: ball, contact: contact, game: self)
}
// ball and ceil => ERROR
else if bitMaskAAndB == CollideType.toMask([.Ball, .Ceil]) {
stopGame()
}
// ball and floor => stop game
else if bitMaskAAndB == CollideType.toMask([.Ball, .Floor]) {
stopGame()
}
}
func didEndContact(contact: SKPhysicsContact) {
let ballAndBoardMask = CollideType.Ball.toMask() | boards.usedCollideMasks
// ball and board, handle it by board delegate
if contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask | ballAndBoardMask == ballAndBoardMask {
let boardNode: SKNode! = contact.bodyA.categoryBitMask == CollideType.Ball.toMask() ? contact.bodyB.node : contact.bodyA.node
let board = boardNode.bind as! BoardDelegate
board.didEndContact(boardNode, ball: ball, contact: contact, game: self)
}
}
CollideType definition as below:
enum CollideType: Int {
case Scene = 0
case Ceil = 1
case Floor = 2
case Ball = 3
case BoardStart = 4
func toMask()-> UInt32 {
return UInt32(1 << self.rawValue)
}
static func toMask(masks: [CollideType])-> UInt32 {
var toMask = UInt32(0)
for type in masks {
toMask |= type.toMask()
}
return toMask
}
So the issue now is that you have two definition for your masks:
Remove this one:
enum CollisionTypes: UInt32 {
case Floor = 1
case Ball = 2
}
Use only this one:
enum CollideType: Int {
case Scene = 0
case Ceil = 1
case Floor = 2
case Ball = 3
case BoardStart = 4
func toMask()-> UInt32 {
return UInt32(1 << self.rawValue)
}
static func toMask(masks: [CollideType])-> UInt32 {
var toMask = UInt32(0)
for type in masks {
toMask |= type.toMask()
}
return toMask
}
Correct all code to match with CollideType definitions.
Correct this line in didBeginContact:
let bitMaskAAndB = contact.bodyA.categoryBitMask == CollisionTypes.Floor.rawValue && contact.bodyB.categoryBitMask == CollisionTypes.Ball.rawValue
with:
let bitMaskAAndB = contact.bodyA.categoryBitMask == CollideType.Floor.toMask() ? contact.bodyB.categoryBitMask : CollideType.Ball.toMask()
If you have correct all you will don't have yet this error:
// ball and ceil => ERROR

Collisions between sprites in SpriteKit

I'm making a game in XCode using SpriteKit. The game has a player and different types of projectiles that he has to avoid. When the player collides with the projectiles, the score changes and the projectile disappears. However, when two projectiles collide, they kind of bounce away.
I want to make that every time two projectiles collide, they act like nothing happened and they keep going in their original path. What should I do?
*Note: This is not the whole code, it's just what matters.
import SpriteKit
struct Physics {
static let player : UInt32 = 1
static let missileOne : UInt32 = 2
static let missileTwo : UInt32 = 3
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var player = SKSpriteNode(imageNamed: "p1.png")
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
player.position = CGPointMake(self.size.width/2, self.size.height/5)
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size)
player.physicsBody?.affectedByGravity = false
player.physicsBody?.dynamic = false
player.physicsBody?.categoryBitMask = Physics.player
player.physicsBody?.collisionBitMask = Physics.missileOne
player.physicsBody?.collisionBitMask = Physics.missileTwo
var missileOneTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("SpawnMissileOne"), userInfo: nil, repeats: true)
var missileTwoTimer = NSTimer.scheduledTimerWithTimeInterval(1.2, target: self, selector: Selector("SpawnMissileTwo"), userInfo: nil, repeats: true)
self.addChild(player)
}
//When contact happens
func didBeginContact(contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody = contact.bodyA
var secondBody : SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == Physics.player) && (secondBody.categoryBitMask == Physics.missileOne)) {
CollisionWithMissileOne(firstBody.node as SKSpriteNode, missileOne: secondBody.node as SKSpriteNode)
} else if ((firstBody.categoryBitMask == Physics.player) && (secondBody.categoryBitMask == Physics.missileTwo)){
CollisionWithMissileTwo(firstBody.node as SKSpriteNode, missileTwo: secondBody.node as SKSpriteNode)
} else if ((firstBody.categoryBitMask == Physics.missileOne)&&(secondBody.categoryBitMask == Physics.missileTwo)) {
CollisionBetweenMissiles(firstBody.node as SKSpriteNode, missileTwo: secondBody.node as SKSpriteNode)
}
}
//For Player and MissileOne
func CollisionWithMissileOne(player: SKSpriteNode, missileOne: SKSpriteNode) {
missileOne.removeFromParent()
}
//For Player and MissileTwo
func CollisionWithMissileOne(player: SKSpriteNode, missileTwo: SKSpriteNode) {
missileTwo.removeFromParent()
}
//For MissileOne and MissileTwo
func CollisionBetweenMissiles(missileOne: SKSpriteNode, missileTwo: SKSpriteNode) {
???WHAT SHOULD I CODE HERE???
}
}
The confusion is that the collisionBitMask is used to define which physicsBodies that interacts in the physicsModel. What you really want is contactTestBitmask.
Also your Physics doesn't return proper values to use for a Bit Mask. As pure Ints they should be 1,2,4,8 etc.
Here is your code changed to something that (hopefully) works, I've commented changes wherever I've made them.
struct Physics {
static let player : UInt32 = 1
static let missileOne : UInt32 = 2
static let missileTwo : UInt32 = 4 // to work properly as bit masks
}
This change is necessary if you want to check for contact with more than one type of physicsBody.categoryBitMask. Check out the player.physicsBody?.contactTestBitMask = ... in didMoveToView:
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
// All your existing player-stuff is fine until...
// contactTest, not collision but contact, also: use bitwise OR
player.physicsBody?.contactTestBitMask = Physics.missileOne | Physics.missileTwo
self.addChild(player)
// It is not recommended to use NSTimer for SpriteKit, use SKActions instead
let missileOneWait = SKAction.waitForDuration(1)
let callSpawnOneAction = SKAction.runBlock({ self.spawnMissileOne() })
let missileOneRepeat = SKAction.repeatActionForever(SKAction.sequence([missileOneWait, callSpawnOneAction]))
runAction(missileOneRepeat)
let missileTwoWait = SKAction.waitForDuration(1.2)
let callSpawnTwoAction = SKAction.runBlock({ self.spawnMissileTwo() })
let missileTwoRepeat = SKAction.repeatActionForever(SKAction.sequence([missileTwoWait, callSpawnTwoAction]))
runAction(missileTwoRepeat)
}
Pretty much rewritten didBeginContact to something I believe reads and scales a lot better:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody = contact.bodyA
var secondBody = contact.bodyB
// Rewritten with dynamic variables
var playerNode : SKSpriteNode? {
if firstBody.categoryBitMask == Physics.player {
return firstBody.node as? SKSpriteNode
} else if secondBody.categoryBitMask == Physics.player {
return secondBody.node as? SKSpriteNode
}
return nil
}
// If you want to handle contact between missiles you need to split this
// into two different variables
var missileNode : SKSpriteNode? {
let bitmask1 = firstBody.categoryBitMask
let bitmask2 = secondBody.categoryBitMask
if bitmask1 == Physics.missileOne || bitmask1 == Physics.missileTwo {
return firstBody.node as? SKSpriteNode
} else if bitmask2 == Physics.missileOne || bitmask2 == Physics.missileTwo {
return secondBody.node as? SKSpriteNode
}
return nil
}
if playerNode != nil {
collisionBetweenPlayer(playerNode, missile: missileNode)
}
}
Then you'll only need one function for contact between missile and player:
func collisionBetweenPlayer(player: SKSpriteNode?, missile: SKSpriteNode?) {
missile?.removeFromParent()
}

How may i detect contact and collision correctly? Swift iOS SKScene

The problem is i can't detect the collision //or contact in any way i have found on the internet
This is my code:
The declaration of my masks:
private let ballCategory : UInt32 = 0x1 << 0
private let holeCategory : UInt32 = 0x1 << 1
The adding of both the hole and the ball:
func addHole(#size : CGSize) {
let actionMoveDown = SKAction.moveToY(CGRectGetMidY(self.frame)-500, duration: 4.7)
let hole = shapedHoles()
let UT = UTIL()
var position:CGFloat
let randomPosition = UT.randomNumberWith(Min: 1, Max: 3)
switch randomPosition{
case 1:
position = CGRectGetMidX(self.frame)
case 2:
position = CGRectGetMidX(self.frame)+size.width
default:
position = CGRectGetMidX(self.frame)-(size.width)
}
var createdHole = hole.createHoleAtPosition(position: CGPointMake(position ,CGRectGetMaxY(self.frame) + (size.height/2)),size: size )//CGSizeMake(CGRectGetMaxX(self.frame)/3 - 10, 70)
createdHole.physicsBody = SKPhysicsBody(rectangleOfSize: createdHole.frame.size)
createdHole.physicsBody?.categoryBitMask = holeCategory
createdHole.physicsBody?.collisionBitMask = 0
createdHole.physicsBody?.contactTestBitMask = ballCategory
createdHole.physicsBody?.affectedByGravity = false
createdHole.physicsBody?.dynamic = false
lastHolePosition = randomPosition
createdHole .runAction(actionMoveDown)
self.addChild(createdHole)
}
func addSphere(){
let mainCharacterController = circle()
let character: (SKNode) = mainCharacterController.createCircleAtPosition(position: CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinY(self.frame)+100))
character.physicsBody = SKPhysicsBody(circleOfRadius: character.frame.size.height/2)
character.physicsBody?.categoryBitMask = ballCategory
character.physicsBody?.collisionBitMask = 0
character.physicsBody?.contactTestBitMask = holeCategory
character.physicsBody?.affectedByGravity = false
character.physicsBody?.dynamic = false
self.addChild(character)
} func addHole(#size : CGSize) {
let actionMoveDown = SKAction.moveToY(CGRectGetMidY(self.frame)-500, duration: 4.7)
let hole = shapedHoles()
let UT = UTIL()
var position:CGFloat
let randomPosition = UT.randomNumberWith(Min: 1, Max: 3)
switch randomPosition{
case 1:
position = CGRectGetMidX(self.frame)
case 2:
position = CGRectGetMidX(self.frame)+size.width
default:
position = CGRectGetMidX(self.frame)-(size.width)
}
var createdHole = hole.createHoleAtPosition(position: CGPointMake(position ,CGRectGetMaxY(self.frame) + (size.height/2)),size: size )//CGSizeMake(CGRectGetMaxX(self.frame)/3 - 10, 70)
createdHole.physicsBody = SKPhysicsBody(rectangleOfSize: createdHole.frame.size)
createdHole.physicsBody?.categoryBitMask = holeCategory
createdHole.physicsBody?.collisionBitMask = 0
createdHole.physicsBody?.contactTestBitMask = ballCategory
createdHole.physicsBody?.affectedByGravity = false
createdHole.physicsBody?.dynamic = false
lastHolePosition = randomPosition
createdHole .runAction(actionMoveDown)
self.addChild(createdHole)
}
func addSphere(){
let mainCharacterController = circle()
let character: (SKNode) = mainCharacterController.createCircleAtPosition(position: CGPointMake(CGRectGetMidX(self.frame), CGRectGetMinY(self.frame)+100))
character.physicsBody = SKPhysicsBody(circleOfRadius: character.frame.size.height/2)
character.physicsBody?.categoryBitMask = ballCategory
character.physicsBody?.collisionBitMask = 0
character.physicsBody?.contactTestBitMask = holeCategory
character.physicsBody?.affectedByGravity = false
character.physicsBody?.dynamic = false
self.addChild(character)
}
And last but not least the didBeginContactMethod
func didBeginContact(contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody!
var secondBody: SKPhysicsBody!
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
}
else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & holeCategory) != 0 &&
(secondBody.categoryBitMask & ballCategory) != 0 {
println("HO")
}
}
Thank you and hope you know what is happening, and if you need any extra code just comment it
You have to make sure that the SKScene subclass you are using also implements the SKPhysicsContactDelegate protocol. For example, it would look like this.
class MyScene : SKScene, SKPhysicsContactDelegate
Then you have to set your physics worlds contact delegate to yourself.
override init() {
self.physicsWorld.contactDelegate = self;
}
Tell me how this works out, but it should solve the problem and successfully allow you to listen in on collisions.
There is a couple things it might be. First you can try using an enum for the contact categories.
enum collisionBodies:UInt32 {
case ballCategory = 1
case holeCategory = 2
}
func Collisions() {
character.physicsBody.categoryBitMask = collisionBodies.ballCategory.rawValue
character.physicsBody.contactTestBitMask = collisionBodies.holeCategory.rawValue
character.physicsBody.collisionBitMask = 0
createdHole.physicsBody.categoryBitMask = collisionBodies.holeCategory.rawValue
createdHole.physicsBody.contactTestBitMask = collisionBodies.ballCategory.rawValue
createdHole.physicsBody.collisionBitMask = 0
}
Also try setting one of your physics bodies to dynamic = true or collisions will likely not work.
A better way to test for collisions in the didBeginContact function is to use a switch statement.
func didBeginContact(contact: SKPhysicsContact) {
let categoryMask = contact.BodyA.categoryBitMask | contact.BodyB.categoryBitMask
switch (categoryMask) {
case collisionBodies.holeCategory.rawValue | collisionBodies.ballCategory.rawValue:
//code to run when contact is detected
default:
return
}
}

Resources