SpriteNode position versus touch location - swift

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.

Related

Swift-Setting a physics body velocity by angle

I was wondering if it was at all possible to make an SKNode move forward in a particular direction, but with only one factor. I'm aware of both applying an impulse and setting the velocity of a physics body, but they're both determined by two factors; dx and dy. I also know of rotating to an angle with SKActions. But is it possible to make an object simply "move forward" once it has been set on an angle? Or set its velocity with just one factor?
Thanks in advance.
Yes, is the answer to your question.
What I think you're looking for = THRUST... right?
What you want is for the "ship" to be able to rotate in any direction, and the thrust to be applied correctly, out of the "arse" of the ship, moving it forward, in ship terms.
This is absolutely possible, but does require a little "dummy" trick.
But I'm confusing you.
The local space of a SKPhysicsBody is relative to its parent. I presume.
And there's the speculative part. I'm guessing. I haven't tried this.
But... most physicsBodys are the child of an SKNode that's parented to the scene.
If you parent your ship to a dummy node for the purposes of rotation, and then rotate the dummy node, you should be able to make your spaceship fly in circles without ever changing the thrust vector, by simply rotating the dummy node.
Theoretically.
Something like this horrible pseudo code might help to start... maybe.
let dummy = SKNode()
let ship = SKSPriteNode()
dummy.addchild(ship)
ship.Physicsbody(add how you want here...)
ship.PhysicsBody.applyForce (vector that's only X, for example)
rotate dummy with action over time...
Sure I think what you're talking about is something like this:
Now let's say you have an SKSpriteNode that is called player who eventually has a physicsBody setup.
var player: SKSpriteNode!
You can just set the dx property of their velocity, so lets say you wanted to move them horizontally towards the location where the user tapped on the right hand side of the screen. If you then detect the position of the touch with touchesBegan(_:)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
// Replace with name of node to detect touch
let touchLocation = touch.location(in: <NAME_OF_NODE_PROPERTY>)
// Verify it's in front of the player and not behind
if (touchLocation.x - playerPosition.x) > 0 {
movePlayerVertically(toward: touchLocation)
}
}
func movePlayerVertically(toward location: CGPoint) {
let dx:CGFloat = location.x - player.position.x
player.physicsBody!.velocity.dx = dx
}
EDIT: -
Since you said you just want to be able to move your player horizontally without knowing the destination, you could do something like this, this is just moving the player forward on the x-axis by 50pts every second, and will repeat it forever. Obviously you would want to tweak it to your liking.
let move = SKAction.moveBy(x: 50, y: 0, duration: 1)
let repeatAction = SKAction.repeatForever(move)
player.run(repeatAction)

Increase Touchable Area of an SKSpriteNode

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.

Swift Spritekit. How am I able to lock a node in a particular point?

I am trying to figure out how to lock a SKSpriteNode in a particular y point. When my finger touches on the screen, the node displayed on the point where I touched. This is because inside TouchesBegan method, I wrote lines of code which are:
if isMovable == false {
isMovable = true
for touch in touches {
let location = touch.locationInNode(self)
ship.position = location
}
}
And then, I wrote these lines of code instead in order to get only the x point:
for touch in touches {
let location = touch.locationInNode(self)
ship.position = CGPointMake(location, self.frame.height/2 - 200)
}
However, the result I got was that location is not usable there because it is CGPoint, not CGFloat. I tried to cast it as CGFloat(location), but futile. Is there any way to lock a node in a y point?
The desirable function I want is when I touch anywhere on the screen, a y point is locked, meaning that a node is only movable horizontally, but not vertically movable.
Since my English is not good enough, I may lack missing some explanation that enables you guys to understand my problem more clearly. I appreciate if you understand me, and give me the solution! Thanks in advance.
Your location variable is of type CGPoint which is :
A structure that contains a point in a two-dimensional coordinate system.
Apple Documentation
So you could use that same variable location to get the xposition and lock the yto whatever point you choose:
for touch in touches {
let location = touch.locationInNode(self)
ship.position = CGPointMake(location.x, self.frame.height/2 - 200)
}

Swift SKSpriteNode will disappear but is still clickable

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()
}

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