OK - I’m sure this is a duplicate and that Whirlwind or KnightOfDragons has posted a solution, but I can’t find it.
I’m writing a clone of Space Invader in Swift using Sprite-Kit. The problem I have is that when a invader bomb hits my ship, the code sometimes registers 2 or 3 collisions, immediately ending the game. Here’s the section of ‘didBeginContact’ that handles the bomb/ship collision:
case category.bomb.rawValue | category.ship.rawValue:
print("Bomb hit ship!")
let bomb = contact.bodyA.categoryBitMask == category.bomb.rawValue ? contact.bodyA.node : contact.bodyB.node
bomb?.physicsBody?.contactTestBitMask = 0 // Seems to prevent multiple contacts with same bomb
ship.physicsBody!.contactTestBitMask = 0 // We'll reset this after the death animation
bomb?.removeAllActions()
ship.removeAllActions()
bomb?.removeFromParent()
ships -= 1
if ships == 0 { endScene(won: false, withMessage: "A bomb got you!") }
and when I run the game I see:
Bomb hit ship!
2 ships left.
after 1 bomb hit (this is correct)
Bomb hit ship!
1 ships left.
Bomb hit ship!
0 ships left.
after a 2nd bomb hit (which is wrong).
I never have the contact NOT being registered, and sometimes (50%?) it works perfectly. Other times I’ve seen myself with -4 ships! I’m sure it’s something obvious/fundamental that I’m getting wrong though.
My comments about setting the contactTestBitMask to 0 for the bomb and the ship are obviously wrong. I know this shouldn’t be necessary, as I remove the bomb when the contact occurs, so it shouldn’t occur again.
How can I guarantee that the contact is only processed once?
================================
Update: I added 2 print statements to provide more debugging information:
print("bodyA is \(contact.bodyA.node!.name)")
print("bodyB is \(contact.bodyB.node!.name)")
This is after the let bomb = contact.bodyA.category... statement and now I get:
Bomb hit ship!
bodyA is Optional("bomb")
bodyB is Optional("playerShip")
1 ships left.
after 1 bomb hit, and :
Bomb hit ship!
fatal error: unexpectedly found nil while unwrapping an Optional value
after a 2nd bomb hit. So after the second collision, bodyA is nil, so I don't see why Sprite-Kit has actually registered a collision?
Any ideas?
OK - it would appear that a simple:
if bomb == nil {return}
is all that's required. This should be added as follows:
let bomb = contact.bodyA.categoryBitMask == category.bomb.rawValue ? contact.bodyA.node : contact.bodyB.node
if bomb == nil {return}
This works to prevent multiple collisions for a node that you removeFromParent in didBeginContact. If you don;t remove the node but are still registering multiple collisions, then use the node's userData property to set some sort of flag indicating that the node i s'anactive' Alternately, subclass SKSPriteNode and add a custom 'isActive' property, which is what I did to solve my problem of bullets passing up the side of an invader and taking out that invader and the one above it. This never happens on a 'direct hit'.
It doesn't answer the underlying question as to why SK is registering multiple collisions, but it does mean that I can remove all the extra code concerning setting contactTestBitMasks to 0 and then back to what they should be later etc.
Edit: So it appears that if you delete a node in didBeginContact, the node is removed but the physics body isn't. So you have to be careful as Sprite-Kit appears to build an array of physicsContacts that have occurred and then calls dBC multiple times, once for each contact. So if you are manipulating nodes and also removing them in dBC, be aware that you might run into an issue if you force unwrap a node's properties.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With