When projectile hits two "monsters" the didBeginContact method crashes. I know why but i don't know how to avoid it - ios

So I have this code from the tutorial:
func didBeginContact(contact: SKPhysicsContact) {
// 1
var firstBody: SKPhysicsBody?
var secondBody: SKPhysicsBody?
var body: SKPhysicsBody
//contact.bodyB.node!.physicsBody!.allContactedBodies().count
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
println("1 = A, 2 = B")
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
println("2 = A, 1 = B")
}
// 2
if ((firstBody!.categoryBitMask & PhysicsCategory.Monster != 0) &&
(secondBody!.categoryBitMask & PhysicsCategory.Projectile != 0)) {
for var c = 1; c <= contact.bodyB.node!.physicsBody!.allContactedBodies().count; c++ {
projectileDidCollideWithMonster(firstBody!.node as! SKSpriteNode, monster: secondBody!.node as! SKSpriteNode)
}
secondBody!.node?.removeFromParent()
}
}
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode) {
println("Hit")
changeScore(1)
changeAmo(true)
projectile.removeFromParent()
monster.removeFromParent()
}
Then what is happening is that a projectile sometimes hit TWO monsters at once.
When this happens - didBeginContact method crashes saying that secondBody is nil.
After a thorough research I found out the reason:
when projectile collides with two other nodes at once - this method runs two times. After the first run - if gets bodyA as projectile, bodyB as a monster - passes them on to projectileDidCollideWithMonster and it removes them both. then it runs immediately again but at that moment projectile doesn't exist anymore and it crashes not able to assign it's node.
I have no idea how to overcome this:( any suggestions, please?
SOLUTION: Thanks to ideas below i did some changes.
added and array and a trigger at the top of the class:
var bodiesToBeRemoved = [SKSpriteNode]()
var shouldRemoveBodies = false
and did these modifications:
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode) {
/* Called when collisions is detected and collided nodes are passed to it */
//score and stuff
println("Hit")
changeScore(1)
changeAmo(true)
//collect the nodes to be removed
bodiesToBeRemoved.append(projectile)
bodiesToBeRemoved.append(monster)
//change the trigger to remove collected nodes later on
shouldRemoveBodies=true
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
//check the trigger
if shouldRemoveBodies == true {
println("\(bodiesToBeRemoved.count)")
//remove collected nodes
for var i = 0; i<bodiesToBeRemoved.count; i++ {
bodiesToBeRemoved[i].removeFromParent()
}
//reset array
bodiesToBeRemoved.removeAll(keepCapacity: false)
//reset the trigger
shouldRemoveBodies = false
} else {
//do nothing:)
}
}

Instead of removing your projectile immediately, just mark it for removal (e.g. by setting a boolean flag, or adding it to some collection if it's not already in the collection). Then later, before the next physics check (e.g. at the end of this frame), go through and remove all projectiles marked for removal.

Just check it's nil or not better than check every nodes's mark.
func removeNodeFromPhysicsBody(ps: SKPhysicsBody){
if (ps.node != nil){
ps.node?.removeFromParent()
}
}
func wallDidCollideWithMeteor(wall:SKPhysicsBody, meteor:SKPhysicsBody) {
removeNodeFromPhysicsBody(wall)
removeNodeFromPhysicsBody(meteor)
}

Related

swift spritekit how to run an action with a specified duration?

I'm trying to make my player change image once it detects collision with one of my game items and changes back after 0.3 seconds however, I dont know how to set the 0.3 duration for my image change? Currently the image changes in a flash
Here's my code:
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if firstBody.node?.name == "Player" && secondBody.node?.name == "Poop" {
let sound = SKAction.playSoundFileNamed("coinsound", waitForCompletion: false)
run(sound)
score += 1
// Changes's player image to Poop once it collides with "Poop"
let poopImage = SKTexture(imageNamed: "Poop")
let action = SKAction.setTexture(poopImage)
firstBody.node?.run(action)
scoreLabel?.text = String(score)
secondBody.node?.removeFromParent()
}
}
// Changes player's image back to what it was once it starts moving
private func managePlayer(){
if canMove{
player?.move(left: moveLeft)
player?.texture = SKTexture(imageNamed:"Player")
}
}
Thank you for your help in advance
Inside didBegin(_ contact) put your actions in a sequence like this
let sequence = SKAction.sequence([firstTextureChangeAction, SKAction.wait(forDuration:0.3), secondTextureChangeAction])
firstBody.node?.run(sequence)

