SpriteKit sprite not following path correctly - sprite-kit

I'm currently converting my android game to iOS, and I'm trying to get a bat enemy to 'swoop' the player. I have the curve created in SpriteKit and it looks fine, however the bat does not appear to follow the line correctly, it disappears for a few seconds and then zips past the top right corner going upwards despite running an action.follow on a path that looks drawn properly. The path is attached to a shapeNode which follows the player.
Code:
func createPath(){
//All x values are moved back 1 / 4 of the screens width due to the camera being 1 / 4 of the screens width ahead of the player, and y values are halved due to the player being in the centre of y axis
path.move(to: CGPoint(x: gameScene.frame.width * 3 / 4, y: gameScene.frame.height / 2)) //top right corner
path.addCurve(to: CGPoint(x: -(gameScene.frame.width / 3.5), y: gameScene.frame.height / 6), control1: CGPoint(x: gameScene.frame.width / 2, y: -(gameScene.frame.height / 9)), control2: CGPoint(x: 0, y: -(gameScene.frame.width / 9)))
//to = off screen
//control1 3 / 4 across screen, slighly higher than player
//control2 exactly on player (players node height is screen width / 4.5)
followLine = SKAction.follow(path, asOffset: true, orientToPath: false, duration: 3)
viewLine.path = path
gameScene.addChild(viewLine)
}
func update(dt: Double){
if(GameScene.gV.distanceM == 4 && !run){//DistanceM is just a timer (seconds)
run = true
bat.run(followLine)
}
if run {
/*time += dt//timer to reset bats position
if time > 4 {
run = false
time = 0
}*/
} else {
bat.position = CGPoint(x: gameScene.pigeonCam.position.x + gameScene.frame.width / 2, y: gameScene.pigeonCam.position.x + gameScene.frame.height / 2)//keep bat at top right of screen
}
viewLine.position = gameScene.pigeon.pigeon.position//get node to follow the player
}
My best guess is that while moving the shapeNode appears to move the path it actually doesn't, if so, any other way I could get this path to "follow" the player

After much trial and error I realised the node asOffset is referring to is the one that runs the action, so I put the bat at the top right corner of the screen and adjusted the values, my final values looked as so:
let width = gameScene.frame.width
let height = gameScene.frame.height
path.move(to: CGPoint(x: 0, y: 0)) //top right corner
//0, 0
path.addCurve(to: CGPoint(x: -(width * 1.2), y: -(height / 3.5)), control1: CGPoint(x: -(width / 4), y: -(height / 1.5)), control2: CGPoint(x: -(width * 3 / 4), y: -(height / 2)))
This makes the bat follow the line perfectly, though the visual representation of the line is messed up

Related

SpriteKit: SKPhysicsJointLimit not respecting 'maxLength'

I'm trying to create a chain-like structure in SpriteKit and I'm having trouble understanding the behavior of SKPhysicsJointLimit's maxLength property. It seems not to do anything at all.
This question didn't solve my problem.
According to the documentation, maxLength is The maximum distance allowed between the two physics bodies connected by the limit joint.
However, my two nodes become oriented much farther apart than their maxLength value. It's true that I'm setting their initial positions to be farther apart than maxLength -- but I would expect the nodes to pull together during the simulation, as if tied together by a stretchy rope. Instead, the nodes remain far apart.
So, here's some code that sets a joint between two SKSpriteNodes.
let screen = UIScreen.main.bounds
let bodyA = SKSpriteNode(imageNamed: "box.png")
let bodyB = SKSpriteNode(imageNamed: "box.png")
bodyA.size = CGSize(width: 20, height: 20)
bodyB.size = CGSize(width: 20, height: 20)
bodyA.position = CGPoint(x: screen.width*0.4, y: screen.height*0.8)
bodyB.position = CGPoint(x: screen.width*0.6, y: screen.height*0.8)
bodyA.physicsBody = SKPhysicsBody(circleOfRadius: 20)
bodyB.physicsBody = SKPhysicsBody(circleOfRadius: 20)
addChild(bodyA)
addChild(bodyB)
let pinJoint = SKPhysicsJointLimit.joint(withBodyA: bodyA.physicsBody!, bodyB: bodyB.physicsBody!, anchorA: CGPoint(x: 0.5, y: 0.5), anchorB: CGPoint(x: 0.5, y: 0.5))
//This doesn't seem to do anything:
pinJoint.maxLength = 5.0
scene?.physicsWorld.add(pinJoint)
In the simulation, it's clear that there is a physics joint connecting the two nodes -- it's just that the nodes are much farther apart than they should be.
Why doesn't my maxLength value change the behavior of my two nodes, and how do I fix the problem? What am I not understanding?
Thanks for your input!
Be sure that the anchor points are in scene coordinates, as described in the documentation. The (0.5, 0.5) is likely intended to be "center of the sprite" or something like that, but that's not correct for a joint.

