Find SKNodes in a specified area - ios

I would like to be able to do something similar to the nodes(at:) function in SpriteKit. However, for my use case, checking if there are SKNodes at one specific CGPoint is simply too specific.
What I would like to have, is an area, (that I can specify), similar to the nodes(at:) function, where I get back the array of nodes intersecting the specific area.
Is there some function that does this for me? Also, I would prefer not to bring any SpriteKit Physics into this.

what you are looking for is
func intersects(_ node: SKNode) -> Bool
Returns a Boolean value that indicates whether this node intersects the specified node. The two nodes are considered to intersect if their frames intersect. The children of both nodes are ignored in this test.
example...
//sprite created in Scene editor
if let locationBox = self.childNode(withName: "locationBox") as? SKSpriteNode {
self.locationBox = locationBox
}
or
//sprite created programmatically
let locationBox = SKSpriteNode(color: .red, size: CGSize(width: 300, height: 300))
locationBox.zPosition = 1
locationBox.position = CGPoint(x: 100, y: 100)
addChild(locationBox)
checking for intersects
for child in children {
if locationBox.intersects(child) {
print("child.name \(child.name)")
}
}

Related

Making velocity of an SKSprite node relative to parent (not view/scene)

I'm using SpriteKit in Swift (3.0).
I store all the SKSprites nodes in my game as children of a "world" node
However when I apply a velocity to one of those nodes, they move relative to the screen, and not "world" (world is rotating, moving, etc.)
Heres how I implement it:
let movingObject = SKSpriteNode(color: UIColor.green, size: CGSize(width: 10, height: 10))
movingObject.physicsBody = SKPhysicsBody(rectangleOf: movingObject.size)
movingObject.physicsBody?.velocity = CGVector(dx: 0, dy: 200)
world.addChild(movingObject)
I need my node's velocity to move in respect to its parent, not the scene/view it is in. How do I do that?
Thanks!

How to create a ring with 3D effect using Sprite Kit?

I want to create a ring with a 3D effect using Sprite Kit. (SEE IMAGES)
I tried subclassing a SKNode and adding two nodes as children. (SEE CODE)
One node was a complete SKShapeNode ellipse, and the other was half ellipse using SKCropNode with a higher zPosition.
It looks good, but the SKCropNode increases the app CPU usage from 40% to 99%.
Any ideas on how to reduce the SKCropNode performance cost, or any alternative to create the same ring 3D effect?
class RingNode: SKNode {
let size: CGSize
init(size: CGSize, color: SKColor)
{
self.size = size
self.color = color
super.init()
ringPartsSetup()
}
private func ringPartsSetup() {
// LEFT PART (half ellipse)
let ellipseNodeLeft = getEllipseNode()
let leftMask = SKSpriteNode(texture: nil, color: SKColor.blackColor(), size: CGSize(
width: ellipseNodeLeft.frame.size.width/2,
height: ellipseNodeLeft.frame.size.height))
leftMask.anchorPoint = CGPoint(x: 0, y: 0.5)
leftMask.position = CGPoint(x: -ellipseNodeLeft.frame.size.width/2, y: 0)
let leftNode = SKCropNode()
leftNode.addChild(ellipseNodeLeft)
leftNode.maskNode = leftMask
leftNode.zPosition = 10 // Higher zPosition for 3D effect
leftNode.position = CGPoint(x: -leftNode.frame.size.width/4, y: 0)
addChild(leftNode)
// RIGHT PART (complete ellipse)
let rightNode = getEllipseNode()
rightNode.position = CGPoint(x: 0, y: 0)
rightNode.zPosition = 5
addChild(rightNode)
}
private func getEllipseNode() -> SKShapeNode {
let ellipseNode = SKShapeNode(ellipseOfSize: CGSize(
width: size.width,
height: size.height))
ellipseNode.strokeColor = SKColor.blackColor()
ellipseNode.lineWidth = 5
return ellipseNode
}
}
You've got the right idea with your two-layer approach and the half-slips on top. But instead of using a shape node inside a crop node, why not just use a shape node whose path is a half-ellipse? Create one using either CGPath or UIBezierPath API — use a circular arc with a transform to make it elliptical — then create your SKShapeNode from that path.
You may try converting your SKShapeNode to an SKSpriteNode. You can use SKView textureFromNode: (but we aware of issues with scale that require you to use it only after the node has been added to the view and at least one update cycle has run), or from scratch using an image (created programatically with a CGBitmapContext, of course).

Detect touch within Sprite texture and not the entire frame iOS Swift SpriteKit?

