Running functions in sequence - sprite-kit

I am trying to make a game that starts after a 3 second delay. So I am trying to add a sequence, so that the startGame function is called in a sequence after a delay. I can then call that function with the delay included at the beginning, but I keep getting an error when I try the run the sequence at the end of the second function.
I have the start game function:
func startGame(){
let spawn = SKAction.run(createEnemy)
let wait = SKAction.wait(forDuration: 2)
let spawnSequence = SKAction.sequence([wait, spawn])
let spawnForever = SKAction.repeatForever(spawnSequence)
self.run(spawnForever)
}
and then I have another function that uses that function in a sequence to add a delay.
func beginGame(){
let countdown = SKAction.wait(forDuration: 3)
let startGame = SKAction.run(self.startGame)
let startSequence = SKAction.sequence([countdown, startGame])
**self.beginGame().run(startSequence)**
}
I then call the beginGame() function at the along with the setup function. along with the setup function at the end.
scene.setup()
scene.beginGame()
I am getting an "Value of tuple '()' has no member 'run'"
Sorry for the stupid question, I'm a beginner at swift.

Try
let spawn = SKAction.run { self.createEnemy() }
and
let startGame = SKAction.run { self.startGame() }
run block is a closure so it might need the brackets
try this...
func startGame() -> SKAction {
let spawn = SKAction.run { self.hello() }
let wait = SKAction.wait(forDuration: 2)
let spawnSequence = SKAction.sequence([wait, spawn])
let spawnForever = SKAction.repeatForever(spawnSequence)
return spawnForever
}
func beginGame() {
let countdown = SKAction.wait(forDuration: 3)
let startGame = self.startGame()
let startSequence = SKAction.sequence([countdown, startGame])
}
self.beginGame()

SKActions should be run on a node. Your line of code
self.beginGame().run(startSequence) appears to be trying to run your sequence on the function beginGame(). Because beginGame()which does not have a defined return type.
According to https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html
Functions without a defined return type return a special value of type
Void. This is simply an empty tuple, which is written as ().
and as the compiler is telling you, you cannot use run on a tuple.

Related

How to get different random delays in a SpriteKit sequence?

I have a sequence where i spawn a obstacle and then wait for a random amount of time, but if I run the game and for example the first random delay 1.4 seconds, but its not just for the first delay it's just all the time 1.4 and it doesn't change (it doesn't have to be 1.4 it's just an example). I have tried to make a function which has a random return value but its doesn't work. I have no idea how i could solve this. Here's my Code for the function with the random return value. If it helps obstSwitch() is the function that creates the Obstacle:
func getRandomDelay() ->Double {
let randomNumber = arc4random_uniform(20) + 5
let randomDelay: Double = Double(randomNumber) / 10
print(randomDelay)
return randomDelay
}
and heres the function that get's called when the game started:
func gameStarted() {
gameAbleToStart = false
moveLine()
scoreTimer()
let runObstSwitch = SKAction.run {
self.obstSwitch()
}
let wait = SKAction.wait(forDuration: getRandomDelay())
let sequence = SKAction.sequence([runObstSwitch, wait])
self.run(SKAction.repeatForever(sequence))
}
let wait = SKAction.wait(forDuration: getRandomDelay())
let sequence = SKAction.sequence([runObstSwitch, wait])
creates the wait action once, which is then used in the sequence,
so the same amount of idle time is spent between the runObstSwitch
actions.
If you want the idle time to be variable, use
wait(forDuration:withRange:) instead. For example with
let wait = SKAction.wait(forDuration: 1.5, withRange: 2.0)
let sequence = SKAction.sequence([runObstSwitch, wait])
the delay will be a random number between 1.5-2.0/2 = 0.5 and 1.5+2.0/2 = 2.5 seconds, varying for each execution.

swift spritekit increase frequency of node creation as time goes on

I have figured out how to continuously spawn a node every x seconds. However, I would like to decrease the time that I wait to create a node as the game goes on, to increase the difficulty. For example, I call this function in didMoveToView:
func createSequentialEnemies(){
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(createEnemy),
SKAction.waitForDuration(2.0)
])
))
}
This creates an enemy every 2 seconds, but I want to decrease this duration arbitrarily. For example, say that after 30 seconds of gameplay I want to now spawn enemies every 1.5 seconds. How would I change the duration dynamically?
Create a spawnDuration property and key reference in your scene class.
class SomeClass: SKScene {
private var spawnDuration: NSTimeInterval = 2.0
private let spawnKey = "SpawnKey"
}
Than adjust your spawn code to use this spawn property and key. I slightly changed the syntax as well to make it more legible in my opinion.
func createSequentialEnemies(){
removeActionForKey(spawnKey) // remove previous action if running. This way you can adjust the spawn duration property and call this method again and it will cancel previous action.
let spawnAction = SKAction.runBlock(createEnemy)
let spawnDelay = SKAction.waitForDuration(spawnDuration)
let spawnSequence = SKAction.sequence([spawnAction, spawnDelay])
runAction(SKAction.repeatActionForever(spawnSequence), withKey: spawnKey) // run action with key so you can cancel it later
}
Than you have to add some logic of when you want to change the spawn duration property you created.
Time based could be a func like this you also call once in DidMoveToView
func startDifficultyTimer() {
let difficultyTimerKey = "DifficultyTimerKey"
let action1 = SKAction.waitForDuration(30)
let action2 = SKAction.runBlock { [unowned self] in
guard self.spawnDuration > 0.2 else { // set a min limit
removeActionForKey(difficultyTimerKey) // if min duration has been reached than you might as well stop running this timer.
return
}
self.spawnDuration -= 0.5 // reduce by half a second
self.createSequentialEnemies() // spawn enemies again
}
let sequence = SKAction.sequence([action1, action2])
runAction(SKAction.repeatActionForever(sequence), withKey: difficultyTimerKey)
}
Hope this helps

