2

I have a an iOS app coded in Swift 3 where a ball is shot and bounces off of bricks on the screen. If I have the brick being one PhysicsBody (a rectangle), I can't easily determine which side/corner of the brick is being hit. What I decided to do instead of this, is have each side of the brick be its own separate node. The issue I am having now, is that a ball can't be in contact with two nodes (say the left and bottom) at once. I am decreasing the value of the brick after every contact with the ball, which in turn is decreasing the value by 2 for this one hit. How can I make it so that if a ball hits two nodes, only execute code for one contact?

Sometimes the below code gets executed twice, with the ball contacting with two brickNodes both times.

func didBegin(_ contact: SKPhysicsContact) {
    var firstBody:SKPhysicsBody
    var secondBody:SKPhysicsBody

    let countPoint = true

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB
    } else {
        firstBody = contact.bodyB
        secondBody = contact.bodyA
    }

    if (firstBody.categoryBitMask & ballCategory) != 0 {
        if (firstBody.node != nil && secondBody.node != nil){
            if (secondBody.categoryBitMask & brickCategory) != 0  {
                ballDidHitBrick(ballNode: firstBody.node as! SKShapeNode, brickNode: secondBody.node as! SKShapeNode, decreasePoint: countPoint)
            } else if (secondBody.categoryBitMask & roofCategory) != 0 || (secondBody.categoryBitMask & rightWallCategory) != 0 || (secondBody.categoryBitMask & leftWallCategory) != 0 || (secondBody.categoryBitMask & bottomCategory) != 0 {
                ballDidHitWall(ballNode: firstBody.node as! SKShapeNode, wallNode: secondBody.node as! SKShapeNode)
            } else {
                //Nothing as of yet
            }
        }
    }
}
D Gray
  • 31
  • 3
  • Possible duplicate of [Sprite-Kit registering multiple collisions for single contact](https://stackoverflow.com/questions/39505583/sprite-kit-registering-multiple-collisions-for-single-contact) – Steve Ives Sep 19 '17 at 15:44

3 Answers3

1

So going along with what Steve has said above, I implemented the code below and I am no longer having dual contacts per update:

if !bricksHit.contains("\(secondBody.node?.name ?? ""), \(firstBody.node?.name ?? "")") {
    //If ball hasnt hit the object more than once
    bricksHit.append("\(secondBody.node?.name ?? ""), \(firstBody.node?.name ?? "")")

    ballDidHitBrick(ballNode: firstBody.node as! SKShapeNode, brickNode: secondBody.node as! SKShapeNode, decreasePoint: countPoint, contact: contact)
}

I also added in the below to my code, which clears the bircksHit after every update:

override func didFinishUpdate() {
    bricksHit.removeAll()
}
D Gray
  • 31
  • 3
0

I would scrap the multiple nodes with multiple bodies, that would yield terrible performance if you have many blocks.

Instead, you should process your work in steps.

During your didBegin phase, you need to keep track of where the contact point is. This can be done with userData

func didBegin(_ contact: SKPhysicsContact) {
    var firstBody:SKPhysicsBody
    var secondBody:SKPhysicsBody

    let countPoint = true

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB
    } else {
        firstBody = contact.bodyB
        secondBody = contact.bodyA
    }

    if (firstBody.categoryBitMask & ballCategory) != 0,  (secondBody.categoryBitMask & brickCategory) != 0  {
        let userData = firstBody.node!.userData ??  [String,AnyObject]()
        let contactPoints = userData["contactPoints"] as? [CGPoint] ?? [CGPoint]()
        contactPoints.append(contact.contactPoint) //if need be add other info like the vector or relative to the node
       userData["contactPoints"] = contactPoints

    }
}

Then in a process later on, like didSimulatePhysics You can evaluate the nodes that were contacted, determine the priority of contact (like the bottom would supersede the sides, or if the velocity x > velocity y, sides, whatever you need to do) and react in this manner.

Note this is only sample code, it will not work verbatim. You will need to structure it into your code to get it to properly work.

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • I did change it from multiple nodes, to a single rectangular node and began to use the contact point to determine the balls relation to the brick. I did not do it exactly how you showed above, but I do agree that it will tremendously help width performance. Although, I still believe it is necessary to have the check to prevent the brick from being hit twice, as from my experience, duplicate contacts were still being recognized. – D Gray Sep 20 '17 at 15:42
-1

Yep - this happens. The consensu of opinion seems to be that if there are multiple simultaneous contact points between 2 physics bodies, SK will call didBegin for every contact point, resulting in multiple calls (in the sam game loop) for the same pair of physics bodies.

The way to handle it (you can't get sprite-kit to NOT call didBegin multiple times in some circumstances) is to make sure that your contact code accommodates this and that handling the contract multiple times does not cause a problem (such as adding to the score multiple times, removing multiple lives, trying to access a node or physicsBody that has been removed etc).

Some things you can do include:

  • If you remove a node that is contacted, check for it being nil before you remove it (for the duplicate contacts)
  • Add the node to a set and then remove all the nodes in the set in didFinishUpdate

  • Add an 'inactive' flag' to the node's userData

  • Make the node a subclass of SKSpriteNode and add an 'inactive' property (or similar)

  • Etc etc.

See this question and answer about SK calling didBegin multiple times for a single contact:

Sprite-Kit registering multiple collisions for single contact

Also SKPhysicsContact contains not only details of the 2 physics bodies that have collided, but also the contact point. From this, and the position properties of the 2 nodes involved, you could indeed calculate which side/corner of the brick is being hit.

Steve Ives
  • 7,894
  • 3
  • 24
  • 55
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/17381562) – aynber Sep 19 '17 at 16:49
  • @aynber OK - question expanded. What we really need is some sort of 'documentation' section... – Steve Ives Sep 19 '17 at 16:56
  • I didn't down vote it, but your linked answer doesn't exactly answer the question. Also it is not an opinion, didBegin contact does get fired when multiple contact points happen. Contacts happen on a per line basis, which is why when 2 rectangles hit each other on the sides, only 1 contact point exists, but if it goes at a diagonal, then 2 contact points exist (one for the top line, and one for the side line) – Knight0fDragon Sep 19 '17 at 18:09