How to make SKSpriteNode with fixed position (after adding physics)? - swift

I have several programmatically made SKSpriteNode's. Some of them I want to move around, some I want to be static (have a fixed position). When adding physics to the nodes (need that to be able to do collision detection, right?) and set physicsBodyXXXX.dynamic = false they stay in the same position when moving other object over them. That's fine!
But, I'm still able to grab the node I want to be statically positioned, and move them around. How can I mask out the node I don't want to move in touches function? Or is there another solution?
Tried to find a property like static which made the node's position fixed, but can't find it...
Here's my code for auto generating nodes (in override func didMoveToView(view: SKView):
for Character in englishWord{
// Make letters:
let letterToMove = SKSpriteNode(imageNamed: "\(Character)")
//then setting size and position
var physicsBodyLetterToMove = SKPhysicsBody(rectangleOfSize: letterToMove.size)
physicsBodyLetterToMove.affectedByGravity = false
physicsBodyLetterToMove.allowsRotation = false
physicsBodyLetterToMove.dynamic = false
letterToMove.physicsBody = physicsBodyLetterToMove
self.addChild(letterToMove)
// Make empty boxes for the letters:
let letterRecBox = SKSpriteNode(imageNamed: "EmptyBox")
//then setting size and position
var physicsBodyLetterRecBox = SKPhysicsBody(rectangleOfSize: letterRecBox.size)
physicsBodyLetterToMove.affectedByGravity = false
physicsBodyLetterRecBox.dynamic = false
letterRecBox.physicsBody = physicsBodyLetterRecBox
self.addChild(letterRecBox)
}
So the touches func's:
var selected: [UITouch: SKNode] = [:]
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
selected = [:]
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
selected[touch as UITouch] = nodeAtPoint(location)
}
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
for (touch, node) in selected{
if !contains([self], node){
let action = SKAction.moveTo(location, duration: 0.1)
node.runAction(SKAction.repeatAction(action, count: 1))
}
}
}
}
Any idea?