Wait For Duration Decreasing Parameter

I want to decreasing the parameter of SKAction.waitForDuration(X).
I'm using the Flappy Bird Tutorial (https://www.youtube.com/watch?v=RjUvEiNxWfc - see on minute 7) and I want to do something like
var timeToWait:Int = 8
let spawn = SKAction.runBlock({
() in
self.createWalls()
timeToWait--
})
let delay = SKAction.waitForDuration(timeToWait)
let SpawnDelay = SKAction.sequence([spawn, delay])
let spawnDelayForever = SKAction.repeatActionForever(SpawnDelay)
self.runAction(spawnDelayForever)
However, the parameter for waitForDuration is NSTimer and not a float. What could I do to change that parameter?
Thank you!
waitForDuration expects a value of type NSTimeInterval.
You can Type cast timeToWait when you pass it to the function:
let delay = SKAction.waitForDuration(NSTimeInterval(timeToWait))
By the way, your code seems to want to decrease the delay between each spawn but it will not do that because timeDelay is captured in your creation of the delay action and will not change that action's duration if you modify the variable afterward.
[EDIT]
Here's an example accelerates spawning interval (from 8 to 1) in cycles:
let spawn = SKAction.runBlock({ self.createWalls() })
var spawnCycle:[SKAction] = []
for timeToWait in (1...8).reverse()
{
spawnCycle.append(spawn)
spawnCycle.append(SKAction.waitForDuration(NSTimeInterval(timeToWait)))
}
self.runAction(SKAction.repeatActionForever(SKAction.sequence(spawnCycle)))

Closure CallBack Only Retain Last Object Created in Swift

i'm not sure if the title fit my question but my question is as below.
First of all, i will talk how i encounter this problem. Basically, I'm creating a game(you can just imagine) which has A MainPlayer and Many Of Enemies(AI). So, the MainPlayer will move around and enemies will chase after him. As you can imagine, the MainPlayer's position will update in every frame(maybe) and i need also to update enemies' chasing position(which is main character's position). So i'm using Closure to do it. The reason why i'm not using array to store all the enemies and update it every frame is because the enemies can be killed and will random spawn new enemies in the amount of time i set. If i use array it is kind of tricky and unsafe.
So, back to my question, i created this beginning of GameScene class:
typealias CallBacks = () -> Void
var playerDidMoveCallBacks: CallBacks?
This is my create enemy class: (The callback is at most bottom)
// Create Enemies
func createEnemyAtPosition(position: CGPoint) {
let enemyNode = EnemyNode()
enemyNode.name = "ENEMY_NODE"
enemyNode.position = position
enemyNode.setScale(1.5)
addChild(enemyNode)
let sprite = SKSpriteNode(imageNamed: "enemyWisp")
enemyNode.addChild(sprite)
enemyNode.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width / 2)
enemyNode.physicsBody?.dynamic = true
enemyNode.physicsBody?.categoryBitMask = CollisionCategoryBitMask.Enemy
enemyNode.physicsBody?.collisionBitMask = 0
enemyNode.physicsBody?.contactTestBitMask = CollisionCategoryBitMask.ThrowingKnife | CollisionCategoryBitMask.Player
enemyNode.physicsBody?.usesPreciseCollisionDetection = true
let distanceWillCollideWithPlayer = sqrt(pow(enemyNode.position.x - self.playerNode.position.x, 2) + pow(enemyNode.position.y - self.playerNode.position.y, 2))
let durationWillColldeWithPlayer = NSTimeInterval(distanceWillCollideWithPlayer / self.enemyMovingSpeed)
let enemyMoveAction = SKAction.moveTo(self.playerNode.position, duration: durationWillColldeWithPlayer)
enemyNode.runAction(enemyMoveAction)
println("\((unsafeAddressOf(enemyNode)))")
// Update Player Position
playerDidMoveCallBacks = {() -> Void in
let distanceWillCollideWithPlayer = sqrt(pow(enemyNode.position.x - self.playerNode.position.x, 2) + pow(enemyNode.position.y - self.playerNode.position.y, 2))
let durationWillColldeWithPlayer = NSTimeInterval(distanceWillCollideWithPlayer / self.enemyMovingSpeed)
println("\((unsafeAddressOf(enemyNode)))")
let enemyMoveAction = SKAction.moveTo(self.playerNode.position, duration: durationWillColldeWithPlayer)
enemyNode.runAction(enemyMoveAction)
}
}
When i know my hero changed position i call the callback like this:
if self.playerDidMoveCallBacks != nil {
self.playerDidMoveCallBacks!()
}
But this can only work for the last object created and i think this make sense. Anyone can give me a solution?
What you want is an array of callbacks.
var playerDidMoveCallBacks: [CallBack]?
As every enemy is captured in the closure you could just iterate the array and call each callback.
for callback in playerDidMoveCallBacks {
callback()
}
But the solution of 0x141E is better.
The problem is that the enemy could not exist anymore when you call the closure, but the closure captures the object which can lead to strange behaviour. (ghost enemies)
With help from 0x141E i create this line of code in func update and solve my problem
// Update Enemies Position
for childNode in children {
let childSprite = childNode as? SKNode
if (childSprite?.name == "ENEMY_NODE") {
let distanceWillCollideWithPlayer = sqrt(pow(CGFloat(childSprite!.position.x) - self.playerNode.position.x, 2) + pow(CGFloat(childSprite!.position.y) - self.playerNode.position.y, 2))
let durationWillColldeWithPlayer = NSTimeInterval(distanceWillCollideWithPlayer / self.enemyMovingSpeed)
let enemyMoveAction = SKAction.moveTo(self.playerNode.position, duration: durationWillColldeWithPlayer)
childSprite!.runAction(enemyMoveAction)
}
}

