SpriteKit : enumerateBodiesAtPoint not showing correct bodies - ios

I have a few non-completed-circles rotating constantly and a user going from circle to circle.
I removed all gravity, forces etc from my scene
Image A
Image B
Problem : I am trying to do a hit detection where I just check where the user is, and if there are SKNode's bodies at this point in the physics world of my scene. If it's a hit with the shape, the user can continue (image A), but fails if he is outside (image B)
Although the shapes are pretty complex, the scene.showPhysics seem to match my shapes precisely. (see image A and B)
let updatedOrigin = user.calculateAccumulatedFrame().origin
user.scene?.physicsWorld.enumerateBodiesAtPoint(updatedOrigin, usingBlock: { (body, stop) in
print("🍄 Shape contains \(body.node!.name)")
})
which prints
🍄 Shape contains Optional("User")
🍄 Shape contains Optional("circle")
🍄 Shape contains Optional("circle")
🍄 Shape contains Optional("circle")
🍄 Shape contains Optional("circle")
🍄 Shape contains Optional("Scene")
It prints the user and scene correctly, but also prints all the circle's shapes around, when there should only be one at this point, or none. The nodes are there, but the bodies physics should not hit.
Any ideas why it shows a hit for all those circles when it should only match 1 or none? Thanks!
Edit : additional info
- I had similar results when using user.physicsBody?.allContactedBodies()
- I am using a CGPath to create the PhysicsBody of my rotating node

I created a simple test project with a scene containing 3 arcs with physics bodies and 3 rectangle shape-nodes that identify the bounding box for each arc. I then added a touch handler that places a small circle at the tap point and a label that identifies the nodes returned by enumerateBodiesAtPoint with the touch location as the parameter. The figure below shows the results of tapping at various locations in the scene.
From the test results, it's not obvious how enumerateBodiesAtPoint determines if a physics body contains the specified point or not. It is clear, though, that the method is not consistent with its documentation. I suggest that you avoid using it in your app.
Alternatively, you can use SpriteKit's built-in contact detection:
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
}
func didBeginContact(contact: SKPhysicsContact) {
// Handle contacts between physics bodies here
}
}
You can also test if a point is within a CGPath using CGPathContainsPoint. Here, you will need to iterate over the paths you used to create the arc-shaped physics bodies. The below figure shows the result of my test project that uses CGPathContainsPoint instead of enumerateBodiesAtPoint. You may need to convert the test point, with convertPoint, to the appropriate coordinate space prior passing it to CGPathContainsPoint.

Related

SCNPhysicsShape(shapes:transforms:) creates MULTIPLE shapes?

My goal is to create a single physics body out of several of SceneKit's SCNBox geometries.
My understanding is that when I pass an SCNPhysicsShape(shapes:transforms:) to an SCNPhysicsBody(type:shape:), it should create a single physics body.
However, I end up with something that seems suspiciously like it's not a single physics body at all, but rather several separate physics bodies -- one for each shape I passed into SCNPhysicsShape(shapes:transforms:).
When I turn on scnView.debugOptions = .showPhysicsShapes, I can clearly see red lines defining the separate bodies in question. On its own, this isn't very convincing evidence (it's conceivable that those lines could be shown for whatever reason while still being a single physics body).
But there's another piece of data, here: The project in which I'm encountering this issue features a small ball that rolls around the scene -- and when that ball rolls over the red lines in question, the ball bounces up into the air a bit. So, it's quite obvious that, whatever is actually going on, there are edges where I would expect to see none.
This behavior is clearly visible in the following GIF. In it, each colored block is a separate SCNBox geometry with its own physics body. Each block has the exact same position.z. The ball bounces considerably as it crosses the point where one geometry meets another.
Here's some code illustrating the issue. parent is an SCNNode that holds the child nodes, and is the node to which I assign the physics body. Please assume that all properties are defined; I'm omitting things that aren't terribly relevant.
let childShape1 = SCNBox(width: 6, height: 2, length: 6, chamferRadius: 0.0)
//Other child shapes defined here...
//Set up the positional translation relative to the child node's parent:
let translateMatrixShape1 = SCNMatrix4MakeTranslation(childShape1.position.x, childShape1.position.y, childShape1.position.z)
//Other child translations defined here...
let parentShape = SCNPhysicsShape(shapes: [childShape1, childShape2, childShape3, childShape4], transforms: [translateMatrixShape1, translateMatrixShape2, translateMatrixShape3, translateMatrixShape4])
parent.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.static, shape: parentShape)
Now, parentShape is four rectangular boxes arranged around a central point, creating a sort of picture-frame-shaped object.
The ball is an SCNNode with an SCNSphere geometry and a dynamic physics body.
Question: Does anyone have any idea what might be going on, here? Have I somehow misunderstood how this whole thing works, or is this a limitation of SceneKit?
Usually you can create one single PhysicsBody from several geometry objects by making a flattenendClone from out of your (i.Ex.) parent node. This new node will have one single geometry then. For the PhysicsBody you can then use the geometry element of the new node. In Addition I recoommend to use a concavePolyhedron for the static body type. (I hope I understood you correctly)

