Wait until function is completed - swift

I have to function for adding two sprites and moving, called Space1 and Space2. I need to make Space1 move Right to Left, after Space 1 is finished the moving. Then, Space2 will move Left to Right.
I can make them move by 2 functions Move1() and Move2(); but they started the same time.
How can I make the Space2 wait until the function Move1 of Space1 is completed and then Space2 start moving?
This is my code
// Create sprite
let space1 = SKSpriteNode(imageNamed: "Spaceship")
space1.position = CGPoint(x: 0, y: frame.height/2)
space1.xScale = 0.3;
space1.yScale = 0.3;
space1.name = "space1";
self.addChild(space1);
let space2 = SKSpriteNode(imageNamed: "Spaceship")
space2.position = CGPoint(x: frame.width, y:0)
space2.xScale = 0.3;
space2.yScale = 0.3;
space2.name = "space2";
self.addChild(space2);
let actualDuration = random(min: CGFloat(1), max: CGFloat(3))
// Create the actions
let actionMove1 = SKAction.moveTo(CGPoint(x: frame.width, y: frame.height/2), duration: NSTimeInterval(actualDuration))
let actionMove2 = SKAction.moveTo(CGPoint(x: 0, y: 0), duration: NSTimeInterval(actualDuration))
self.runAction(SKAction.sequence([SKAction.runAction(actionMove1, onChildWithName: "space1"),SKAction.runAction(actionMove2, onChildWithName: "space2")]))

You can use the completion handler of runAction function to get what you need. Start the next SKAction inside the completion handler of the first runAction function.
space1.runAction(actionMove1, completion: { () -> Void in
space2.runAction(actionMove2)
})

SpriteKit provides something called SKAction.sequence. You can give it an array of actions which will be started after the one before has finished:
func startAction(){
var space1 = SKSpriteNode()
var space2 = SKSpriteNode()
var move1 = SKAction()
var move2 = SKAction()
space1.name = "space1"
space2.name = "space2"
self.runAction(SKAction.sequence([SKAction.runAction(move1, onChildWithName: "space1"), SKAction.runAction(move2, onChildWithName: "space2")]))
}
As you see here, I add names to the SKSpriteNodes. That's important because as you can see in the sequence, you have to specify, on which node the action is getting called.
Also important to say is, that if you use sub-sequences, you need to work with SKAction.waitForDuration(sec: NSTimeInterval). For example:
self.runAction(SKAction.sequence([SKAction.runAction(move1, onChildWithName: "space1"), SKAction.waitForDuration(move1.duration), SKAction.runAction(move2, onChildWithName: "space2")]))}

Related

My SKSpriteNode speed values are not updating properly