How can I create a loop that runs two functions, never overlapping?

I'm new to programming and have been taking online courses in swift and spritekit trying to create my first working game from scratch.
Currently I'm having an issue trying to create a sequence of functions that run independently, wait so that only one is running at a time, and loops indefinitely.
The first function:
func shootTwentyArrows() {
var oneArrow = SKAction.runBlock{
self.shootArrow()
}
var waitBetweenArrows = SKAction.waitForDuration(arrowSpeed)
var fireAnArrow = SKAction.sequence([oneArrow, waitBetweenArrows])
self.runAction(SKAction.repeatAction(fireAnArrow, count: 20))
}
And the second function:
func shootSpiral1() {
var leftArrow = SKAction.runBlock{
self.arrowFromLeft()
}
var rightArrow = SKAction.runBlock{
self.arrowFromRight()
}
var waitBetweenArrows = SKAction.waitForDuration(arrowSpeed)
var sequence = SKAction.sequence([leftArrow, waitBetweenArrows, rightArrow, waitBetweenArrows])
var spiral1 = SKAction.repeatAction(sequence, count: 5)
self.runAction(spiral1)
to clarify, I'm trying to run something like:
shootTwentyArrows()
when that's done, shootSpiral1(), when that's done repeat.
Thanks in advance for any responses.
I guess the most correct way to do that would be to refactor code a little bit:
func shootTwentyArrows() -> SKAction {
let oneArrow = SKAction.runBlock{
self.shootArrow()
}
let waitBetweenArrows = SKAction.waitForDuration(arrowSpeed)
let fireAnArrow = SKAction.sequence([oneArrow, waitBetweenArrows])
return SKAction.repeatAction(fireAnArrow, count: 20)
}
func shootSpiral1() -> SKAction {
let leftArrow = SKAction.runBlock{
self.arrowFromLeft()
}
let rightArrow = SKAction.runBlock{
self.arrowFromRight()
}
let waitBetweenArrows = SKAction.waitForDuration(arrowSpeed)
let sequence = SKAction.sequence([leftArrow, waitBetweenArrows, rightArrow, waitBetweenArrows])
let spiral1 = SKAction.repeatAction(sequence, count: 5)
return spiral1
}
Then somewhere in the code you can just do something like that :
let spiralAction = shootSpiral1()
let oneArrowAction = shootTwentyArrows()
let sequence = SKAction.sequence([spiralAction, oneArrowAction])
let infiniteSequence = SKAction.repeatActionForever(sequence)
self.runAction(infiniteSequence)
I left function names the same on purpose, so you get the idea.
P.S. It is a common practice to declare a variable as let, declare it as var only when you have to modify it later.
The best way to do this is have an SKAction sequence that calls both of your functions. You would call this SKAction from wherever you want to initialize the arrow shooting actions like viewDidLoad for example. This would be the code to call the actions...
var actionShootingArrows = SKAction.sequence([shootSpiral1(), shootTwentyArrows()])
self.runAction(SKAction.repeatActionForever(actionShootingArrows))
Hope this helps!