Accurately selecting a node based on path from SKShapeNode - swift

I'm working on determining which node is tapped in an SKScene. I have a line that is created from a CGMutablePath and added to an SKShapeNode. However, when trying to accurately select this line, it is included in the array returned from nodesAtPoint whenever i tap anywhere within it's accumulated rectangle frame. See the image for more detail. I'm able to tap anywhere within those blue or red squares and still get the line from nodesAtPoint. I'm trying to figure out a way to only return the node from nodesAtPoint if the actual path was tapped (or maybe a threshold of +- 20 points to help). What method am i looking for?
Here is some relevant code that i've tried based on some articles i've found.
let tempActualTouchedNodes = self.nodes(at: positionInScene)
for n in tempActualTouchedNodes {
if let tempLine = n as? LineNode {
if tempLine.contains(touch.location(in: tempLine)){
print("yes")
}
}
}
However, "yes" is never printed.

Related

Moving mouse cursor with CGDisplayMoveCursor not working as expected when moving between monitors

so I need to be able to move the mouse to any given point (x and y). I'm trying to do it with CGDisplayMoveCursor while it moves the cursors it does not place it where I expect it. I have two monitors in my set up. I get the frame via NSScreen.screens[i].frame and these are their frames.
Built-in Retina Display
NSRect (0.0, 0.0, 1440.0, 900.0)
SwitchResX4 - LG HDR WQHD
NSRect (-1186.0, 900.0, 3840.0, 1600.0)
Then when I receive the new point I use NSRect.contains(NSPoint) to find which of the two monitors contain that point. That part works as expected.
But then I want to move the cursor to that point but it doesn't work as you would expect (the cursor moves but not to the points as you would expect based on the frames). Here is the code for that.
let point = NSPoint(x: x, y: y)
guard let monitor = Monitor.displayIdWithPoint(point) else {
SendlogCallbackEvent("D", "", "Found nil in monitor with point", #function, #file, #line)
}
if CGDisplayMoveCursorToPoint(monitor.directDisplayId, point) == CGError.failure {
SendlogCallbackEvent("D", "", "CGError.failer to move cursor", #function, #file, #line)
}
I have read that it probably has to do with how CG and NS handle coordinate system in a different way but I haven't been able to figure how to make it work. How do I "convert" my NSPoint to a CGPoint so it works with CGDisplayMove... function. Or what is the best way to do this.
This snippet of your code:
let point = NSPoint(x: x, y: y)
guard let monitor = Monitor.displayIdWithPoint(point) else {
SendlogCallbackEvent("D", "", "Found nil in monitor with point", #function, #file, #line)
}
gives me the impression that your point you're looking up is a point in the window system coordinate space (the coordinate space in which those two window frames are described).
But looking at the documentation of CGDisplayMoveCursorToPoint(_:_:), we see (emphasis mine):
point
The coordinates of a point in local display space. The origin is the upper-left corner of the specified display.
So I think you'll need to take your point, and transform it so that it's relative to the origin of your desired display:
let pointRelativeToScreen = point - monitor.frame.origin
if CGDisplayMoveCursorToPoint(monitor.directDisplayId, pointRelativeToScreen) == CGError.failure { ... }
Alternatively, there might be some other API which expects a cursor point in expressed in the window system's coordinates, which would automatically plop it onto the right screen for you. I don't have access to Xcode right now, so I can't check for this myself.

Positioning Nodes At Below the Bottom of the Screen (Spritekit)

Hello I'm trying to spawn bullets at the bottom of my screen to travel upwards but the current code that I have spawns the bullets at the top of the screen. I've tried making the height negative and nothing happened. Here's the code I'm working with, thanks.
let randomBulletPosition = GKRandomDistribution(lowestValue: -300, highestValue: 300)
let position = CGFloat(randomBulletPosition.nextInt())
bullet.position = CGPoint(x: position, y: self.frame.size.height + bullet.size.height)
Some nice conversions will help you.
Now, do not do this all the time, this should be a one and done type deal, like in a lazy property.
First, we want to get the bottom of our view
let viewBottom = CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY) //In a UIView, 0,0 is the top left corner, so we look to bottom middle
Second, we want to convert the position to the scene
let sceneBottom = scene!.view!.convert(viewBottom, to:scene!)
Finally we want to convert to whatever node you need it to be a part of. (This is optional if you want to place it on the scene)
let nodeBottom = scene!.convert(sceneBottom,to:node)
Code should look like this:
let viewBottom = CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY)
let sceneBottom = scene!.view!.convert(viewBottom!, to:scene!)
let nodeBottom = scene!.convert(sceneBottom,to:node)
Of course, this is a little ugly.
Thankfully we have convertPoint and convert(_from:) to clean things up a little bit
let sceneBottom = scene.convertPoint(from:viewBottom)
Which means we can clean up the code to look like this:
let sceneBottom = scene.convertPoint(from:CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY))
let nodeBottom = node.convert(sceneBottom,from:scene!)
Then we can make it 1 line as:
let nodeBottom = node.convert(scene.convertPoint(from:CGPoint(x:scene!.view!.midX,y:scene!.view!.frame.maxY),from:scene!)
As long as the node is available to the class, we can make it lazy:
lazy var nodeBottom = self.node.convert(self.scene!.convertPoint(CGPoint(x:self.scene!.view!.midX,y:self.scene!.view!.frame.maxY),from:self.scene!)
This means the first time you call nodeBottom, it will do these calculations for you and store it into memory. Everytime after that, the number is preserved.
Now that you know where the bottom of the screen is in the coordinate system you want to use, you can assign the x value to whatever your random is producing, and you can subtract the (node.height * (1 - node.anchorPoint.y)) to fully hide your node from the scene.
Now keep in mind, if your node moves between various parents, this lazy will not update.
Also note, I unwrapped all optionals with !, you may want to be using ? and checking if it exists first.

How to add a set of sprites in a certain pattern

I'm making a game that has a player that goes up and down if you hold the screen. This is not the important part though.
What I need is to add ENEMIES, that come toward you.
I need to know how to add the ENEMIES in a couple of different patterns.
Like this:(LOOK AT THE COINS PATTERN, HOW CAN I ACHIEVE THIS?)
You could define a 2-dimensional array to indicate where a coin should be e.g.
var coinRow = [[Int]]()
coinRow.append([0,1,1,1,1,1,1,0]) // '0' means 'No coin here'
coinRow.append([1,1,1,1,1,1,1,1]) // '1' means 'put coin here'
coinRow.append([0,1,1,1,1,1,1,0])
Then treat each coin 'area' as a 3x8 grid so given a starting location of the bottom-left hand corner as (0,0), do the following:
let coinStart = CGPoint(0,0)
coinPos = coinStart
for row in 0...2 { // Iterate over all rows
for column in 0...7 { // and all columns
if coinRow[row][column] == 1 { // Should there be a coin here?
putCoin(at: coinPos) // yes - draw one
}
coinPos.x += coin.width + coinHorizontalSeparation // next coin location
}
coinPos.y += coin.height + coinVerticalSeparation // Position to next row
coinPos.x = coinStart.x // Reset position to start of row
}
You wouldn't actually start at (0,0), so set coinStart as required. If the groups of coins appear in a regular pattern, then you can calculate coinStart and make the code that generates a block of coins a function that you call, passing coinStart as a parameter.

Can I copy a SCNNode and keep the scale

I would like to copy a SCNNode multiple times, have different materials for each node and different positions. However keeping the same scale. So, if I change the scale for the node I copy, all copied nodes should change.
In the code below, when I run changeScale(), the copied node scale does not change.
Is there a way I can change the scale of all copied Nodes or size of geometry together. Without enumerating or changing them individually
let mainNode = SCNNode()
let mainGeo = SCNPlane(width: CGFloat(4), height: CGFloat(4))
mainNode.geometry = mainGeo
for var i = 1; i <= 10; i += 1 {
let thisNode = mainNode.copy() as! SCNNode
thisNode.position = SCNVector3Make( Float(rx), Float(ry), Float(rz) )
thisNode.geometry = thisNode.geometry!.copy() as? SCNGeometry
thisNode.geometry?.firstMaterial = thisNode.geometry?.firstMaterial!.copy() as? SCNMaterial
if i == 0 {
thisNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blueColor()
} else {
thisNode.geometry?.firstMaterial?.diffuse.contents = UIColor.redColor()
}
scene.rootNode.addChildNode(thisNode)
}
func changeScale() {
mainNode.scale = SCNVector3Make(7, 7, 7)
}
I am not sure whether this is the right answer or not. As I am not an expert in Swift or ios.
It seems like when a node is copied or cloned, the node carries no properties apart from the assigned information on geometries etc.
I had initially thought some properties could be kept the same for all copied nodes, like scale or position.
What I wanted was to have different positions and materials for every node at creation, with a changeable scale of all the nodes or size of geometries together.
Now, as the nodes and geometry's are created multiple times, they are all different and cannot be sized or scaled together
So what I did:
Copied the main node multiple times, gave it different positions
Created one main geometry (outside), set it to the main node with a
particular size. So all the main nodes use this geometry
Added a subnode to the main node, with different material properties
Added a SCNTransformConstraint to the subnode, to transform according
to the size of the main material
So now whenever I edit the geometry size the subnode size changes all together.
I am not sure how this method is with speed/performance. But it seems better than enumerating through every node
You may try to use clone instead of copy:
let thisNode = mainNode.clone() as! SCNNode

Flipping a child sprite's xscale in Spritekit using Swift

Alright I'm offically stumped. I've been able to figure out my problems myself in the past but I'm lost on this one.
I'm trying to flip a child sprite's xscale. I want the flip to occur when the child's x position is negative. With the code below, the sprite does flip, every time the sprite reaches negative position, it loops back and forth flipping until reaching positive again.
I've tried numerous variations, but this is the code in it's simplest form. Any help or alternatives would be appreciative.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let pin = childNodeWithName(Pin1) as SKSpriteNode!
let guyPoint = guy.convertPoint(guy.position, fromNode: pin)
if guyPoint.x <= 0 {
guy.xScale = -1
}
else {
guy.xScale = 1
}
}
So if it is constantly flipping it's probably an issue of coordinate space. So check to make sure your convertPoint method is accurately returning the point you want. I would use println() to see what's going on there.
Probably what's happening is you want to flip the xScale when the child node is negative in the scene space or something (i.e. when the child is off the screen) but instead guyPoint might be the position of the child in its parent's coordinate space (so relative to the parent).
Also try with and without the else{} part to see if that changes anything.