How do I stop a node as it is moving around a UIBezierPath and then restart the moving from where it stopped?

I have an oval UIBezierPath with a moving SKSpriteNode,

I stop its motion and record the stopped position. I then restart this motion and want it to restart where it initially stopped.

Works great if motion is not stopped. Movement is great around entire oval Path.

Also works great as long as this stop-restart sequence occurs along the top half of the oval UIBezierPath. However, I have problems along the bottom half of this Path -- it stops okay, but the restart position is not where it previously stopped.

My method to create this oval UIBezierePath is as follows:

func createTrainPath() {
    
    trainRect = CGRect(x: tracksPosX - tracksWidth/2,
                       y: tracksPosY - tracksHeight/2,
                       width: tracksWidth,
                       height: tracksHeight)

    // these methods come from @DonMag
    trainPoints = generatePoints(inRect: trainRect,
                                 withNumberOfPoints: nbrPathPoints)
    trainPath = generatePathFromPoints(trainPoints!,
                                       startingAtIDX: savedTrainIndex)
            
}   // createTrainPath

My method to stop this motion is as follows:

func stopFollowTrainPath() {
    
    guard (myTrain != nil) else { return }

    myTrain.isPaused = true

    savedTrainPosition = myTrain.position
    // also from @DonMag
    savedTrainIndex = closestIndexInPath(
                                  trainPath,
                                  toPoint: savedTrainPosition) ?? 0

}   // stopFollowTrainPath

Finally, I call this to re-start this motion:

func startFollowTrainPath() {
                           
    var trainAction = SKAction.follow(trainPath.cgPath,
                                      asOffset: false,
                                      orientToPath: true,
                                      speed: thisSpeed)
    trainAction = SKAction.repeatForever(trainAction)
    myTrain.run(trainAction, withKey: runTrainKey)
    myTrain.isPaused = false

}   // startFollowTrainPath

Again, great if motion is not stopped. Movement is great around entire oval Path.

Again, no problem for stopping and then restarting along top half of oval .. the ohoh occurs along bottom half.

Is there something I need to do within GameScene's update method that I am missing? For example, do I need to reconstruct my UIBezierPath? every time my node moves between the top half and the bottom half and therein account for the fact that the node is traveling in the opposite direction from the top half?

Answered by JohnLove in 814257022

===== SOLVED SOLVED =====

I placed print("pause index =", savedTrainIndex)after pausing the game and got 160, e.g. I also placed print("resume index =", savedTrainIndex) before resuming and got 160 as it should be as the node was moving along the top half of the oval.

Then, as the node was moving along the bottom half of the oval, I got 90.

Here is the response I received from Apple Deve;loper Tech Support - which worked flawlessly:

When you run the follow action on a node, SpriteKit will set the position of the node to the starting position of the provided path.

The issue you are observing (when the train makes large jumps in position) is caused because of large differences in the node’s current position compared to the follow path’s starting position.

This large difference is due to a bug in your code, specifically in the closestPointInPath function, which appears to be an algorithm that tries to determine the point on the path that is closest to the current position of the node, which you then use to calculate the starting point for the new follow path.

If you replace that function with this one (which contains a naive implementation to find the closest point to the target point), you will see that the large position jumps no longer occur:

public func closestPointInPath(_ path:UIBezierPath,
                               toPoint:CGPoint) -> CGPoint? {
        
    let targetPoint = toPoint
        
    let thePoints = getPointsForPath(path)
        
    var closestPoint = CGPoint.zero
    var minDistance = CGFloat.infinity
        
    for point in thePoints {
        let distance = distanceBetween(point, targetPoint)
            
        if distance < minDistance {
            minDistance = distance
            closestPoint = point
        }
    }
        
    return closestPoint

}

Mr. Chistie from Apple DTS is out-of-sight awesome.

Once again, I labor for 3 weeks and DTS solves the problem in 3 days.

===== new discovery =====

I placed print("pause index =", savedTrainIndex)after pausing the game and got 160, e.g. I also placed print("resume index =", savedTrainIndex) before resuming and got 160 as it should be as the node was moving along the top half of the oval.

Then, as the node was moving along the bottom half of the oval, I got 90.

I think this is the key to my challenge because I believe after stopping and then restarting along the bottom half of the oval, the stop/restart index should be a number > 160, not 90. In short, shouldn't the index monotonically increase as it is traveling along the whole oval?

Anyway, I am currently investigating this discrepancy. Don't know if it will lead to a solution or not at this time.

Accepted Answer

===== SOLVED SOLVED =====

I placed print("pause index =", savedTrainIndex)after pausing the game and got 160, e.g. I also placed print("resume index =", savedTrainIndex) before resuming and got 160 as it should be as the node was moving along the top half of the oval.

Then, as the node was moving along the bottom half of the oval, I got 90.

Here is the response I received from Apple Deve;loper Tech Support - which worked flawlessly:

When you run the follow action on a node, SpriteKit will set the position of the node to the starting position of the provided path.

The issue you are observing (when the train makes large jumps in position) is caused because of large differences in the node’s current position compared to the follow path’s starting position.

This large difference is due to a bug in your code, specifically in the closestPointInPath function, which appears to be an algorithm that tries to determine the point on the path that is closest to the current position of the node, which you then use to calculate the starting point for the new follow path.

If you replace that function with this one (which contains a naive implementation to find the closest point to the target point), you will see that the large position jumps no longer occur:

public func closestPointInPath(_ path:UIBezierPath,
                               toPoint:CGPoint) -> CGPoint? {
        
    let targetPoint = toPoint
        
    let thePoints = getPointsForPath(path)
        
    var closestPoint = CGPoint.zero
    var minDistance = CGFloat.infinity
        
    for point in thePoints {
        let distance = distanceBetween(point, targetPoint)
            
        if distance < minDistance {
            minDistance = distance
            closestPoint = point
        }
    }
        
    return closestPoint

}

Mr. Chistie from Apple DTS is out-of-sight awesome.

Once again, I labor for 3 weeks and DTS solves the problem in 3 days.

How do I stop a node as it is moving around a UIBezierPath and then restart the moving from where it stopped?
 
 
Q