didBeginContact is being called multiple times for the same SKPhysicsBody
Asked Answered
P

6

8
 func didBeginContact(contact: SKPhysicsContact) {
    if ( contact.bodyA.categoryBitMask & BodyType.shield.rawValue ) == BodyType.shield.rawValue  {
        contact.bodyB.node?.removeFromParent()
        counter++
        println(counter)


    } else if ( contact.bodyB.categoryBitMask & BodyType.shield.rawValue ) == BodyType.shield.rawValue {
        contact.bodyA.node?.removeFromParent()
        counter++
        println(counter)
    }
}

One physics body is from a texture shield.physicsBody = SKPhysicsBody(texture: shieldTexture, size: shieldTexture.size())

the other is from a circle sand.physicsBody = SKPhysicsBody(circleOfRadius: sand.size.width/2)

When the tow objects contact each other sometimes sand.physicsBody = SKPhysicsBody(circleOfRadius: sand.size.width/2) gets called multiple times. How do i get it to only get called once for each object even though i remove it from the parent as soon as it contacts.

Probable answered 15/1, 2015 at 4:4 Comment(2)
i believe this works as intended since body from texture may generate multiple shapes internally, each may cause a contact event. Removing the node doesn't remove the body until after the physics simulation step is over. You have to manually "mark" the node or body as having been processed in this contact event so that you can skip any subsequent contact events of the same bodies.Axil
I wish there was an option for SKPhysicsBody from texture to set it through parameter so it behaves exactly as e.g. SKPhysicsBody from circleOfRadius so it counts ONLY 1 hit/contact as in some cases that would be much desired than creating any extra logic, including the answer below. Also, it could save resources not to detect more contacts if they are not needed..Anhydride
P
2

I have figured out how to get func didBeginContact(contact: SKPhysicsContact) to only be called once. This allows physics bodies with a texture SKPhysicsBody(texture: size:) to count collisions once even though in reality (because of the nature of the texture's physics body) this function will be called multiple times.

Step 1:

Create a name property for the SKSpriteNode (we will use ball for this example) and set it equal to a unique name. We can do this by creating a int

var number = 0 

ball.name = "ball \(number)"

This allows for a unique name evertime that object is created.

Step 2:

Create a array to hold these , append the ball to the array, increment the number

    var array: [String] = []
    var number = 0 

ball.name = "ball \(number)" 
array.append(ball.name!)
number ++

Step 3: Now in the func didBeginContact(contact: SKPhysicsContact) find out if the name is in the array. If it is increment the score, remove the node, and remove the name from the array. If the name is not in the array don't do anything.

Removing the name from the array allows us now to only count the function call once.

func didBeginContact(contact: SKPhysicsContact) {
    if ( contact.bodyA.categoryBitMask & BodyType.shield.rawValue ) == BodyType.shield.rawValue  {
        var name = contact.bodyB.node?.name!
        let index = find(array, name!)

        if contains(array, name!) {
            score++
            contact.bodyB.node?.removeFromParent()
            array.removeAtIndex(index!)
        }
    } 
}
Probable answered 16/1, 2015 at 3:55 Comment(1)
Can't you just add the SKSpriteNode to the array? Its passed by reference so we can compare them, I don't see why we need to assign unique names.Pundit
I
2

You can make it work without the array in this case. Instead of this:

contact.bodyA.node?.removeFromParent()
counter++

Use something like this:

if let node = contact.bodyA.node as? SKSpriteNode {
    if node.parent != nil {
        node.removeFromParent()
        counter++
    }
}

On the first contact you remove the node from the parent, on the subsequent calls the code in the if statement will be skipped.

Ilia answered 1/4, 2016 at 22:59 Comment(0)
M
2

there's a very simple solution if your using contactbitmask to determine which collisions to capture.

just update the categoryBitMask of the object you don't want to be repeatedly detected to a new value that's not being used so the system does' t consider subsequent function calls relevant anymore.

Marie answered 11/8, 2016 at 6:3 Comment(0)
C
2

The problem seems to occur more often when texture-based physics bodies are used. The way I got around it was to inhibit 'didBegin(_ contact: SKPhysicsContact)' from continuing if the same 'contact.bodyA.node' was indicated in the previous collision trigger. i.e.:

if lastNodeA != contact.bodyA.node {
    lastNodeA = contact.bodyA.node
    ...
Crewelwork answered 19/10, 2018 at 2:48 Comment(0)
E
0

LearnCocos2D is right, SKPhysicsbody didBeginContact will call continuously as long as SKphysicsbody of the two objects are in contact because the shape we allowed in SKPhysicsBody(texture:xxx, size:xx) can come in multiple forms and shapes.

To those who need it to detect once only, we just need to use a boolean as flag to check if the detection is done and over with.

Here is how I do it:

  1. Declare a var boolean:

    var contactDone = Bool()
    
  2. Initialize it at start of program (E.g. below didMoveToView)

    contactDone = false
    
  3. Do the checking in didBeginContact:

    func didBeginContact(contact:SKPhysicsBody){
    
    if((contact.bodyA.categoryBitMask) == scoreCategory ||       (contact.bodyB.categoryBitMask) == scoreCategory){
    
          if (contactDone == false){
    
             // Increment score           
             score++
    
             // Set flag to disable multiple calls by checking in didEndContact
             contactDone = true
    
          }
    }
    }
    
  4. Clear the flag to let it check again in didEndContact:

    func didEndContact(contact: SKPhysicsContact) {
    
    if((contact.bodyA.categoryBitMask) == scoreCategory || (contact.bodyB.categoryBitMask) == scoreCategory){
    
                if(contactDone == true){
    
                    contactDone = false
    
                }
    
    }
    
    }
    

It worked just like it did when I used SKPhysicBody(circleOfRadius: object.size.height/2).

Ecclesiology answered 20/2, 2016 at 8:13 Comment(1)
Doesn't work when I use 'SKPhysicsBody(texture: ghostTexture, size: Obj.size)'Hunchback
T
0

My solution is, to take the time via Date().timeIntervalSince1970 in contact didBegin

let passedTime = Date().timeIntervalSince1970 - lastContactTime

lastContactTime = Date().timeIntervalSince1970

if passedTime < 0.01 {
  // same object
  return
}
Teak answered 15/9, 2020 at 16:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.