How to make UIBezierPath smooth in Swift

I am trying to create a line chart which represents a set of values (x and y) in a smooth bezier curve. This works fine, except when the x-values are close to each other and the y-values go from a continuous line to a lower or higher value. The values are not shown in the chart itself, but here is an image illustrating my problem:
As you can see, the line makes a backwards movement before continuing to the next point. I would like this to not happen and smoothen out. To generate the data points, I use this library from Minh Nguyen, which has helped me a lot. The only problem is this issue still. For easiness, here is the code I currently use:
private func controlPointsFrom(points: [CGPoint]) -> [CurvedSegment] {
var result: [CurvedSegment] = []
let delta: CGFloat = 0.3
for i in 1..<points.count {
let A = points[i-1]
let B = points[i]
let controlPoint1 = CGPoint(x: A.x + delta*(B.x-A.x), y: A.y + delta*(B.y - A.y))
let controlPoint2 = CGPoint(x: B.x - delta*(B.x-A.x), y: B.y - delta*(B.y - A.y))
let curvedSegment = CurvedSegment(controlPoint1: controlPoint1, controlPoint2: controlPoint2)
result.append(curvedSegment)
}
for i in 1..<points.count-1 {
let M = result[i-1].controlPoint2
let N = result[i].controlPoint1
let A = points[i]
let MM = CGPoint(x: 2 * A.x - M.x, y: 2 * A.y - M.y)
let NN = CGPoint(x: 2 * A.x - N.x, y: 2 * A.y - N.y)
result[i].controlPoint1 = CGPoint(x: (MM.x + N.x)/2, y: (MM.y + N.y)/2)
result[i-1].controlPoint2 = CGPoint(x: (NN.x + M.x)/2, y: (NN.y + M.y)/2)
}
return result
}
func createCurvedPath(_ dataPoints: [CGPoint]) -> UIBezierPath? {
let path = UIBezierPath()
path.move(to: dataPoints[0])
var curveSegments: [CurvedSegment] = []
let useDataPoints = dataPoints.filter { ($0.y < 1000) }
curveSegments = controlPointsFrom(points: useDataPoints)
for i in 1..<useDataPoints.count {
path.addCurve(to: useDataPoints[i], controlPoint1: curveSegments[i - 1].controlPoint1, controlPoint2: curveSegments[i - 1].controlPoint2)
}
return path
}
For documentation, I would refer to the tutorial/blogpost I linked earlier. I figure the issue should be somewhere in the calculation of controlPoint1 and controlPoint2 in the controlPointsFrom function. When I remove the delta or make it 0, it just become straight lines but then the issue doesn't occur either. So the math should be different I think, to keep track of the previous value and perhaps don't create a control point with a higher or lower y-value when the next point is lower or higher, respectively. But I am unable to figure out how to make it work. Any smart mind who can make this happen?
Would be forever grateful!
try this:
Smooth UIBezierPath
https://medium.com/#ramshandilya/draw-smooth-curves-through-a-set-of-points-in-ios-34f6d73c8f9

Why is this expression "too complex to be solved in reasonable time?"

