SpriteKit: detect complete node overlap

2019-02-05 13:54发布

问题:

I have two SKShapeNodes – one with an edge-based SKPhysicsBody, one volume-based – and I want to detect their intersection without collision. I've got this working fine, with the SKPhysicsContactDelegate contact methods getting called as one passes over another, but my issue is that didEndContact gets called when the edges no longer intersect, even when one body is completely contained within the other. What's the best way to determine true contact or overlap, not just edge intersection? I've tried usesPreciseCollisionDetection, to no avail.

回答1:

CGPoint locObj1 = [sprite1 locationInNode:self];
CGPoint locObj2 = [sprite2 locationInNode:self];

if([sprite1 containsPoint: locObj2]) return;
if([sprite2 containsPoint: locObj1]) return;

Add this to the beginning of didBeginContact and didEndContact. This checks to see if one of the nodes contains the other node. If it does, it does nothing which will alleviate your issue of didBeginContact and didEndContact being unessesarily called. I am not on my mac so you may need to play with the syntax a bit. Hope this sends you in the right direction.



回答2:

As meisenman suggested, it looks like the best way to do this is using the containsPoint method to determine true node overlap. The docs state that this "returns a Boolean value that indicates whether a point lies inside the node's bounding box", but in my experimentation it looks like this works for concave edge-based shapes as well.

So if I want to detect when two objects no longer overlap, it makes sense to do a containsPoint check within didEndContact – but it turns out didEndContact gets called when each edge is no longer intersected, even if another edge of the same shape still intersects the same object. For example, a ball exiting a rectangle through its corner will yield two didEndContact events. Therefore, it's necessary to keep an absolute count of contact events (the difference between begin and end), and only test for containment when this count is zero.

My solution, in Swift:

var _contactCount = 0

func didBeginContact(contact: SKPhysicsContact!) {
  if ((contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) == (CATEGORY_ONE | CATEGORY_TWO)) {
    if (_contactCount == 0) {
      // Contact is actually beginning
    }
    _contactCount += 1
  }
}

func didEndContact(contact: SKPhysicsContact!) {
  if ((contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) == (CATEGORY_ONE | CATEGORY_TWO)) {
    _contactCount -= 1
    let overlap = contact.bodyA.node.containsPoint(contact.bodyB.node.position) || contact.bodyB.node.containsPoint(contact.bodyA.node.position)
    if (!overlap && _contactCount == 0) {
      // Contact is actually ending
    }
  }
}


回答3:

These are my thoughts on #meisenman example for swift 3. Instead of collision detection through masks, let's say we wanted to to know if an node is inside a node. With #meisenman, the location of the node is being used for each.

This following image is what we actually what I want done.

The code #meisenman uses does not require SKPhysics. Here is the code in Swift 3.0

let Object_1: CGPoint! = Sprite_Object_1.position
//this obtains the location of the sprite node, through CGPoint.
if Object_Sprite_2.contains(Object_1){
    print("Then it is true, object 2 is within the location of object 1")
}

The only limitations that I have found are that if the center of these nodes are not within the body of the desired node, then this if statement will not be true. This has not been tested by setting the node's body defination (mask).

What I like, is having the ability to use this if statement throughout different functions or overide fuctions, this is not limited to only 'did begin' or 'touches moved.'