I am working with SpriteKit right now and I came across a problem that seems simple but I couldn't find anything on the internet. I have three buttons that are shaped like parallelograms stacked on top of each other and it looks like this:
Screenshot of buttons
let button = SKSpriteNode(imageNamed: "playbutton")
let leaderButton = SKSpriteNode(imageNamed: "leaderbutton")
let homeButton = SKSpriteNode(imageNamed: "homebutton")
button.position = CGPoint(x: size.width/2, y: size.height/2)
addChild(button)
leaderButton.position = CGPoint(x: size.width/2, y: size.height/2 - button.size.height/2)
addChild(leaderButton)
homeButton.position = CGPoint(x: size.width/2, y: size.height/2 - button.size.height)
addChild(homeButton)
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if button.containsPoint(location) {
button.runAction(SKAction.scaleTo(0.8, duration: 0.1))
}
else if leaderButton.containsPoint(location) {
leaderButton.runAction(SKAction.scaleTo(0.8, duration: 0.1))
}
else if homeButton.containsPoint(location) {
homeButton.runAction(SKAction.scaleTo(0.8, duration: 0.1))
}
}
}
This is how I am detecting touches. The problem is that they overlap because the sprite is actually a rectangle so when i try and tap on the top left of the second button, the top button detects it. I was wondering of there is a way to detect touch only in the texture like how you can set the physics body to a texture.
Thanks for any help you can give me!
Link works now.
So I tried this:
button.position = CGPoint(x: size.width/2, y: size.height/2)
button.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "playbutton"), size: button.size)
button.physicsBody?.dynamic = false
addChild(button)
leaderButton.position = CGPoint(x: size.width/2, y: size.height/2 - button.size.height/2)
leaderButton.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "leaderbutton"), size: leaderButton.size)
leaderButton.physicsBody?.dynamic = false
addChild(leaderButton)
homeButton.position = CGPoint(x: size.width/2, y: size.height/2 - button.size.height)
homeButton.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "homebutton"), size: homeButton.size)
homeButton.physicsBody?.dynamic = false
addChild(homeButton)
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if physicsWorld.bodyAtPoint(location)?.node == button {
button.runAction(SKAction.scaleTo(0.8, duration: 0.1))
print("play")
}
if physicsWorld.bodyAtPoint(location)?.node == leaderButton {
leaderButton.runAction(SKAction.scaleTo(0.8, duration: 0.1))
print("leader")
}
if physicsWorld.bodyAtPoint(location)?.node == homeButton {
homeButton.runAction(SKAction.scaleTo(0.8, duration: 0.1))
}
}
}
It still registers the full frame and not just the physics body. See the link to see the buttons and how their coordinates intersect.
If you attach a physics body to each button, you can detect which physics body your touch lands on.
You can generate a physics body from the button's texture (assuming the button is an SKSpriteNode) using SKPhysicsBody(texture:size:) or SKPhysicsBody(texture:alphaThreshold:size:), or you can create a CGPath describing the button's shape and use SKPhysicsBody(polygonFromPath:). Assign the body to the button's physicsBody property. Assuming you don't actually want the physics simulator to move your buttons, set each body's dynamic property to false.
Then you can use physicsWorld.bodyAtPoint(_:) to get one of the bodies that a touch lands on (where physicsWorld is a property of your SKScene). Use the body's node property to get back to the button node. If bodies overlap, bodyAtPoint returns an arbitrary body.
You can use physicsWorld.enumerateBodiesAtPoint(_:usingBlock:) if you need all of the bodies that your touch lands on.
A completely different approach, if you can create a CGPath describing the button's shape, is to use SKScene.convertPointFromView(_:) and then SKNode.convertPoint(_:fromNode:_) to convert the point into the button's coordinate system, and then use CGPathContainsPoint (a global function) to detect whether the point is in the path describing the button shape.
(I can't see your imgur link, but I think I have a pretty good idea of what you're describing.)
There are some discussions on how to read the alpha value of out an SKTexture, but those require quite a bit of overhead for just "did I touch a parallelogram"? Especially if you add alpha effects to your buttons.
You can do whatever logic you want on the location - you have a CGPoint that contains the location, and you know the size and shape of your buttons.
It should be pretty straightforward to write a function to test whether a point is inside a parallelogram-shaped button. Since parallelograms are basically rectangles with triangles on each end of an "inner" rect, if you know the size of that "inner" rect, you can pretty easily determine whether the touch is where you want by:
Checking that it's in the rect of the entire button. If not, you know it's not in the parallelogram.
Checking to see if it's inside the "inner" rectangle - the part of the parallelogram with the triangles "chopped off" the ends. If so, then you know it's in the parallelogram.
Checking to see if it's inside one of the triangles. You know how far up and how far across the touch is in the rect that contains the triangle, and you know the slope of the line that divides that rect to make the triangle by virtue of its width and height. That makes it trivial to check whether the point is in the triangle or not - just use the "y=mx+b" formula for a line, where m is the slope and b is whatever you need to add to get the line to pass through the corner of the rect (typically your "y intercept"). If the y coordinate is less than/greater than m*(the x coordinate) + b, you can determine whether the touch is inside or outside that triangle.
Once you have that function, you can use it as the test for checking each button.