Setting dynamic to false will make the node unaffected by physics. SKActions and touch events are not considered physics so they will still affect your nodes that are not dynamic.
You could do something like:
YourSpriteNode.name = #"staticNode"; //Right after you create it
Then alter your touch method:
for (touch, node) in selected{
if !contains([self], node){
if(![node.name isEqualToString:#"staticNode"])
{
let action = SKAction.moveTo(location, duration: 0.1)
node.runAction(SKAction.repeatAction(action, count: 1))
}
}
}
The newly introduced if statement will prevent any node named "staticNode" from getting moved due to your SKActions. Other nodes will move as expected.

Thanx, that worked.
Only, I had to write it like this:
letterRecBox.name = "staticNode"
...
if !(node.name == "staticNode"){
...then do something...
}

Related

How to make one line follow touch instead of drawing infinite in spritekit?

I am trying to make one single, straight line follow my finger after I touch a certain sprite object. I have it working so far except instead of one single line being drawn, infinite lines are drawn following the touch...
My Code:
import SpriteKit
import GameplayKit
class WireDrawTest: SKScene{
var drawingLayer: CAShapeLayer!
var redBox = SKSpriteNode()
var redBoxPoint = CGPoint(x: 445, y: 800)
var redBoxTouched:Int = -1
var currentTouch = touchesMoved
func drawLine(from: CGPoint, to: CGPoint) {
let line = SKShapeNode()
let path = CGMutablePath()
path.addLines(between: [from, to])
line.path = path
line.strokeColor = UIColor.yellow
line.lineWidth = 13
addChild(line)
}
override func didMove(to view: SKView) {
redBox = self.childNode(withName: "redBox") as! SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print(redBoxTouched)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let location = touch?.location(in: self){
let nodesArray = self.nodes(at: location)
if nodesArray.first?.name == "redBox" {
if redBoxTouched == -1 {
redBoxTouched = 1
}
}
if redBoxTouched == 1 {
drawLine(from: redBoxPoint, to: location)
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print(redBoxTouched)
if redBoxTouched == 1 {
redBoxTouched = -1
}
}
}
This is a screenshot of the current result:
screenshot
TIA!!
Every time you call drawLine, you're creating a new shape node and adding it to the scene. However, you're never removing any of these shape nodes, so the number of lines is increasing as you move your finger.
One fix is to have an optional shape node that remembers the previous line, if any. Then just remove and discard the previous shape node if there is one whenever the touch moves. And when the touch ends, remove the line.
Or you could create one shape node and add it to the scene when the touch begins, change its path as the touch moves, and remove it from the scene when the touch ends. If I recall correctly, the shape node makes a copy of the path internally, so just having a mutable path and changing the path won't update the shape node. But I believe you can make a new path and assign to the shape node's path property to update the node.
I figured out the solution to this with thanks to bg2b settings me on the right track...
First of all instead of 'let line = SKShapeNode' being contained inside of the drawWire function, it needs to be outside of it, in the main scene setup.
From there I just added 'line.removeFromParent' to touchesEnded. Now it works! I don't know why this works but it does! LOL!

hitTest(_:options:) don't recognize nodes behind ARKit planes

I place an object on a wall, then try to recognize tap on it, but hit test returns 0 objects. When I change Z position of the object and place it a little bit closer to cam, it's recognized well, but this isn't a solution, because planes are always changing and it can cover the object in any moment. How can I made hitTest work correctly and recognize my nodes behind planes? Or, maybe, I use the wrong method?
fileprivate func addNode(atPoint point: CGPoint) {
let hits = sceneView.hitTest(point, types: .existingPlaneUsingExtent)
if hits.count > 0, let firstHit = hits.first, let originNode = originNode {
let node = originNode.clone()
sceneView.scene.rootNode.addChildNode(node)
node.position = SCNVector3Make(firstHit.worldTransform.columns.3.x, firstHit.worldTransform.columns.3.y, firstHit.worldTransform.columns.3.z)
let resize = simd_float4x4(SCNMatrix4MakeScale(0.2, 0.2, 0.2))
let rotation = simd_float4x4(SCNMatrix4MakeRotation(.pi / 2, -1, 0, 0))
let transform = simd_mul(firstHit.worldTransform, resize)
let finalTransform = simd_mul(transform, rotation)
node.simdTransform = finalTransform
addedNodes.insert(node)
}
}
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
print("Unable to identify touches on any plane. Ignoring interaction...")
return
}
let touchPoint = touch.location(in: sceneView)
let hits = sceneView.hitTest(touchPoint, options: [SCNHitTestOption.boundingBoxOnly: true])
let filtered = hits.filter({ addedNodes.contains($0.node) })
print("\(hits.count) vs \(filtered.count), \(hits.first?.node.name ?? "no name")")
if let node = filtered.first?.node {
node.removeFromParentNode()
addedNodes.remove(node)
return
}
addPictureToPlane(atPoint: touchPoint)
}
addedNodes - set with added objects. When I added translating transform with changing Z coordinate at least on 0.05 (close to the camera) detecting working good. At least before plane changing and moving ahead the node.
I believe what you need to do is change your SCNHitTestSearchModeparameter which allows you to set:
Possible values for the searchMode option used with hit-testing
methods.
static let searchMode: SCNHitTestOption
Whereby:
The value for this key is an NSNumber object containing the raw
integer value of an SCNHitTestSearchMode constant.
From the Apple Docs there are three possible options you can use here:
case all
The hit test should return all possible results, sorted from nearest
to farthest.
case any
The hit test should return only the first object found, regardless of
distance.
case closest
The hit test should return only the closes object found.
Based on your question therefore, you would likely need to to utilise the all case.
As such your hitTest function would probably need to look something like this (remembering that self.augmentedRealityView refers to an ARSCNView):
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//1. Get The Current Touch Location
guard let currentTouchLocation = touches.first?.location(in: self.augmentedRealityView) else { return }
//2. Perform An SCNHitTest Setting The SearchMode To 1 (All) Which Returns A List Of Results Sorted From Nearest To Farthest
if #available(iOS 11.0, *) {
let hitTestResults = self.augmentedRealityView.hitTest(currentTouchLocation, options: [SCNHitTestOption.searchMode: 1])
//3. Loop Through The Results & Get The Nodes
for index in 0..<hitTestResults.count{
let node = hitTestResults[index]
print(node)
}
}
}

SpriteKit Scale on tap