Detecting nodes on any side of a touched node

I have a game built using SceneKit and swift.
I have been struggling to figure out how to solve my problem.
I am trying to figure out how to detect nodes touching in my specific scenario. The image below demonstrates the issue I am facing... If a user touched any of the yellow cubes it would highlight that whole chain of yellow cubes. Same for the three red cubes on the bottom and the two red cubes on the top.
The way the game works is a user is given a shape of cubes. The shape can change position by the user swiping it various ways. Cubes may appear or get removed from the scene, so the position of the cubes can change easily. Finally a gravity function will pull the cubes down to the ground when the user swipes the shape, so if they twisted the image below to the right then it would end up as a brand new shape with most of the cubes in a new position.
Here is what I have tried:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let location = touch.location(in: gameView)
let hitList = gameView.hitTest(location, options: nil)
if let hitObject = hitList.first {
let node = hitObject.node
//This is where I'm trying to detect the nodes and remove them
gameScene.rootNode.childNodes.filter({ $0.name == node.color }).forEach({ $0.removeFromParentNode() })
}
}
The problem with my code is that it removes all of the cubes that are the same color as the hit cube.
I would not use SceneKit APIs to solve this problem.
You have a game with cubes that can be arranged according to specific constraints. The application should have a model (abstract representation) of where each cube is, and the drawing of the cube is only a view of that model. Everything that involves your gameplay, including resolving which cubes are part of a chain of the same color, should be done on that abstract representation and then any update to the state of the cubes should be propagated to the SceneKit nod hierarchy.

How to return the SKSpriteNode within given constraints following an event?

Suppose the following:
You have a myriad of SKSpriteNodes in the view.
When the user taps the screen, you want the whatever sprite that is in / near a specific location to do an animation.
Question: How can figure out which SKSpriteNode is at the specific location without looping through all sprites?
For this, I have implemented a SKSpriteNode, box, which is transparent and has a texture which covers the span of the specific location, and is positioned accordingly.
The SKSpriteNode methods contains and intersects seem promising, but require that I pass a point or a sprite respectively.
Question: How can I get a SKSpriteNode to report what sprite, if any, it intersects with? Again, without looping through every sprite. If two sprites intersect with box, then return only that which is most prominently intersecting with box.
Diagram:
This is not my actual use case, but illustrates the point. There are a lot of sprites (more than visualized below) and there is an area of interest that:
if the user touches, and
a sprite is in that area
I want to know what sprite is there.
There is no way to do this without SOMETHING looping through the sprites. That's either:
The physics engine, as Stoneburner suggests
The scene, via update() setting flags on sprites when they're in the
region
Your code that handles the touch, searching for sprites in the region
GameplayKit offers some optimisations on doing this sort of thing: https://developer.apple.com/reference/gameplaykit/gkrtree
Attach a UITapGestureRecognizer to the view
On tap state UIGestureRecognizerStateRecognized get the location of the tap using CGPoint pointInView = [tapper locationInView:mySKScene.view]
Convert from the view's coordinate system to the scene's coordinate system using CGPoint pointInScene = [mySKScene convertPointFromView:pointInView]
Get the node at that point by asking the scene. SKNode *touchedNode = [self nodeAtPoint:pointInScene];
You can use SKPhysicsBodies to detect collisions (overlaps).
Assign physicsbodies to all sknodes, add one dynamically on the region you want to detect sknodes inside, handle the SKPhysicsContactDelegate, remove the body again

Detecting touch inside bounding box (alpha mask) of Sprite using SpriteKit

