Increase Touchable Area of an SKSpriteNode - swift

I know a similar question has been asked before about this topic but here is my problem. I am using the following simple code to touch and SKNode.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch!
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
I then remove the touches node with the following:
node.removeFromParent()
Now, my problem is that the image of the node I am touching is small. Therefore it is very hard to accurately register a touch. I do not want to make the image bigger. This wouldn't be a problem but the nature of my game is such that I:
1) Cannot use another invisible SKSpriteNode ontop of my image SKSpriteNode to increase the registered touch area because each image has a unique name so I need that specific node to be touched and removed.
2) I cannot give the invisible SKSpriteNode the same name as the image SKSpriteNode because then only the invisible SKSpriteNode would be removed on touching.
3) I have tried to load a new SKSpriteNode into the original image SKSpriteNode to increase the touchable area, but once again, on touching, only the invisible SKSpriteNode gets removed, not the image SKSpriteNode which I want.
So, how can I increase the touchable area? Is there a way to maybe:
1) Remove all nodes associated with the node that is touched? This would then work if I put an invisible SKSpriteNode inside the original node to increase the touchable area. If they had the same name. But I have already tried "node.removeAllChildren()"......didn't work.
2) Detect a touch and the closest possible image near that touch is the image that the code in TouchesBegan gets use on? This would also work for my situation.
Any other suggestions? Cheers :)
-------------------------------UPDATE---------------------------
The following code below works! If I touch the invisible SKNode, then both the invisible and Original SKNodes disappear. However, for whatever reason, the original SKNode always appears infront of the invisible SKNode. I cannot fix this?
let original = SKSpriteNode(imageNamed: "original")
original.size = CGSize(width: 50, height: 50)
original.zPosition = 0
let invisible = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(70, 70))
invisible.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
invisible.alpha = 1.0
invisible.zPosition = 1
invisible.name = original.name
invisible.addChild(original)
self.addChild(invisible)

For zPosition order, put this inside GameViewController
//Put this line
skView.ignoresSiblingOrder = true
//before this
skView.presentScene(scene)

In you code you have two nodes:
original
invisible
Who is the parent, who is the child?
According to your question "If I touch the invisible SKNode, then both the invisible and original SKNodes disappear..however, for whatever reason, the original SKNode always appears infront of the invisible SKNode" seems that you want invisible inside original, in other words original should be the parent and invisible should be the child.
But your code says the exact opposite:
invisible.addChild(original)
self.addChild(invisible)
So , we make an example:
Suppose that your original node is green, this how appear your elements follow your code: invisible node (red) do an addChild to the original (green) , and then you add to self the invisible. Invisible is parent, original is child.
Finally, if you touch the red rectangle to remove it, both red and childs (green) will be removed, if you touch the green rectangle to remove it, only the green rectangle disappear.
Hope you can help you to understand what happen to your code.

Related

SpriteNode position versus touch location