im trying to create a "button touch" effect for one of my sprites, it works well but then I tap with 2 or more fingers at the same time, i get really weird results, here is my code:
let buttonPressAction = SKAction.scaleBy(0.8, duration: 0)
var button = SKNode()
override func didMoveToView(view: SKView) {
//assign sprite to node
button = self.childNodeWithName("button") as! SKSpriteNode!
}
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if button.containsPoint(location) {
button.runAction(buttonPressAction)
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
button.runAction(buttonPressAction.reversedAction())
}
Try changing the SK scale action from
...scaleBy
to
...scaleTo
to ensure it will always scale to the same size. With scaleBy it will scale it by 0.8, not to 0.8. That most likely causes the weird results on multiple touches because you are scaling by 0.8 for each finger/tap.
I never used reverseAction before so I am not sure if that might cause issues. If it does just reset the button by scaling it back to 1
...scaleTo(1, duration: 0)
As as side note you can just say
for touch in touches
instead of
for touch: AnyObject in touches

Why my node is invisible

I'm currently trying to make a node that can change it's texture, but it's not visible on the simulator. I know it's there, cause I set showNodesCount to true and it displays that 2 nodes are on scene. It's displaying a cannon node, but not rgyBox node, that should change it's texture if I tap on cannon node. Here is my code:
class GameScene: SKScene {
var gameOver = false
let cannon = SKSpriteNode(imageNamed: "cannon")
let rgyArray = ["redBox", "greenBox", "yellowBox"]
var rgyBlock = SKSpriteNode()
func changeRgyColor() {
var randomRGY = Int(arc4random_uniform(3))
rgyBlock.texture = SKTexture(imageNamed: rgyArray[randomRGY])
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
cannon.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addChild(cannon)
rgyBlock.texture = SKTexture(imageNamed: rgyArray[1])
rgyBlock.position = CGPointMake(self.cannon.position.x, self.cannon.position.y + 20)
self.addChild(rgyBlock)
rgyBlock.zPosition = 10
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.cannon {
changeRgyColor()
}
}
}
What am I doing wrong?
You need to recreate the rgyBlock by calling rgyBlock = SKSpriteNode(imageNamed: rgyArray[1]). Where it is defined at the top, it is defined as nothing. You then need to recreate it in didMoveToView. You can remove the texture set in didMoveToView for the rgyBlock.
You could do it a different way, and put imageNamed: "aTextureNameHere" into the define of rgyBlock. This will give rgyBlock a value. Then you can set the texture (as you already do) in didMoveToView. This way may be quicker and easier.

Swift nodeAtPoint difficulty

I am building a game in which the player drags a piece around the gameboard. I wish to know what are all of the nodes underneath that piece, and I am getting odd results. Here is the touchesMoves func:
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self.parent)
self.position = location
println("checker x: \(location.x)")
println("node at point: \(self.nodeAtPoint(location))")
println("nodes at point: \(self.nodesAtPoint(location))")
}
The sprite moves around the board just fine, but what is reported as the nodeAtPoint is always the sprite being moved around (which kind of makes sense but is not useful. Any oddly, the nodesAtPoint is always reported as an empty array! How is this possible? What should I be doing differently?
Update: This continues to be a struggle. I want to keep the touch methods in the node itself, and not the scene. The most recent version of the touchesMoved is the following:
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
// Turn off the touched bool:
touched = false
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self.parent)
let loc = touch.locationInView(scene.view)
let loc2 = touch.locationInNode(scene)
self.position = location
println("checker: \(loc2.x), \(loc2.y)")
println("node at point: \(self.nodeAtPoint(loc2).name)")
println("nodes at point: \(self.nodesAtPoint(loc2))")
}
The nodesAtPoint array continues to be empty with one really odd exception. When hovering near the center of the scene, I get this:
nodes at point: [ name:'(null)' accumulatedFrame:{{-30, -19.80000114440918}, {60, 39.600002288818359}}]
There is not shape node there that I am aware of! Why am I not detecting the nodes I pass over?
I discovered the answer to this. Essentially I was trying to detect nodesAtPoint on self, which in this case was the node being moved around the screen. Once I changed that to self.scene, the nodesAtPoint array populated as expected. So, to be clear:
println("nodes at point: \(self.nodesAtPoint(loc2))")
Needed to be changed to this:
println("nodes at point: \(self.scene.nodesAtPoint(loc2))")
If self is SKScene, try to change
let location = touch.locationInNode(self.parent)
to
let location = touch.locationInNode(self)
because SKScene's parent is nil