6

I have 2 SKSpriteNode:

  • a simple square (A)
  • the same square with a rotation (-45°) (B)

I need to check, at any time, if the center of another SKSpriteNode (a ball) is inside one of these squares. The ball and the squares have the same parent (the main scene).

override func update(_ currentTime: TimeInterval) {

    let spriteArray = self.nodes(at: ball.position)

    let arr = spriteArray.filter {$0.name == "square"}

    for square in arr {
        print(square.letter)

        if(square.contains(self.puck.position)) {
             print("INSIDE")
        }

    }
}

With the simple square (A), my code works correctly. The data are right. I know, at any time, if the CGPoint center is inside or outside the square.

But with the square with the rotation (B), the data aren't as desired. The CGPoint is detected inside as soon as it's in the square which the diamond-shape is contained.

demo

The SKSpriteNode squares are created via the level editor.

How can I do to have the correct result for the diamond-shape?

EDIT 1

Using

view.showsPhysics = true

I can see the bounds of all the SKSpriteNode with physicsBody. The bounds of my diamond-square is the diamond-square and not the grey square area.

 square.frame.size -> return the grey area
 square.size -> return the diamond-square

In the Apple documentation, func nodes(at p: CGPoint) -> [SKNode], the method is about node and not frame, so why it doesn't work?

cmii
  • 3,556
  • 8
  • 38
  • 69
  • Possibly, your frame is not using alpha mask? You could use `.physicsBody` and collisions to get the pinpoint accuracy of this. I don't have any code on hand, but you can do this for sure with `SKPhysicsContactDelegate.` http://stackoverflow.com/questions/27576149/detect-collision-between-two-objects-in-swift – Fluidity Aug 23 '16 at 20:27
  • I tried to used .physicsBody and collision, but with this method, I know when the ball is in contact with the square. It isn't what I need. I'm interested in the contact with the center of the ball (CGPoint) and the square.It's not exactly the same thing. – cmii Aug 23 '16 at 20:39
  • well, @cmi , I think you can still use at least `.frame.intersects` but you have to have the frame set up as an alpha mask. Right now, it's seeing the whole square (gray area) as a contact patch, whereas the alpha mask would only show the blue diamond area. If I'm wrong on that, then The only other way I can think of is just to do math and subtract out the 4 triangles (the gray areas) surrounding the blue diamond. – Fluidity Aug 23 '16 at 20:48
  • @Fluidity please see my edit, indeed the frame of the diamond-square is the grey square. The node mask is the diamond-square. – cmii Aug 23 '16 at 21:11
  • 1
    node.contains is not an SKPhysics method, and all that it does is check if it is inside the nodes coodinate system (the frame). If you know the angle, just figure out where the touch point lies relative to the center of the node, than apply a reverse angle rotation on it, then use this new point to check if it contains the node – Knight0fDragon Aug 24 '16 at 02:26
  • @cmi I wish I was more knowledgeable so I could give an official answer, but you can do math on the results to get what you want, for example... You would just run a second check AFTER what you already have (to save on CPU). `if squarecontains(puck) TRUE -> if puck.center MATHFUNCTION(diamond.center) -> DOSTUFF` I just finished calc/trig a few months ago, but you should be able to do this with somewhat basic algebra. I'm sure there is a simpler / more elegant solution, but this could be a workaround until you (and hopefully we) figure it out. – Fluidity Aug 24 '16 at 04:02
  • the rotate / reverse rotate idea put forth by KnightOfDragon works too--it essentially 'shrinks' the frame of the diamond to the point that it would exclude the puck.. I don't know if it would produce any undesired visual effects though (the math function would not). His is something like `if squarecontains(puck) -> rotate(diamond) -> if squareSTILLcontains(puck) -> rotateback(diamond) -> DOSTUFF` – Fluidity Aug 24 '16 at 04:11
  • There is SKPhysicsWolrd method called bodyAtPoint, you could check it out to see if it helps. It returns the first body at given point. – Whirlwind Aug 24 '16 at 06:13
  • @Whirlwind bodyAtPoint returns only the ball – cmii Aug 24 '16 at 07:41
  • There is enumerateBodiesAtPoint: usingBlock so you can differentiate between multiple bodies. – Whirlwind Aug 24 '16 at 07:43

1 Answers1

2

There are many ways to do it, usually I like to work with paths so , if you have a perfect diamond as you describe I would like to offer a different way from the comments, you could create a path that match perfectly to your diamond with UIBezierPath because it have the method containsPoint:

let f = square.frame
var diamondPath = UIBezierPath.init()
diamondPath.moveToPoint(CGPointMake(f.size.width-f.origin.x,f.origin.y))
diamondPath.addLineToPoint(CGPointMake(f.origin.x,f.size.height-f.origin.y))
diamondPath.addLineToPoint(CGPointMake(f.size.width-f.origin.x,f.size.height))
diamondPath.addLineToPoint(CGPointMake(f.size.width,f.size.height-f.origin.y))
diamondPath.closePath()
if diamondPath.containsPoint(<#T##point: CGPoint##CGPoint#>) {
   // point is inside diamond
}
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • 1
    Ormano , This is pretty much exactly what I was looking for (if it does what I'm thinking it does)... a very elegant solution without having to "trick" it or come up with a fancy formula. I'm going to snipper this for myself as well :) – Fluidity Aug 24 '16 at 12:53
  • Using SKShapeNode seems the best solution for me and the CPU. – cmii Aug 24 '16 at 19:31