func createPaddles() -> SKNode {
let container = SKNode()
let colors = [SKColor.green, SKColor.blue, SKColor.red, SKColor.green, SKColor.blue]
let width = self.frame.size.width
let height = self.frame.size.height
for i in 0...4 {
let node = SKShapeNode(rectOf: CGSize(width: self.frame.size.width / 3, height: self.frame.size.height * 0.15))
node.fillColor = colors[i]
node.strokeColor = colors[i]
node.zPosition = 100
node.position = CGPoint(x: -width * 1/6 + width / 3 * i, y: height * 0.075)
container.addChild(node)
}
return container
}
node.position is giving me an error saying it's too complex. Is this because calculating the frame width twice in one equation is too much for the compiler? Seems unlikely and this doesn't seem overly complex to me. Am I missing something here?
EDIT: Great group effort from everyone, thank you! This is what eventually worked (a combination of a few different answers):
node.position = CGPoint(x: -width / 6.0 + width / 3.0 * CGFloat(i), y: height * 0.075)
One of the reasons why this happens is that you have a type mismatch. However, instead of printing the type mismatch error, the compiler tries to find a way to match the types... and fails.
In your specific case, i is an Int while other variables in the expression are CGFloat:
-width * 1/6 + width / 3 * CGFloat(i)
Also note that 1/6 will be probably considered to be integer division, with the result being 0 although I am not sure about the priority right now. You probably want:
-width / 6 + width / 3 * CGFloat(i)
to be sure.
Generally it has to do with the compiler type system inferring the proper types to execute the statement. You can often shortcut the process by providing the types for it.
Note that simplifying your equation results in this:
node.position = CGPoint(x: width * (i - 0.5) / 3.0, y: height * 0.075)
You can also expressly convert types so that the compiler has less to do:
node.position = CGPoint(x: CGFloat(width) * (CGFloat(i) - CGFloat(0.5)) / CGFloat(3.0), y: CGFloat(height) * CGFloat(0.075))
This error occurs when the compiler needs to do a lot of type checking. See this answer for a good explanation: https://stackoverflow.com/a/29931329/6658553
In your specific case, try reducing the number of operators you are using, and split it into multiple calculations.
node.position = CGPoint(x: -width * 1/6 + width / 3 * i, y: height * 0.075)
could be refactored to something like this:
let newXPosition: CGFloat = -width * 1/6 + width / 3 * CGFloat(i)
node.position = CGPoint(x: newXPosition, y: height * 0.075)
It is not a very clear error, but it's because you are trying to multiply CGFloats with Ints
width is a CGFloat
let width = self.frame.size.width
"i" in your for loop is an int
for i in 0...4 {
let node = SKShapeNode(rectOf: CGSize(width: self.frame.size.width / 3, height: self.frame.size.height * 0.15))
node.fillColor = colors[i]
node.strokeColor = colors[i]
node.zPosition = 100
node.position = CGPoint(x: -width * 1/6 + width / 3 * i, y: height * 0.075)
container.addChild(node)
}
You can separate them to sub clocks as others have mentioned (probably preferred) but at the core of it you need to ensure that all of the values in your equation are of the same type
easiest way to make them of the same type would be to cast i to CGFloat
CGFloat(i)

Struggle creating correct physicsbody

In my current project I create some node that I would like to create physics bodies for.
This creates the nodes:
let seg = SKShapeNode(ellipseOf: CGSize(width: 10 / xScale, height: 10 / yScale))
let offset = (seg.frame.height / 1.5) * CGFloat(i + 1)
seg.position = CGPoint(x: anchorPoint.x, y: ((size.height / 2) / yScale) + (10 / yScale) - offset)
segments.append(seg)
addChild(seg)
And this is how I currently try to create the bodies:
seg.physicsBody = SKPhysicsBody(circleOfRadius: 10 / xScale / yScale / 2)
I dont know how I would go about creating the correct one. It should function like a normal physics body(edge based bodies don't collide with stuff).
Currently when the circles roll down it looks like this although they are initially circles:
Also they look very pixelated. Do you have any idea why?
Thanks for your help!

how to make contents in my app universal?

I have made an app that fits all my contents perfectly on Iphone 6, but when I run it on any other device, the positions are shifted. How do I fix this using UIKit on Xcode
make the size and position relevant to the screen size for example:
Image.position = CGPoint(x: (scene?.size.width)! * 0.1, y: (scene?.size.height)! * 0.2)
Image.size = CGSize(x: (scene?.size.width)! * 0.6, y: (scene?.size.height)! * 0.6)
i use scene as a reference majority of the time but frame size works as well by replacing the (scene.size.height)! portion with
(self.frame.size.width) or (self.frame.size.height)