What algorithm is available to correlate a SKSpriteNode's position with the UIBezierPath's currentPoint?
I have this code (GameViewController
) which implements the following of a SKSpriteNode
along a UIBezierPath
:
func createTrainPath() {
trackRect = CGRect(x: tracksPosX - tracksWidth/2,
y: tracksPosY,
width: tracksWidth,
height: tracksHeight)
trainPath = UIBezierPath(ovalIn: trackRect)
} // createTrainPath
func startFollowTrainPath() {
var trainAction = SKAction.follow(
trainPath.cgPath,
asOffset: false,
orientToPath: true,
speed: theSpeed)
trainAction = SKAction.repeatForever(trainAction)
myTrain.run(trainAction, withKey: runTrainKey)
} // startFollowTrainPath
func stopFollowTrainPath() {
guard myTrain == nil else {
myTrain.removeAction(forKey: runTrainKey)
savedTrainPosition = getPositionFor(myTrain, orPath: trainPath)
return
}
} // stopFollowTrainPath
The user of this segment can bring up another SKScene
.. at which action I call stopFollowTrainPath()
and save the position
of the SKSpriteNode
to the global savedTrainPosition
via:
func getPositionFor(_ node: SKSpriteNode, orPath: UIBezierPath) -> CGPoint {
if (useNode) {
return node.position
}
else {
return orPath.currentPoint
}
} // getPositionFor
(1) When the user returns from looking at the other SKScene
, I want the moving (following) SKSpriteNode
to continue moving around the UIBezierPath
starting where I stopped the Node
before presenting the other SKScene
.
(2) If the SKSpriteNode
is not moving before the other SKScene
is brought up, I need the position
of the SKSpriteNode
to remain where it was before the new SKScene
was presented.
What is the best method for doing these?
Because you will be re-sizing the oval on device rotation, and, when you show a different scene and then "come back" you want to:
You probably need to generate an array of points defining the oval path (instead of using ovalIn:
).
Let's look at an oval path using only 10 points, with array[0]
at 3 o'clock:
the animation always begins at the first point of the path:
now the train animates a little bit:
and the user taps to go to the "Directions" scene.
We find the index of the closest point in the array, save it, and then on return re-create the path using that index as the starting point:
We let the animation run, and the user taps to see the "Credits" scene:
Again, we find the index of the closest point in the array, save it, and then on return re-create the path using that index as the starting point:
To get a smoother oval path, we'll use more points:
We can still use the current train position to find the closest point, and re-create the path using that point as the starting point:
The more points we use, the smoother the oval will be. Here it is with 180 points (the labels would be a mess):
To get the train on the inside of the oval, and "right-side-up," we can replace the texture with a horizontally flipped image when the node is below the centerline of the oval:
Re-creating the path from a new index is very fast:
var ptsCopy = Array(thePoints[startIDX...])
ptsCopy.append(contentsOf: thePoints[0..<startIDX])
let pth = UIBezierPath()
pth.move(to: ptsCopy.removeFirst())
while !ptsCopy.isEmpty {
pth.addLine(to: ptsCopy.removeFirst())
}
pth.close()
and, unless you're doing something you haven't talked about in this and your other related posts, we don't have to repeatedly generate the "oval array of points" ... You'll only have two sizes - one for portrait orientation and one for landscape.
I updated the GitHub repo I had provided you before: https://github.com/DonMag/SpriteKitRotation
and here's how it looks when running: https://www.youtube.com/watch?v=ziDqt-ApuSU
Edit:
With only minor code change, and with one (or more) "overhead" views of the train, you could also do this with the animation to make it a little less abrupt:
Edit 2:
Sometimes, we have to experiment to understand how things work.
Let's try using these two images:
The yellow/red arrow shape is your "train." The Black arrow is pointing to the top of the image.
When we animate a node along a path, and we use orientToPath: true
, SceneKit positions the center of the node on the path, and orients the top of the node in the direction of movement.
So, let's define an octagonal path (so we can see the direction/angle changes):
We'll create two of these, and use the "horizontal" image for one path and the "vertical" image for the other. We position the nodes at the starting points of the paths, but we do not yet add animation:
As soon as we start the animation, we see that the Black Arrow - which points to the top of the image/node - is oriented in the direction of movement:
As it "travels":
We see that it rotates as it should.
For your usage, though, you don't want the built-in behavior... you want the node flipped -- based on your own desired behavior. You want the "yellow top of the train" to be oriented "up" at different locations on the path:
So, it's not a matter of SceneKit not doing what Apple's docs says it will do... it's a misunderstanding of how these things work. And, as I said, Sometimes, we have to experiment to reach that understanding.
Edit 3:
Multiple sprite nodes, using the same points-defined-oval-path, starting at different "index" points (ignore the "jump" across the top... had to keep the gif under 2mb):