2

I am attempting to generate a random X position for a simple object, in order to have it bounce back and forth inside the scene in a Swift SpriteKit game. The repeatForever action should generate a random value and then move the object (a circle) multiple times to different locations, left and right. However, it acts only one time. It is as if the random function, which works correctly, is called only one time, and the action then simply continues to move the object to the same position forever.

let circle = SKSpriteNode(imageNamed: "circle")
circle.run(SKAction.repeatForever(SKAction.move(to:
        CGPoint(x: random(min: minCircleX, max: maxCircleX),
            y: scene.size.height*0.5),
            duration: 0.5)))

The circle moves only one time, to one position, and never seems to be moved after that. I suspect it is simply moving to the same position over and over. Thanks for any suggestions!

Dave Jones
  • 21
  • 2
  • 1
    I’m not a Swift user, but that looks like a single call to `random` instead of passing in a lambda or anonymous-function. It’s also possible that `random`‘s static state is being reset between each call. What happens if you replace `random` with a call to your own function that wraps `random` and then you set a breakpoint in it to see how it’s being called? – Dai Aug 24 '19 at 21:53
  • Dai is correct, the random value gets passed into the moveto function, which is then stored to run. The repeat function is not going to call random again, it is going to only call the returned object that SKAction.moveto produced – Knight0fDragon Aug 25 '19 at 00:27
  • Hmmm...great comments, thanks. The random function is my own, so it easy to demonstrate that it does indeed get called every time through the repeatForever loop. Apparently, though, the CGPoint is frozen after the first call, and the object passed to the move(to:) function does not get updated. I will try other variations where the CGPoint is regenerated each time, if possible. – Dave Jones Aug 25 '19 at 18:05

2 Answers2

0

OK, after multiple efforts, the following works:

let customAction = SKAction.customAction(withDuration: 1000)
{
    circle, _ in
    let newXLocation: CGPoint = CGPoint(x: random(min: minCircleX, max: maxCircleX),
                                        y: scene.size.height*0.5)
    circle.run(SKAction.move(to: newXLocation, duration: 0.5))
}
circle.run(customAction)

Dai was correct in suggesting an anonymous function. The SKAction method customAction allows for that. And then running the actual action inside the function insured that the movement was random every time. Thanks so much!

Dave Jones
  • 21
  • 2
  • I would not do this in this way, your durations are throwing everything off here, you are going to find your guy moving at weird speeds all across your random locations. On top of that, you will have multiple animations running concurrently, which is going to make your circle twitch – Knight0fDragon Aug 26 '19 at 02:41
0

Instead of repeat forever, use the completionblock to establish a new moveTo:

func newLocation(){
    let newXLocation: CGPoint = CGPoint(x: random(min: minCircleX, max: maxCircleX),
                                    y: scene.size.height*0.5)
    circle.run(SKAction.move(to: newXLocation, duration: 0.5){
       [weak self] in self?.newLocation()
    }
}

What this does is upon finishing the moveTo, it will add a new moveTo and the next random location. The only "flaw" is it will start the next move during the same frame as the current move since this is being done in the action phase of the update. If you do not want this effect, then you need to store it in a block to be done during didFinishUpdate

var  finishedUpdates = [()->()]()
func newLocation(){
    let newXLocation: CGPoint = CGPoint(x: random(min: minCircleX, max: maxCircleX),
                                        y: scene.size.height*0.5)
    circle.run(SKAction.move(to: newXLocation, duration: 0.5){
       [weak self] in 
       self?.finishedUpdates.append({weak self] in self?.newLocation()})
    }
}


func didFinishUpdate()
{
    finishedUpdates.forEach{$0()}
    finishedUpdates = [()->()]() //Be sure to clear the array
}

My apologies in advance, this is written from memory alone, and may not be 100% syntactically correct

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44