I am starting to learn Swift2 to develop on iOS using SpriteKit and I cannot seem to detect if I touched a visible part of my Sprite.
I can find which node was touched like this:
let touchedNode = self.nodeAtPoint(location)
But this detect the touch on the sprite. I would like to detect the touch on the "not transparent" part of the sprite only.
I tried creating a PhysicsBody with an alpha mask bounding box and testing if the bounding box of the node I selected contains the location of the touch like this:
sprite.physicsBody = SKPhysicsBody.init(texture: sprite.texture!, size: sprite.size)
sprite.physicsBody?.affectedByGravity = false
if (touchedNode.containsPoint(location)){
But it didn't help. If I click in a transparent part of my sprite, the event still triggers.
The documentation says "A new physics body is created that includes all of the texels in the texture that have a nonzero alpha value.", so shouldn't it work?
Thanks for your time.
PS: I also tried to be more generous on my alpha threshold, but this also did not work (in case my transparency wasn't perfect).
UPDATE:
To add a little more details, I am building a level editor. This means that I will create many nodes, depending on what the user chooses, and I need to be able to select/move/rotate/etc those nodes. I am actually using SKSpriteNodes as my PNG pictures are automatically added in xcassette that way. I decided to use the PhysicsBody's Alpha Mask bounding box, as this value is shown in the Scene Editor (Xcode) when you select a node, and when selected, the Alpha Mask highlights exactly the part of my Sprite that I want to be able to detect a touch inside.
If I am using the wrong ideas/techniques, please tell me. This is possible as I am only starting to use Swift and SpriteKit.
UPDATE 2
I queried the physicsWord (as recommended) to get the right physicsBody and got the name of the attached node like this:
let body = self.physicsWorld.bodyAtPoint(location)
print(body?.node?.name)
But this is still printing the name of the node even if I touch outside the bounding box, which makes no sense to me.
Thank you anyway for your help.
The SKPhysicsBody doesn't define the bounds of the SKNode. You can do the hit check by querying the SKPhysicsWorld of your scene by calling bodyAtPoint on it. That will return the SKPhysicsBody you are interested in.
I was having the same problem. You actually can't perform the touch on the alpha mask of a Sprite Node and it says it right in the Apple documentation. You have to use a Composite Box. I used a shape Node and changed the "line width" to 0 so essentially the composite box is invisible. Make sure that the Z axis is higher for the composite box and then run the touch event through the composite box shape node instead of the original node.

SCNNode static body with .dae causing issues

I have built a landscape model in blender, exported to .dae and added to my xcode project.
I have loaded the scene then attached the child (landscape grid mesh) to my landscapeNode, this loads perfectly.
However when I attach a static physics body to the landscspeNode my heroNode seems to crash into a invisible wall when attempting to fly above the land.
The functionality I am looking for is the obvious collision with the land I have modelled so the heroNode cannot fly through the land and is forced to move around it.
Note: I did not do any converting of the y axis in blender nor xcode just simply rotated the node 90degrees about the -x axis.
Edit: code i've attempted to add physics shape
landscapeNode.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeStatic shape:[SCNPhysicsShape shapeWithNode: [landscapeScene.rootNode childNodeWithName:#"Grid" recursively:NO] options:#{SCNPhysicsShapeTypeKey:SCNPhysicsShapeTypeConcavePolyhedron}]];
landscapeNode.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeStatic shape:[SCNPhysicsShape shapeWithNode: landscapeNode options:#{SCNPhysicsShapeTypeKey:SCNPhysicsShapeTypeConcavePolyhedron}]];
landscapeNode.physicsBody = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeStatic shape:[SCNPhysicsShape shapeWithGeometry: landscapeNode.geometry options:#{SCNPhysicsShapeTypeKey:SCNPhysicsShapeTypeConcavePolyhedron}]];
If you don't specify additional options when creating a physics body for a node, what you get is a convex hull encompassing that node's geometry.
Because your landscape is a static body, you can make it use a concave shape that more closely approximates the geometry. (Note that this works only for static bodies.) To do this, you'll need to:
Use the bodyWithShape:type: method to create the body, not staticBody.
For that method you need to pass an SCNPhysicsShape. Create one with shapeWithGeometry:options: or shapeWithNode:options:.
For the options parameter, pass a dictionary containing the key SCNPhysicsShapeTypeKey and corresponding value SCNPhysicsShapeTypeConcavePolyhedron.

Resources