What I'm trying to do is update my SKSpriteNodes so I can change their scrolling speeds dynamically, however they aren't really working consistently. I didn't include the code, but I have another method with a switch case that sets the value of platformSpeed whenever the state is changed (in this case, the switch case is changed with UIButtons). In my code I have an SKSpriteNode array and a platformSpeed property that includes didSet so my value is updated properly.
In my method to create the platforms, I grouped my SpriteNodes into platformGroup then looped through them with addChild(). Not sure why it's acting this way but here's a quick video of what it looks like in action:
demonstration clip
So with the buttons I'm changing the switch case, and as you can see, not all of the nodes speeds are updating properly and some get faster than others and eventually pass them. I need them to stay equal distance between each other.
Now here's my code:
class GameScene: SKScene, SKPhysicsContactDelegate {
var platformGroup = [SKSpriteNode]()
var platformSpeed: CGFloat = 1.0 {
didSet {
for platforms in platformGroup {
platforms.speed = platformSpeed
}
}
}
let platformTexture = SKTexture(imageNamed: "platform")
var platformPhysics: SKPhysicsBody!
func createPlatforms() {
let platformLeft = SKSpriteNode(texture: platformTexture)
platformLeft.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformLeft.physicsBody?.isDynamic = false
platformLeft.scale(to: CGSize(width: platformLeft.size.width * 4, height: platformLeft.size.height * 4))
platformLeft.zPosition = 20
let platformRight = SKSpriteNode(texture: platformTexture)
platformRight.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformRight.physicsBody?.isDynamic = false
platformRight.scale(to: CGSize(width: platformRight.size.width * 4, height: platformRight.size.height * 4))
platformRight.zPosition = 20
let scoreNode = SKSpriteNode(color: UIColor.clear, size: CGSize(width: frame.width, height: 32))
scoreNode.physicsBody = SKPhysicsBody(rectangleOf: scoreNode.size)
scoreNode.physicsBody?.isDynamic = false
scoreNode.name = "scoreDetect"
scoreNode.zPosition = 40
platformGroup = [platformLeft, platformRight, scoreNode]
let yPosition = frame.width - platformRight.frame.width
let max = CGFloat(frame.width / 4)
let xPosition = CGFloat.random(in: -80...max)
let gapSize: CGFloat = -50
platformLeft.position = CGPoint(x: xPosition + platformLeft.size.width - gapSize, y: -yPosition)
platformRight.position = CGPoint(x: xPosition + gapSize, y: -yPosition)
scoreNode.position = CGPoint(x: frame.midX, y: yPosition - (scoreNode.size.width / 1.5))
let endPosition = frame.maxY + (platformLeft.frame.height * 3)
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
let moveSequence = SKAction.sequence([moveAction, SKAction.removeFromParent()])
for platforms in platformGroup {
addChild(platforms)
platforms.run(moveSequence)
}
platformCount += 1
}
func loopPlatforms() {
let create = SKAction.run { [unowned self] in
self.createPlatforms()
platformCount += 1
}
let wait = SKAction.wait(forDuration: 1.1)
let sequence = SKAction.sequence([create, wait])
let repeatForever = SKAction.repeatForever(sequence)
run(repeatForever)
}
I think I can see what's going wrong. When you change platformSpeed, it changes the speed of all the platforms in platformGroup. And createPlatforms() is being called multiple times. Now, each time it's called you create a pair of platforms and assign these to platformGroup. Since you call the function multiple times, it's overwriting any existing values in the array. That's why changing platformSpeed only updates the speed of the latest platforms you've created---the older platforms stay the same speed because they're not in platformGroup anymore.
To fix this, my advice would be to have platformGroup store all the platforms currently on the screen. You could do this by changing
platformGroup = [platformLeft, platformRight, scoreNode]
to something like
let newNodes = [platformLeft, platformRight, scoreNode]
platformGroup += newNodes
// Alternatively, platformGroup.append(contentsOf: newNodes)
Now you need to make sure you're 1) only adding the new nodes to the scene, and 2) removing the old nodes from platformGroup when they're removed from the parent. You could do this by changing
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
let moveSequence = SKAction.sequence([moveAction, SKAction.removeFromParent()])
for platforms in platformGroup {
addChild(platforms)
platforms.run(moveSequence)
}
to something like
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
for node in newNodes {
let moveSequence = SKAction.sequence([
moveAction,
SKAction.removeFromParent(),
SKAction.run {
self.platformGroup.remove(node)
}
])
addChild(node)
node.run(moveSequence)
}
Now that you're keeping a track of all platforms ever made, your speed changes should be applied consistently to every platform on the screen. Hope this works!

Swift: Pause endless scrolling background (i.e. infinite loop)

In my game I would like certain things to pause when the "endGame" function is called, while other things stay running. I have been able to accomplish this for most actions by using the "paused" boolean, but I can't get it to work for my scrolling background. I know the problem has to do with how the background action is contained in a for loop, but I'm not sure how to get around this. Here is an example of my code:
class GameScene: SKScene {
var sun = SKSpriteNode()
var background = SKSpriteNode()
override func didMoveToView(view: SKView) {
sun = SKSpriteNode(imageNamed: "Sun")
sun.zPosition = -2
sun.setScale(0.8)
sun.position = CGPoint(x: self.frame.size.width * 0.632, y: self.frame.size.height * 0.90)
let rotate = SKAction.rotateByAngle(CGFloat(M_PI_2), duration: NSTimeInterval(10))
let rotateForever = SKAction.repeatActionForever(rotate)
sun.runAction(rotateForever)
addChild(sun)
let backgroundTexture = SKTexture(imageNamed: "Background")
backgroundTexture.filteringMode = .Nearest
let moveBackground = SKAction.moveByX(-backgroundTexture.size().width, y: 0, duration: NSTimeInterval(0.004 * backgroundTexture.size().width * 2.0))
let resetBackground = SKAction.moveByX(backgroundTexture.size().width, y: 0, duration: 0.0)
let moveBackgroundForever = SKAction.repeatActionForever(SKAction.sequence([moveBackground,resetBackground]))
for var i:CGFloat = 0; i < 4.0 + self.frame.size.width / ( backgroundTexture.size().width ); ++i {
background = SKSpriteNode(texture: backgroundTexture)
background.setScale(0.5)
background.zPosition = -1
background.position = CGPointMake(i * background.size.width, backgroundVert)
background.runAction(moveBackgroundForever)
addChild(background)
}
}
func endGame {
sun.paused = true
background.paused = true
}
}
In this example the "paused" boolean works perfectly for the rotating SKSpriteNode called "sun", but it doesn't work for the background. Any thoughts?
What you should do is create 1 SKNode for your background sprites, add all the background nodes to this node, then add the master node to your scene. Then, when you need to pause, you only pause the master node, and the master node will then pause all of its children
for var i:CGFloat = 0; i < 4.0 + self.frame.size.width / (backgroundTexture.size().width ); ++i {
var background = SKSpriteNode(texture: backgroundTexture)
background.setScale(0.5)
background.zPosition = -1
background.position = CGPointMake(i * background.size.width, backgroundVert)
background.runAction(moveBackgroundForever)
self.background.addChild(background)
}
addChild(background)
Looks like you are creating multiple backgrounds in the for loop. But you are pausing only one.

Getting different colour backgrounds to scroll up

I am coding for my A2 coursework project, and making a well-known game 'Fall Down'. However i am trying to get the background to change every cycle while it scrolls - so it changes from blue to red to yellow etc. However when I run it, this only works for the first two colours, then flashes back to the first colour. Here is the code i currently have
var background = SKSpriteNode()
override func didMoveToView(view: SKView) {
let blueTexture = SKTexture(imageNamed: "BlueBackground")
let redTexture = SKTexture(imageNamed: "RedBackground")
let yellowTexture = SKTexture(imageNamed: "YellowBackground")
let greenTexture = SKTexture(imageNamed: "GreenBackground")
let purpleTexture = SKTexture(imageNamed: "PurpleBackground")
let TextureArray = [blueTexture, redTexture, yellowTexture, greenTexture, purpleTexture]
let levelProgress = SKAction.moveByX(0, y:blueTexture.size().height, duration: 5)
let newLevel = SKAction.moveByX(0 , y: -blueTexture.size().height, duration: 0)
let sequenceForever = SKAction.repeatActionForever(SKAction.sequence([ levelProgress, newLevel]))
var a = 0
for var i: CGFloat = 0; i<5; i++ {
let currentBG = TextureArray[a]
a++
background = SKSpriteNode(texture: currentBG)
background.position = CGPoint(x: CGRectGetMidX(self.frame), y: -blueTexture.size().height/2 + blueTexture.size().height * i)
background.size.width = self.frame.width
background.runAction(sequenceForever)
self.addChild(background)
}
if anyone could point me in the right direction or if any more information is needed let me know! This is my first post so any posting advice would be great too.
It might by because of this:
let levelProgress = SKAction.moveByX(0, y:blueTexture.size().height, duration: 5)
let newLevel = SKAction.moveByX(0 , y: -blueTexture.size().height, duration: 0)
let sequenceForever = SKAction.repeatActionForever(SKAction.sequence([ levelProgress, newLevel]))
background.position = CGPoint(x: CGRectGetMidX(self.frame), y: -blueTexture.size().height/2 + blueTexture.size().height * i
you always use the blueTexture.size. try to change it to a constant value.

Swift: how to invalidate timer on animated SKShapenode countdown?

Alright I'm building a sprite kit game and on one of my previous questions, I got the code that lets a circle skshapenode "unwind" in accordance with a timer that runs out (so that the circle has disappeared by the time the timer is done).
Here was my question: Swift, sprite kit game: Have circle disappear in clockwise manner? On timer?
And here is the code that I've been using:
func addCircle() {
circle = SKShapeNode(circleOfRadius: 50)
let circleColor = UIColor(red: 102/255, green: 204/255, blue: 255/255, alpha: 1)
circle.fillColor = circleColor
circle.strokeColor = SKColor.clearColor()
circle.position = CGPoint (x: self.frame.size.width * 0.5, y: self.frame.size.height * 0.5+45)
circle.zPosition = 200
circle.zRotation = CGFloat(M_PI_2)
addChild(circle)
countdown(circle, steps: 120, duration: 5) { //change steps to change how much of the circle it takes away at a time
//Performed when circle timer ends:
print("circle done")
self.gameSoundTrack.stop()
self.removeAllChildren()
self.removeAllActions()
self.viewController.removeSaveMe(self)
self.GOSceneNodes() //Implements GOScene
self.viewController.addGOScene(self) //Implements GOViewController
}
}
// Creates an animated countdown timer
func countdown(circle:SKShapeNode, steps:Int, duration:NSTimeInterval, completion:()->Void) {
let radius = CGPathGetBoundingBox(circle.path).width/2
let timeInterval = duration/NSTimeInterval(steps)
let incr = 1 / CGFloat(steps)
var percent = CGFloat(1.0)
let animate = SKAction.runBlock({
percent -= incr
circle.path = self.circle(radius, percent:percent)
})
let wait = SKAction.waitForDuration(timeInterval)
let action = SKAction.sequence([wait, animate])
runAction(SKAction.repeatAction(action,count:steps-1)) {
self.runAction(SKAction.waitForDuration(timeInterval)) {
circle.path = nil
completion()
}
}
}
// Creates a CGPath in the shape of a pie with slices missing
func circle(radius:CGFloat, percent:CGFloat) -> CGPath {
let start:CGFloat = 0
let end = CGFloat(M_PI*2) * percent
let center = CGPointZero
let bezierPath = UIBezierPath()
bezierPath.moveToPoint(center)
bezierPath.addArcWithCenter(center, radius: radius, startAngle: start, endAngle: end, clockwise: true)
bezierPath.addLineToPoint(center)
return bezierPath.CGPath
}
This all works well, however I need a way to invalidate the countdown for the circle if another button within the game is pressed so that everything that is performed when the timer runs out (everything after //Performed when circle timer ends: above) is NOT performed.
I have tried
circle.removeAllActions()
circle.removeFromParent()
when the button is pressed, however this just removes the circle and everything is still executed even though the circle is gone.
I'm pretty new to Swift and I can't figure out what part of those 3 functions I need to invalidate or stop in order to stop the countdown from finishing. How can I accomplish this?
You can stop an action by adding a key to the action and then run
removeActionForKey("actionKey")
To add a key to the action, replace the countdown method with the following:
// Creates an animated countdown timer
func countdown(circle:SKShapeNode, steps:Int, duration:NSTimeInterval, completion:()->Void) {
let radius = CGPathGetBoundingBox(circle.path).width/2
let timeInterval = duration/NSTimeInterval(steps)
let incr = 1 / CGFloat(steps)
var percent = CGFloat(1.0)
let animate = SKAction.runBlock({
percent -= incr
circle.path = self.circle(radius, percent:percent)
})
let wait = SKAction.waitForDuration(timeInterval)
let action = SKAction.sequence([wait, animate])
let completed = SKAction.runBlock{
circle.path = nil
completion()
}
let countDown = SKAction.repeatAction(action,count:steps-1)
let sequence = SKAction.sequence([countDown, SKAction.waitForDuration(timeInterval),completed])
runAction(sequence, withKey: "actionKey")
}

Randomizing node movement duration

I am making a game in SpriteKit and I have a node that is moving back and forth across the screen and repeating using the code:
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: 1.5)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: 1.5)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "Drake2"))
let texLeft = SKAction.setTexture(SKTexture(imageNamed: "Drake1"))
let moveBackAndForth = SKAction.repeatActionForever(SKAction.sequence([texRight, moveRight, texLeft, moveLeft,]))
Drake1.runAction(moveBackAndForth)
I am trying to figure out what method I can use to randomize the duration. Every time moveBackandForth runs, I want it to rerun using a different duration, (within games, not between). If someone could give me some example code to try I would really appreciate it.
Also arc4Random works fine, but it doesn't randomize within the game, only between games.
When you run actions like from your example and randomize duration parameter with something like arc4Random this is actually happening:
Random duration is set and stored in action.
Then action is reused in a sequence with a given duration.
Because the action is reused as it is, duration parameter remains the same over time and moving speed is not randomized.
One way to solve this (which I prefer personally) would be to create a "recursive action", or it is better to say, to create a method to run desired sequence and to call it recursively like this :
import SpriteKit
class GameScene: SKScene {
let shape = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 20, height: 20))
override func didMoveToView(view: SKView) {
shape.position = CGPointMake(CGRectGetMidX(self.frame) , CGRectGetMidY(self.frame)+60 )
self.addChild(shape)
move()
}
func randomNumber() ->UInt32{
var time = arc4random_uniform(3) + 1
println(time)
return time
}
func move(){
let recursive = SKAction.sequence([
SKAction.moveByX(frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.runBlock({self.move()})])
shape.runAction(recursive, withKey: "move")
}
}
To stop the action, you remove its key ("move").
I don't have any project where I can try right now.
But you might want to try this :
let action = [SKAction runBlock:^{
double randTime = 1.5; // do your arc4random here instead of fixed value
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: randTime)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: randTime)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "Drake2"))
let texLeft = SKAction.setTexture(SKTexture(imageNamed: "Drake1"))
let sequence = SKAction.sequence([texRight, moveRight, texLeft, moveLeft])
Drake1.runAction(sequence)
}]; 
let repeatAction = SKAction.repeatActionForever(action)
Drake1.runAction(repeatAction)
Let me know if it helped.