Swift SKSpriteNode will disappear but is still clickable - swift

I have a problem with SpriteKit and Swift.
I'm adding SKSpriteNodes at different points of the code to the scene - some of them are clickable, some are not. I use the clickable Nodes as a Menu for the player. So, for example - if he clicks the InventoryButtonNode he jumps into the Inventory. In the Inventory he can touch the PlayButton and jumps back into the game. So, first i add the Nodes:
override func didMoveToView(view: SKView) {
PlayButton = SKSpriteNode(imageNamed: "PlayButton")
PlayButton.size = CGSize(width: 100, height: 100)
PlayButton.position = CGPoint ... // not important
PlayButton.zPosition = 200
PlayButton.name = "PlayButton"
self.addChild(PlayButton)
InventoryButton = SKSpriteNode(imageNamed: "InventoryButton")
InventoryButton.size = CGSize(width: 100, height: 100)
InventoryButton.position = CGPoint ... // different position than PlayButton
InventoryButton.zPosition = 200
InventoryButton.name = "PlayButton"
self.addChild(InventoryButton)
In the override func touchesBegan I use these "menu"-Nodes, here for example the InventoryButton-Node.
if InventoryButton.containsPoint(touch.locationInNode(self)) {
print("Show Inventory")
ShowInventory()
}
Now in the ShowInventory() function i want to remove those "menu"-Buttons from view so that i can add other nodes to show the Inventory of the Player.
func ShowInventory(){
PlayButton.removeFromParent()
InventoryButton.removeFromParent()
}
If I build and run this, the Nodes will be removed - or lets better say - they will get invisible.
Because, if i touch now at the position of InventoryButton I still get the print "Show Inventory" - so the function still reacts to my touch even if the node is not visible.
My problem is, that i have like 4 different functions like ShowInventory() .. I have ShowGame() and so on .. and i want in these functions to completely remove the Nodes and the "Touch"-Ability ..
I need a function which can remove the Nodes completely ..
I have even tried:
func ShowInventory(){
self.removeAllChildren()
}
I get a grey Background without any nodes .. but still - if I touch where the Inventory Buttons position was i get the function called and the print "Show Inventory" ... it's frustrating.

It is because your check for which button is pressed does not take into account whether the button is visible.
Even if a node has been removed from its parent, it still has a position and size. Those two properties are used by containsPoint: to determine if a point is in a node
The easiest way to fix it would be to just check if the button has a parent node before checking to see if the button contains the point.
if InventoryButton.parrent != nil && InventoryButton.containsPoint(touch.locationInNode(self)) {
print("Show Inventory")
ShowInventory()
}

Related

skLabelNodes only working sometimes

So I added two label nodes to my code, and want one to be hidden while the other is shown, and then vice versa once the game begins.
class GameScene: SKScene {
// Declarations
let startLabel:SKLabelNode = SKLabelNode()
let scoreLabel:SKLabelNode = SKLabelNode()
var score = 0
override func didMoveToView(view: SKView) {
// Properties
startLabel.fontSize = 20
startLabel.fontColor = UIColor.blackColor()
startLabel.position = CGPointMake(self.frame.width*0.5, self.frame.height*0.5)
startLabel.text = "Touch Paddle To Begin"
startLabel.hidden = false
self.addChild(startLabel)
scoreLabel.fontSize = 20
scoreLabel.fontColor = UIColor.blackColor()
scoreLabel.position = CGPointMake(self.frame.width*0.5, self.frame.height*0.75)
scoreLabel.text = "\(score)"
scoreLabel.hidden = true
self.addChild(scoreLabel)
Then, when the Paddle is touched, a moveBall() function is initiated and the game begins. This is where I swapped the labels.
func moveBall() {
// Setting Labels
self.startLabel.hidden = true
self.scoreLabel.hidden = false
// Starting game
self.ball.physicsBody?.dynamic = true
However, it only works sometimes. It will work perfectly, and then without changing anything I'll run it again and only one label will show up. Then again and the other label will show up, or the startLabel will show for only a frame then disappear.
Disclaimer: I do not really know how to code, just got an idea for a game and am trying to make it a reality. Apologies if solution is something simple. Also any advice for my code would be appreciated. Thanks!
From the information given it should work correctly. However you should call the moveBall() in the touchesBegan method. Not the update method.
Also if you want it to be called if the user touches the labelNode use the location of touches began. (But I would rather use a sprite instead of a label coz labels are a bit unresponsive coz sometimes finger doesn't touch the label.
So call the moveBall() in the correct method. Give some more info to give a more accurate answer.

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.

Drag and drop children of the same sprite

I'm making a game using Sprite Kit and I want to be able to drag and drop boxes as they travel down the screen.
Here's the gist of the code: I spawn the boxes on a timer and they move down the screen.
override func didMoveToView(view: SKView) {
let timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: Selector("spawnBox"), userInfo: nil, repeats: true)
}
func spawnBox() {
/* Set up the box */
addChild(box)
let boxMoveDown = SKAction.moveToY(-100, duration: 5.0)
let actionDone = SKAction.removeFromParent()
box.runAction(SKAction.sequence([boxMoveDown, actionDone]))
}
But the problem is I how can I move a specific child which I am touching without affecting all the other 'children'? I understand that at the moment, every time I spawn a box it's exactly the same so I can't be specific when I set a individual child's position.
Here's what's inside my touchesBegan and touchesMoved functions
if let touch = touches.first {
let location = touch.locationInNode(self)
let objects = nodesAtPoint(location) as [SKNode]
if objects.contains(nodeAtPoint(location)) && nodeAtPoint(location).name == "box" {
box.position = location
box.removeAllActions()
}
}
The - box.position = location is what
needs changing.
Hopefully you understand my idea. I've tried to keep included code to what's necessary. I'm quite new to Sprite Kit which you can probably tell.
If I were you, I would handle it this way:
Create a custom class for your box nodes that extends SKSpriteNode.
In this custom class, override the touch property.
Then set the position inside this function based on location.
Now all you need to worry about is your zPosition, whatever child has the highest zPosition will be the one that gets called on touch.
You do not need to worry about nodesAtPoint or what not anymore, the API will handle all that for you.

show menu when paused a game ins spritekit

I created a game like as tower defence. I can successfully "pause" all game. And then i can resume the game with these code:
let startLocation = CGPoint(x: location.x, y: location.y);
let myNodeName = self.nodeAtPoint(location)
if(myNodeName.Name == "Start"){
self.view!.paused = false
}
if(myNodeName.Name == "pause"){
self.view!.paused = true
self.showPausedMenu()
}
In showPausedMenu function. I design a menu with SKLabelNodes and SKSpriteNodes. However when i paused the game all of these "node creation" things also paused. So nothing happen. How i can show my menu while game is paused?
thank you.
Problem is that you're pausing the view itself. Pausing the view stops all of the logic.
If you need to do a pause menu, you need to either add a subview that will appear on top of your view to manage all of the interactions with the user, or you need to not pause the view, but put the view ELEMENTS on pause and create things on top of it.
The best method would be to make use of a SKNode group which consist of all the objects that you want to be paused.
In this way, you retain full control of the elements that you want to pause or unpause
I had solve my problem. While paused the game, all screen freezed however gamescene still detect all "touch" and if it has node name then we can do some operations. At this point, the proble is only showing my menu. So here is my solution.before I paused game, i added my menu on the screen. then pause the game.
In live version is
let startLocation = CGPoint(x: location.x, y: location.y);
let myNodeName = self.nodeAtPoint(location)
if(myNodeName.Name == "Start"){
self.view!.paused = false
self.removePausedMenu()
}
if(myNodeName.Name == "pause"){
self.showPausedMenu()
}
showPausedMenu is:
func showPausedMenu(){
.....
.....
/* Create menu and buttons and labels with NODES. and add to parent as child nodes. */
self.view!.paused = true
}

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