So i'm using a spritenode with an image and I don't set the position of it. Funny enough I was having issues setting the position of it so I didn't and it by default was set to center of page (for obvious reason) which ended up being perfect. So now in my touchesbegan method i'm looking to change the said image IF the original image was pressed so i'm checking if the touched location is equal to the node (of the image)'s position. Then if that is true, I replace it with the new image which is called "nowaves".
for touch in touches
{
let location = touch.location(in: self)
if (location == audioPlaying.position)
{
audioPlaying = SKSpriteNode(imageNamed: "nowaves")
}
else
{
Do you guys think I need to readd it? Well right as I asked that I tested it to no result. This is what I tried:
audioPlaying.size = CGSize(width: frame.size.width / 13, height: frame.size.height / 20)
addChild(audioPlaying)
If anyone has any idea what the problem is I would love some feedback, thank you :D
By default, the position property returns the coordinates of the center of the node. In other words, unless you touched the exact center of the sprite, if (location == audioPlaying.position) will never be true. However, you should be aware that touching a node's exact center point is practically impossible.
So we use another method. We just need to check if the touched node is the node you want.
let touchedNode = self.nodes(at: touch.location(in: self)).first
if touchedNode == audioPlaying {
// I think setting the texture is more suitable here, instead of creating a new node.
audioPlaying.texture = SKTexture(imageNamed: "nowaves")
}
To add on to #sweeper ,
I had all my initial image content in the scenedidLoad method, rather than that I created a new method that I was already using for another functionality to start the game and had the initial image there instead and now everything works well.

Center SKLabelNode in SKSpriteNode

I have a SKSpriteNode with an attached image
menu_bg = SKSpriteNode(imageNamed: "menu_bg")
Additionally I want a SKLabelNode to be added as a child of menu_bg and this SKLabelNode should be centered horizontally and vertically wihtin the menu_bg node.
To do so, I added the menu_bg to the scene
self.addChild(menu_bg)
And I create a new SKLabelNode and add it to the menu_bg and try to place it centered
self.menu_bg.addChild(menuTitleLabel!)
self.menuTitleLabel!.position = CGPoint(x: menu_bg.frame.width/2, y: menu_bg.frame.height/2)
But the Label is not displayed centered.
Any ideas what I am missing?
I also tried to add
self.menuTitleLabel!.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.Center
but with no effect,...
any ideas?
If you are setting the position, you will need to account for the menuTitleLabel height and width as well.
self.menuTitleLabel!.position = CGPoint(x: menu_bg.frame.width/2 - (menuTitleLabel.frame.width/2), y: menu_bg.frame.height/2 -(menuTitleLabel.frame.height/2))
This should center it.
I followed the hint of #Glideman and when I set the label to 0,0 they were already centered, since the anchor of a SpriteNode is always in it´s center - as I learned now ;-)
Thank you for your help and approach to find a solution :)

Frame of SKShapeNode is offset

I am trying to add a physicsBody to an SKShapeNode that is a rectangle. I built my PhysicsBody using fromEdgesWithLoop using the Node's frame as the CGRect to build it.
Using the debug option to show PhysicsBody, I see that it is actually not centered on my Sprite.
I searched the web to find that I should use this:
scene.scaleMode = .ResizeFill
Sadly, I tried all scaleModes and there are no differences.
I also verified my GameScene's size and it fits the iPad2 screen (my target), 1024x720.
The SKShapeNode is a child of the Scene.
I tried to set the position of my node in the center of the Scene before adding a PhysicsBody with its frame, but this changed nothing. The node was already in the center of the scene.
myShape.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
myShape.physicsBody = SKPhysicsBody(edgeLoopFromRect: myShape.frame)
The position of an Edge Loop is relative to the node it's attached to. In general, for an edgeLoopFromRect, the position is given by
edge_loop_position = node.position + rect.frame.origin
In this case, your physics body is offset by the position of myShape. One way to fix this issue is to attach the edge loop to the scene instead of the shape node:
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: myShape.frame )
Alternatively, you can subtract the node's position from the frame's position with
let rect = CGRect(origin:CGPoint(x:myShape.frame.origin.x-myShape.position.x, y:myShape.frame.origin.y-myShape.position.y), size:myShape.frame.size)
myShape.physicsBody = SKPhysicsBody(edgeLoopFromRect:rect)

How to add new SKSprideNodes off the screen and sync their animation with existing objects?