trouble getting collision point

Hey I'm trying to get the point of collision and then create a joint at that point for two nodes. I'm not sure what my results mean or how to get what I want.
I don't have access to the contact points which I see in other posts. The CCContactSet from the collision only gives me number of points and normal.
I output the normal during collision begin and its (1.0, -0.0) for left wall, (-0.0, 1.0) for bottom wall, (-1.0, 0.0) for right wall, (-0.0, -1.0) for top wall. I don't understand them basically. I know they are only referring to the second hullPiece though because no matter how I rotate or position the booster during the collision, the results stay the same. They only change if I rotate the hullPiece.
So how do I get the contact points to create the joint? Am I supposed to use the normal for that, and if so how?
newSprite = CCSprite(imageNamed: "Booster.png")
newSprite.position = CGPoint(x: 200, y: 200)
newSprite.scale = 4.0
let spritePhysics = CCPhysicsBody(rect: newSprite.textureRect, cornerRadius: 0)
spritePhysics.collisionType = "booster"
spritePhysics.sensor = true
newSprite.physicsBody = spritePhysics
editorPhysics.addChild(newSprite)
newSprite2 = CCSprite(imageNamed: "HullPiece.png")
newSprite2.position = CGPoint(x: 200, y: 200)
newSprite2.scale = 4.0
let spritePhysics2 = CCPhysicsBody(rect: newSprite2.textureRect, cornerRadius: 0)
spritePhysics2.collisionType = "hull"
spritePhysics2.sensor = true
newSprite2.physicsBody = spritePhysics2
editorPhysics.addChild(newSprite2)
func ccPhysicsCollisionBegin(pair: CCPhysicsCollisionPair!, booster: CCNode!, hull: CCNode!) -> ObjCBool
{
NSLog("contact point\(pair.contacts.count) \(pair.contacts.normal)")
return true
}
You get access to the contact points through the contacts property of the CCPhysicsCollisionPair.
Here's what the contacts struct looks like:
typedef struct CCContactSet {
/// The number of contact points in the set.
/// The count will always be 1 or 2.
int count;
/// The normal of the contact points.
CGPoint normal;
/// The array of contact points.
struct {
/// The absolute position of the contact on the surface of each shape.
CGPoint pointA, pointB;
/// Penetration distance of the two shapes.
/// The value will always be negative.
CGFloat distance;
} points[2];
} CCContactSet;
Through the points array you can access the exact collision position for each involved shape.

How to simulate a world larger than the screen in SpriteKit?

I'm looking for the proper SpriteKit way to handle something of a scrollable world. Consider the following image:
In this contrived example, the world boundary is the dashed line and the blue dot can move anywhere within these boundaries. However, at any given point, a portion of this world can exist off-screen as indicated by the image. I would like to know how I can move the blue dot anywhere around the "world" while keeping the camera stationary on the blue dot.
This is Adventure, a sprite kit game by apple to demonstrate the point I made below. Read through the docs, they explain everything
Theres a good answer to this that I can't find at the moment. The basic idea is this:
Add a 'world' node to your scene. You can give it a width/height that is larger than the screen size.
When you 'move' the character around (or blue dot), you actually move your world node instead, but in the opposite direction, and that gives the impression that you're moving.
This way the screen is always centered on the blue dot, yet the world around you moves
below is an example from when I was experimenting a while ago:
override func didMoveToView(view: SKView) {
self.anchorPoint = CGPointMake(0.5, 0.5)
//self.size = CGSizeMake(600, 600)
// Add world
world = SKShapeNode(rectOfSize: CGSize(width: 500, height: 500))
world.fillColor = SKColor.whiteColor()
world.position = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
world.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(world)
}
override func update(currentTime: CFTimeInterval) {
world.position.x = -player.position.x
world.position.y = -player.position.y
}
override func didSimulatePhysics() {
self.centerOnNode(self.camera)
}
func centerOnNode(node: SKNode) {
if let parent = node.parent {
let nodePositionInScene: CGPoint = node.scene!.convertPoint(node.position, fromNode: parent)
parent.position = CGPoint(
x: parent.position.x - nodePositionInScene.x,
y: parent.position.y - nodePositionInScene.y)
}}
If you create a "camera" node which you add to your "world" node, a couple of simple functions (above) allow you to "follow" this camera node as it travels through the world, though actually you are moving the world around similar to Abdul Ahmad's answer.
This method allows you to use SpriteKit functionality on the camera. You can apply physics to it, run actions on it, put constraints on it, allowing effects like:
camera shaking (an action),
collision (a physics body, or matching the position of another node with a physics body),
a lagging follow (place a constraint on the camera that keeps it a certain distance from a character, for example)
The constraint especially adds a nice touch to a moving world as it allows the "main character" to move around freely somewhat while only moving the world when close to the edges of the screen.

Resources