Trying to make platforms that I can jump through from underneath but land on top of. having trouble fine tuning the logic

My goal is to set up all my platforms in the .sks file for easier design of my levels.
this is declared at the top of game scene.swift before didMove:
private var JumpThroughPlatformObject = SKSpriteNode()
and this is in DidMove:
if let JumpThroughPlatformObjectNode = self.childNode(withName: "//jumpThroughPlatform1") as? SKSpriteNode {
JumpThroughPlatformObject = JumpThroughPlatformObjectNode}
I reference the platform to get it's height from the .sks, since all my platforms are going to be the same height I only need to get it from one.
Below is what Im trying to use in my update method to turn off collisions until my player is totally above the platform. The main issue with only checking if my players velocity is greater than zero is: if the player is at the peak of a jump (his velocity slows to zero). if this happens and the player is inside a platform, he either instantly springs up to the top of the platform or gets launched downward.
I don't want my platforms to have to be 1 pixel high lines. I also need to have the player have a full collision box since he will be interacting with other types of environments. This leads me to believe that I somehow need to only register the top of the platform as a collision box and not the entire platform.
This if statement I wrote is supposed to take the y position of a platform and add half of its height to it, since the y position is based on the center of the sprite I figured this would put the collision for the platform on its top boundary.
I did the same for the player but in reverse. Putting the players collisions on only the bottom of his border. But its not working perfectly and I'm not sure why at this point.
if (JumpThroughPlatformObject.position.y + (JumpThroughPlatformObject.size.height / 2)) > (player.position.y - (player.size.height / 2))
The function below is giving me 3 main issues:
My players jump is always dy = 80. If I'm jumping up to a platform that position.y = 90, the players peak of the jump stops in the middle of the platform, but he teleports to the top of it instead of continuing to fall to the ground.
the left and right edges of the platforms still have full collision with the player if I'm falling
if my player is on a platform and there is another one directly above me, the player can't jump through it.
let zero:CGFloat = 0
if let body = player.physicsBody {
let dy = player.physicsBody?.velocity.dy
// when I jump dy is greater than zero else I'm falling
if (dy! >= zero) {
if (JumpThroughPlatformObject.position.y + (JumpThroughPlatformObject.size.height / 2)) > (player.position.y - (player.size.height / 2)) {
print(" platform y: \(JumpThroughPlatformObject.position.y)")
print ("player position: \(player.position.y)")
// Prevent collisions if the hero is jumping
body.collisionBitMask = CollisionTypes.saw.rawValue | CollisionTypes.ground.rawValue
}
}
else {
// Allow collisions if the hero is falling
body.collisionBitMask = CollisionTypes.platform.rawValue | CollisionTypes.ground.rawValue | CollisionTypes.saw.rawValue
}
}
Any advice would be greatly appreciated. I've been tearing my hair out for a couple days now.
EDIT in didBegin and didEnd:
func didBegin(_ contact: SKPhysicsContact) {
if let body = player.physicsBody {
let dy = player.physicsBody?.velocity.dy
let platform = JumpThroughPlatformObject
let zero:CGFloat = 0
if contact.bodyA.node == player {
// playerCollided(with: contact.bodyB.node!)
if (dy! > zero || body.node!.intersects(platform)) && ((body.node?.position.y)! - player.size.height / 2 < platform.position.y + platform.size.height / 2) {
body.collisionBitMask &= ~CollisionTypes.platform.rawValue
}
} else if contact.bodyB.node == player {
// playerCollided(with: contact.bodyA.node!)
isPlayerOnGround = true
if (dy! > zero || body.node!.intersects(platform)) && ((body.node?.position.y)! - player.size.height / 2 < platform.position.y + platform.size.height / 2) {
body.collisionBitMask &= ~CollisionTypes.platform.rawValue}
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
if let body = player.physicsBody {
// let dy = player.physicsBody?.velocity.dy
// let platform = JumpThroughPlatformObject
if contact.bodyA.node == player {
body.collisionBitMask |= CollisionTypes.platform.rawValue
}else if contact.bodyB.node == player {
body.collisionBitMask |= CollisionTypes.platform.rawValue
}
}
}
Adding what I did, the player can no longer jump through the platform.
Here is a link to the project that I made for macOS and iOS targets:
https://github.com/fluidityt/JumpUnderPlatform
Basically, this all has to do with
Detecting collision of a platform
Then determining if your player is under the platform
Allow your player to go through the platform (and subsequently land on it)
--
SK Physics makes this a little complicated:
On collision detection, your player's .position.y or .velocity.dy
may already have changed to a "false" state in reference to satisfying the #2 check from above (meaning #3 will never happen). Also, your player will bounce off the platform on first contact.
There is no "automatic" way to determine when your player has finished passing through the object (thus to allow player to land on the platform)
--
So to get everything working, a bit of creativity and ingenuity must be used!
1: Detecting collision of a platform:
So, to tackle 1 is the simplest: we just need to use the built in didBegin(contact:)
We are going to be relying heavily on the 3 big bitMasks, contact, category, and collision:
(fyi, I don't like using enums and bitmath for physics because I'm a rebel idiot):
struct BitMasks {
static let playerCategory = UInt32(2)
static let jupCategory = UInt32(4) // JUP = JumpUnderPlatform
}
override func didBegin(_ contact: SKPhysicsContact) {
// Crappy way to do "bit-math":
let contactedSum = contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask
switch contactedSum {
case BitMasks.jupCategory + BitMasks.playerCategory:
// ...
}
--
Now, you said that you wanted to use the SKSEditor, so I have accommodated you:
// Do all the fancy stuff you want here...
class JumpUnderPlatform: SKSpriteNode {
var pb: SKPhysicsBody { return self.physicsBody! } // If you see this on a crash, then WHY DOES JUP NOT HAVE A PB??
// NOTE: I could not properly configure any SKNode properties here..
// it's like they all get RESET if you put them in here...
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
}
--
Now for the player:
class Player: SKSpriteNode {
// If you see this on a crash, then WHY DOES PLAYER NOT HAVE A PB??
var pb: SKPhysicsBody { return self.physicsBody! }
static func makePlayer() -> Player {
let newPlayer = Player(color: .blue, size: CGSize(width: 50, height: 50))
let newPB = SKPhysicsBody(rectangleOf: newPlayer.size)
newPB.categoryBitMask = BitMasks.playerCategory
newPB.usesPreciseCollisionDetection = true
newPlayer.physicsBody = newPB
newPlayer.position.y -= 200 // For demo purposes.
return newPlayer
}
}
2. (and dealing with #4): Determining if under platform on contact:
There are many ways to do this, but I chose to use the player.pb.velocity.dy approach as mentioned by KOD to keep track of the player's position... if your dy is over 0, then you are jumping (under a platform) if not, then you are either standing still or falling (need to make contact with the platform and stick to it).
To accomplish this we have to get a bit more technical, because again, the physics system and the way SK works in its loop doesn't always mesh 100% with how we think it should work.
Basically, I had to make an initialDY property for Player that is constantly updated each frame in update
This initialDY will give us the correct data that we need for the first contact with the platform, allowing us to tell us to change the collision mask, and also to reset our player's CURRENT dy to the initial dy (so the player doesn't bounce off).
3. (and dealing with #5): Allow player to go through platform
To go through the platform, we need to play around with the collisionBitMasks. I chose to make the player's collision mask = the player's categoryMask, which is probably not the right way to do it, but it works for this demo.
You end up with magic like this in didBegin:
// Check if jumping; if not, then just land on platform normally.
guard player.initialDY > 0 else { return }
// Gives us the ability to pass through the platform!
player.pb.collisionBitMask = BitMasks.playerCategory
Now, dealing with #5 is going to require us to add another piece of state to our player class.. we need to temporarily store the contacted platform so we can check if the player has successfully finished passing through the platform (so we can reset the collision mask)
Then we just check in didFinishUpdate if the player's frame is above that platform, and if so, we reset the masks.
Here are all of the files , and again a link to the github:
https://github.com/fluidityt/JumpUnderPlatform
Player.swift:
class Player: SKSpriteNode {
// If you see this on a crash, then WHY DOES PLAYER NOT HAVE A PB??
var pb: SKPhysicsBody { return self.physicsBody! }
// This is set when we detect contact with a platform, but are underneath it (jumping up)
weak var platformToPassThrough: JumpUnderPlatform?
// For use inside of gamescene's didBeginContact (because current DY is altered by the time we need it)
var initialDY = CGFloat(0)
}
// MARK: - Funkys:
extension Player {
static func makePlayer() -> Player {
let newPlayer = Player(color: .blue, size: CGSize(width: 50, height: 50))
let newPB = SKPhysicsBody(rectangleOf: newPlayer.size)
newPB.categoryBitMask = BitMasks.playerCategory
newPB.usesPreciseCollisionDetection = true
newPlayer.physicsBody = newPB
newPlayer.position.y -= 200 // For demo purposes.
return newPlayer
}
func isAbovePlatform() -> Bool {
guard let platform = platformToPassThrough else { fatalError("wtf is the platform!") }
if frame.minY > platform.frame.maxY { return true }
else { return false }
}
func landOnPlatform() {
print("resetting stuff!")
platformToPassThrough = nil
pb.collisionBitMask = BitMasks.jupCategory
}
}
// MARK: - Player GameLoop:
extension Player {
func _update() {
// We have to keep track of this for proper detection of when to pass-through platform
initialDY = pb.velocity.dy
}
func _didFinishUpdate() {
// Check if we need to reset our collision mask (allow us to land on platform again)
if platformToPassThrough != nil {
if isAbovePlatform() { landOnPlatform() }
}
}
}
JumpUnderPlatform & BitMasks.swift (respectively:)
// Do all the fancy stuff you want here...
class JumpUnderPlatform: SKSpriteNode {
var pb: SKPhysicsBody { return self.physicsBody! } // If you see this on a crash, then WHY DOES JUP NOT HAVE A PB??
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
}
struct BitMasks {
static let playerCategory = UInt32(2)
static let jupCategory = UInt32(4)
}
GameScene.swift:
-
MAKE SURE YOU HAVE THE TWO NODES IN YOUR SKS EDITOR:
-
// MARK: - Props:
class GameScene: SKScene, SKPhysicsContactDelegate {
// Because I hate crashes related to spelling errors.
let names = (jup: "jup", resetLabel: "resetLabel")
let player = Player.makePlayer()
}
// MARK: - Physics handling:
extension GameScene {
private func findJup(contact: SKPhysicsContact) -> JumpUnderPlatform? {
guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else { fatalError("how did this happne!!??") }
if nodeA.name == names.jup { return (nodeA as! JumpUnderPlatform) }
else if nodeB.name == names.jup { return (nodeB as! JumpUnderPlatform) }
else { return nil }
}
// Player is 2, platform is 4:
private func doContactPlayer_X_Jup(platform: JumpUnderPlatform) {
// Check if jumping; if not, then just land on platform normally.
guard player.initialDY > 0 else { return }
// Gives us the ability to pass through the platform!
player.physicsBody!.collisionBitMask = BitMasks.playerCategory
// Will push the player through the platform (instead of bouncing off) on first hit
if player.platformToPassThrough == nil { player.pb.velocity.dy = player.initialDY }
player.platformToPassThrough = platform
}
func _didBegin(_ contact: SKPhysicsContact) {
// Crappy way to do bit-math:
let contactedSum = contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask
switch contactedSum {
case BitMasks.jupCategory + BitMasks.playerCategory:
guard let platform = findJup(contact: contact) else { fatalError("must be platform!") }
doContactPlayer_X_Jup(platform: platform)
// Put your other contact cases here...
// case BitMasks.xx + BitMasks.yy:
default: ()
}
}
}
// MARK: - Game loop:
extension GameScene {
// Scene setup:
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
addChild(player)
}
// Touch handling: (convert to touchesBegan for iOS):
override func mouseDown(with event: NSEvent) {
// Make player jump:
player.pb.applyImpulse(CGVector(dx: 0, dy: 50))
// Reset player on label click (from sks file):
if nodes(at: event.location(in: self)).first?.name == names.resetLabel {
player.position.y = frame.minY + player.size.width/2 + CGFloat(1)
}
}
override func update(_ currentTime: TimeInterval) {
player._update()
}
func didBegin(_ contact: SKPhysicsContact) {
self._didBegin(contact)
}
override func didFinishUpdate() {
player._didFinishUpdate()
}
}
I HOPE THIS HELPS SOME!
You just need a condition that let's you know if you are in a body. I also cleaned up your code to avoid accidently putting in the wrong categories
if let body = player.physicsBody, let dy = body.velocity.dy {
// when I am jumping or I am in a platform, then do not register
if (dy > zero || body.node.intersects(platform) && (body.node.position.y - body.node.size.height/2 != platform.position.y + platform.size.height / 2) {
body.collisionBitMask &= ~CollisionTypes.platform.rawValue
}
else {
// Allow collisions if the hero is falling
body.collisionBitMask |= CollisionTypes.platform.rawValue
Well, The answers above work well but those are very completed.
Simple answer is use Platform effector 2D component. which applies various “platform” behavior such as one-way collisions, removal of side-friction/bounce etc.
Check out this Unity's official tutorial for more clearance.

iOS Swift SpriteKit didEndContact not working properly

in my app(game) i need to detect contact with water and run action(withKey) while my Node is under water,im doing that with didBeginContact and Update method. But when i move my Node(with touches) from water im using didEndContact to detect whether Node is outof water and stop previous action(withKey) and it could be all fine but sometimes didEndContact not stoping action and do not detect (using print("EndContact")) that contact is ended.
Cheking contact between to nodes:
func didBeginContact(contact: SKPhysicsContact)
{
if let node1 = contact.bodyA.node
{
let entityNode = node1 as! Entity
entityNode.collideWith(contact.bodyB, contact: contact)
}
if let node2 = contact.bodyB.node
{
let entityNode = node2 as! Entity
entityNode.collideWith(contact.bodyA, contact: contact)
}
}
func didEndContact(contact: SKPhysicsContact)
{
if let node1 = contact.bodyA.node
{
let entityNode = node1 as! Entity
entityNode.collideEndedWith(contact.bodyB, contact: contact)
}
if let node2 = contact.bodyB.node
{
let entityNode = node2 as! Entity
entityNode.collideEndedWith(contact.bodyA, contact: contact)
}
}
Entity is subclass of SKSpriteNode, where i declare physics bitmask and so-on. I got Player class and Enemy class based on Entity. In my Player class i got functions that run some code when didBeginContact and didEndContact triggers
override func collideWith(body: SKPhysicsBody, contact: SKPhysicsContact)
{
if let enemy = body.node as? Enemy
{
contactingWater = true
self.runAction(SKAction.scaleBy(1.1, duration: 0.2), withKey: "action")
}
}
override func collideEndedWith(body: SKPhysicsBody, contact: SKPhysicsContact)
{
if let enemy = body.node as? Enemy
{
contactingWater = false
self.removeActionWithKey("action")
}
}
override func update(delta: CFTimeInterval){
}
Also i update my 'Player' on 'MainScene' in Update method. It seems to work fine but as i said sometimes didEndContact do not trigger
override func update(currentTime: NSTimeInterval)
{
if lastUpdateTime > 0
{
dt = currentTime - lastUpdateTime
} else {
dt = 0
}
lastUpdateTime = currentTime
player?.update(dt)
}
I noticed that this"sometimes" occurs
when i move Node into the water and when move back remove my finger ith stil thinks that im in contact with water. Ill be glad to any sugestion, cause im very new in programing) thx

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()
}

Sprite Kit Collision error - Swift

I am new to IOS dev and am currently having some issues with sprite kit collisions in the didBeginContact method.
How do I break out of, or stop didBeginContact from running if one of the colliding physics bodies are removed.
eg:
1 bullet collides with 2 overlapping enemies. Because the bullet hits enemy one and is destroyed, the collision check running on the second enemy throws an exception because the bullet no longer exists.
I have tried checking for nil and NSNULL values with no luck.
The error code that I receive is "Thread 1: EXC_BAD_INSTRUCTION(code=EXC_I386_INVOIP,subcode=0x0)" and occurs when trying to check the category bit mask (Because the torpedo no longer exists).
CODE:
var bodyA:SKPhysicsBody = contact.bodyA
var bodyB:SKPhysicsBody = contact.bodyB
if(contact.bodyA.categoryBitMask == alienCategory && contact.bodyB.categoryBitMask == alienCategory){
return
}
if((bodyA.categoryBitMask == alienCategory) && (bodyB.categoryBitMask == photonTorpedoCategory)){
torpedoDidCollideWithAlien(bodyA.node as SKSpriteNode)
}
var currTorpedo:SKSpriteNode = contact.bodyB.node as SKSpriteNode
var currAlien:SKSpriteNode = contact.bodyA.node as SKSpriteNode
currTorpedo.removeFromParent()
currAlien.removeFromParent()
}
func torpedoDidCollideWithAlien(alien:SKSpriteNode){
aliensDestroyed++
label.text = ("Current Score: " + String(aliensDestroyed))
}
Perhaps you could delay removing the torpedo from the scene until the next scene 'update'. You could flag the torpedo as disabled/inactive so it doesn't impact your 2nd overlapping enemy. Something like this:
class YourScene: SkScene {
// Array of disabled torpedos
var disabledTorpedos = SKSpriteNode()[]
func didBeginContact(contact: SKPhysicsContact) {
var bodyA:SKPhysicsBody = contact.bodyA
var bodyB:SKPhysicsBody = contact.bodyB
if(contact.bodyA.categoryBitMask == alienCategory && contact.bodyB.categoryBitMask == alienCategory){
return
}
if((bodyA.categoryBitMask == alienCategory) && (bodyB.categoryBitMask == photonTorpedoCategory)){
torpedoDidCollideWithAlien(bodyA.node as SKSpriteNode)
}
var currTorpedo:SKSpriteNode = contact.bodyB.node as SKSpriteNode
var currAlien:SKSpriteNode = contact.bodyA.node as SKSpriteNode
// Created a new photonTorpedoDisabledCategory so the torpedo cannot hit a 2nd alien
currTorpedo.physicsBody.categoryBitMask = photonTorpedoDisabledCategory
disabledTorpedoes.append(currTorpedo)
// Hide torpedo from scene
currTorpedo.hidden = true
currAlien.removeFromParent()
}
override func update(currentTime: NSTimeInterval) {
// loop through disabledTorpedos array and removeFromParent
}
}

Resources