I am trying to animate multiple SKSpriteNode's of the same type using single SKAction.
What I would like to do, is to display four SKSpriteNode's of the same type on the screen.
Then animate them all when the button is pressed and move them to the left.
As soon as the first SKSpriteNode would leave the screen I want to add another SKSpriteNode off the screen position on the right side and add him to this SKAction loop.
Image below shows in detail what I am after.
So far I was able to display four SKSpriteNode's of the same type on the screen and add them to the array.
var squareBox = SKSpriteNode()
func addSquares(size:CGSize){
for var i = 0; i < 4; i++ {
// Create a new sprite node from an image
squareBox = SKSpriteNode(imageNamed: "noBox")
// Square pysics settings
squareBox.physicsBody = SKPhysicsBody(rectangleOfSize: self.squareBox.frame.size)
squareBox.physicsBody!.dynamic = false
squareBox.physicsBody!.categoryBitMask = squareBoxCategory
squareBox.physicsBody!.contactTestBitMask = leftEdgeCategory | edgeCategory
// SquareBox positioning on X
var xPos = size.width/5 + squareBox.size.height/2
var xPosInt = Int(xPos) * (i + 1)
xPos = CGFloat(xPosInt)
var yPos = size.height/2 + (squareBox.size.height/2)
squareBox.position = CGPointMake(xPos - squareBox.size.height/2, yPos)
self.addChild(squareBox)
squareArray.append(squareBox)
}
}
I am not sure if that is correct way to animate four SKSpriteNode's of the same type, but when the button is pressed they all seem to move to the left as desired.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
self.nodeAtPoint(location)
if self.nodeAtPoint(location) == self.button {
let move = SKAction.moveByX(-size.width/5 - squareBox.size.height/2, y: 0, duration: 1)
print("button clicked!")
for box in squareArray {
box.runAction(move)
}
}
}
}
I created an invisible 1px line on the left edge of the screen to define when the first SKSpriteNode leaves the screen.
func addLeftEdge(size:CGSize){
let leftEdge = SKNode()
leftEdge.physicsBody = SKPhysicsBody(edgeFromPoint: CGPointMake(1, 1), toPoint: CGPointMake(1, size.height))
leftEdge.physicsBody!.categoryBitMask = leftEdgeCategory
self.addChild(leftEdge)
}
The problem with this approach is that SKSpriteNode's in the array when I animate them don't respond on didBeginContact when they touch the 1px line.
func didBeginContact(contact: SKPhysicsContact) {
println("contact!")
}
In the end I want to be able to apply the Force to all SKSpriteNode's on the screen for a few seconds to move them to the left and descend the force speed over the time until they all stop. My idea was to create four objects of the same type and add them to the screen + add them to the array var squareArray = [SKSpriteNode]().
Then when the first SKSpriteNode leaves the screen I would remove it from array and add it again at the end and position it off the screen on the right so it will be moved in seamless animation.
I have a feeling that my entire approach is wrong.
Please advise what is the best approach to animate multiple SKSpriteNode's to achieve my goal as described above. Am I on the right track ?
Please help, thank you.
In order to detect contact, one of the SKSpriteNodes must have physicsBody.Dynamic= YES;
Since you want neither to be dynamic, there is a quick fix I have found:
leftEdge.physicsBody.Dynamic=YES; //This allows it to enter onContact
leftEdge.physicsBody.collisionBitMask=0; //Disables edge from being moved by collision
leftEdge.physicsBody.AffectedByGravity=NO; //Prevents any in game gravity from moving the edge
This allows you have an edge that is dynamic and can trigger onContact yet the edge will still act as if it is not dynamic.
I think this solution isn't very clean and requires a lot more code than what is necessary to get this effect. In the update() function since there are only 4 or 5 boxes why don't you simply loop through the objects and check if any of their frame's minX's are less than 0 and if they are reset them to the starting point which I'm assuming is (view.frame.width + box.size.width) because that's completely offscreen. That way there's no array manipulation in that you don't have to remove and append objects to the array. Also you don't need to attach a physicsBody where none is required. And you don't need the leftEdge

Constraining movement of a SKSpriteNode to a fixed area within a scene

How can I constrain manual movement of an SKSpriteNode to a fixed rectangular area within a scene? This fixed rectangular area a also a SKSpriteNode which is fixed within the scene. In other words, I want to constrain manual movement of an object (SKSpriteNode) to be completely contained within another SKSpriteNode or at least in the same space that it occupies. I have tried several different approaches (e.g. using an SKShapeNode that has an edged-based physics body), but nothing seems to work. This seems like it should be a fairly simple task to accomplish. Thanks for any help or hints you can offer.
Put an if statement around your moving code - so don't carry out the movement if it will take the object past your boundary. e.g.
//check that a positive movement won't take your node past the right boundary
if(node.position.x + yourXMovementValue < boundaryXRight){
//move your node
}
//same for y
let rangeX = SKRange(lowerLimit: CGFloat, upperLimit: CGFloat)
let contraintX = SKConstraint.positionX(rangeX)
let rangeY = SKRange(lowerLimit: CGFloat, upperLimit: CGFloat)
let contraintY = SKConstraint.positionY(rangeY)
yourObject.constraints = [contraintX, contraintY]