How to gradually speed up falling nodes over time? - swift

I have two dots that fall parallel to each other from the top of my screen that are to be matched with two circles on the bottom that can be rotated with touch. I have them generated like this:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(generateDots),
SKAction.waitForDuration(1.0)])))
}
func generateDots() {
let count = 2
let index=Int(arc4random_uniform(UInt32(count)))
let dots = SKSpriteNode(imageNamed: "Color\(index+1)")
dots.physicsBody = SKPhysicsBody(circleOfRadius: 10)
dots.physicsBody?.dynamic = true
dots.physicsBody?.affectedByGravity = false
for i in 0..<2 {
dots.physicsBody?.categoryBitMask = UInt32(0x1 << index)
dots.physicsBody?.contactTestBitMask = UInt32(0x1 << index)
}
addChild(dots)
dots.size = CGSizeMake(45, 45)
dots.position = CGPointMake(150, 400)
dots.runAction(
SKAction.moveByX(0, y: -900,
duration: NSTimeInterval(11.5)))
}
}
Is there any way to gradually speed up either how fast they're falling or gradually change the waitForDuration so that over time it will produce a node every 3 sec, then 2 sec, then 1 sec and so forth?

This is completely doable! You just need to add some variables.
If you want to change how fast they fall then you need to make a variable like
Var droptime:NSTimeInterval = 11.5
Then in your "dropdot()" method you need to do two things.
At the beginning subtract or devide your droptime variable like...
Droptime -= 1
Then at the end when you generate the falling action make it
Duration: droptime
Instead of what it was before.
If you want to make the generation time be shorter then you need to make a function that you can trigger each time you want to make your action that the scene runs (like you did in the viewdidload) and edit it so that it has variable wait and triggers itself. Also you will need to self trigger it once in your didMoveToView method.
func controlMethod() {
waitdur -= 1
runAction(SKAction.repeatActionForever( SKAction.sequence([
SKAction.runBlock(generateDots),
SKAction.waitForDuration(waitdur),
SKAction.runBlock(controlMethod)
])))
}
Good luck!
So sorry for the formatting! I'm on mobile... Hopefully someone can fix it.

Related

Running SKActions on Multiple SKNodes in Sequence

I am creating a card game and having trouble having SKActions run in a sequence across multiple SK Objects (Nodes, Textures, Labels, etc.). Any help would be much appreciated! In the example below, I am trying to create a "dealing" motion. Where 1 card gets dealt, some time elapses, then the next card gets dealt. I tried using this, but with 52 cards, it's not exactly the simplest approach.
First, I tried creating an array of SKActions for each card. However, I don't believe I can run the below using one command based on my understanding of the documentation. Each action needs to be run against the specific Sprite Object as opposed to running a whole sequence of actions across multiple Sprite Objects.
let dealAction = SKAction[]()
for card in deck {
let move = SKAction....
dealAction.append(move)
}
run.SKAction.sequence(dealAction) // This will not work.
Then I tried this with the hope that the loop would complete each cards block of code before moving on to the next card. However, all the actions run at the same time. Now, I am a bit lost and I don't know exactly how to implement this efficiently. The only thing I could think of was creating a "timingIndex", where .2 seconds gets added to the wait time for each card. So even though they are all running at the same time, the wait time grows for each card. Not sure if this is the best way to approach the problem however and was hoping there was a more elegant solution.
for card in deck {
let move = SKAction....
let wait = SKAction.wait(forDuration: 1)
card.run(SKAction.sequence[move, wait])
}
// Possible Solution
let timingIndex = 0.2
for card in deck {
let move = SKAction....
let wait = SKAction.wait(forDuration: timingIndex)
card.run(SKAction.sequence[move, wait])
timingIndex += 0.2
}
import SpriteKit
import UIKit
let screenH = 100
let screenW = 50
class Player {
var handLocation: CGPoint
var pile = [Card]()
init() {
handLocation = CGPoint(x: 100, y: 583)
}
}
class Card: SKSpriteNode {
}
struct Game {
var player1 = Player()
var player2 = Player()
var deck = [Card]()
func dealDeck() {
for card in deck {
player1.pile.append(card) // This would theoretically loop through both players
card.position = CGPoint(x: screenW / 2, y: screenH / 2)
let move = SKAction.move(to: player1.handLocation, duration: 1)
card.run(move)
}
}
}
This worked! Worried about the use of this overall and if there exists a better solution still, but for the time being, this worked.
// Possible Solution
let timingIndex = 0.2
for card in deck {
let move = SKAction....
let wait = SKAction.wait(forDuration: timingIndex)
card.run(SKAction.sequence[move, wait])
timingIndex += 0.2
}

Understanding Spritekit run block, variable not as expected

I'm looking for some help understanding why my speedToPoint variable is always 1 when it gets to the wait action.
This is part of an SKSpriteNode extension i wrote for making a bird fly to a random point in a predefined rectangle. The speedToPoint is also randomized between 1 and 4 and used as the duration for the moveTo action. However, i also need to use that TimeInterval for my wait block in the action sequence.
speedToPoint is indeed being randomized in the run block (i've confirmed). How can i use that same randomized number in the wait block in the next part of the sequence?
var speedToPoint:TimeInterval = 1
self.run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run{
speedToPoint = TimeInterval(Globals.sharedInstance.randomF(min: 1, max: 4))
var pointX = Globals.sharedInstance.randomF(min: left,max: right)
let pointY = Globals.sharedInstance.randomF(min: top,max:bottom)
while abs(self.position.x.distance(to: pointX)) < 200 {
pointX = Globals.sharedInstance.randomF(min: left,max: right)
}
self.xScale = pointX < self.position.x ? -1 : 1
self.run(SKAction.move(to: CGPoint(x:pointX,y:pointY),duration: speedToPoint))
},
SKAction.wait(forDuration: speedToPoint)])
),withKey: "inFrame")
To clarify, what i'm really tring to do is have the bird fly to a point, once it's arrived at that point, fly to another point. I'm still wrapping my heard around action sequences and whether or not they actually wait for completion to move on. Which from what i've read, they do, but not for any move actions. That's why the wait is in there. Perhaps there is another way?
An SKAction, once created, can't be modified, and it is meant to be reused eg. you can't modify the duration parameter, or change other passed parameters. This means that you have to re-create it if you need it changed. Of course you can change the speed property of an existing action, or you can pause the action but that's pretty much it when it comes to modifying the existing action.
To solve your issue, you could do next:
1) Create an action which moves a sprite to a specific location
2) Once the action is completed, you create a new one which does the same
you can do this using recursion, like this (just copy & paste the code to see how it works):
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
private var bird = SKSpriteNode(color: .purple, size: CGSize(width: 100, height: 100))
override func didMove(to view: SKView) {
addChild(bird)
recursive()
}
func recursive(){
let sequence = SKAction.sequence([
SKAction.move(to: self.randomPoint(inRect: self.frame), duration: TimeInterval(self.random(between: 1, and: 3))),
SKAction.run({[unowned self] in NSLog("Block executed"); self.recursive()})
])
self.bird.run(sequence, withKey: "aKey")
}
func random(between minimum: CGFloat, and maximum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(minimum - maximum) + min(minimum, maximum)
}
func randomPoint(inRect rect:CGRect)->CGPoint{
let x = random( between: -rect.size.width / 2.0 , and: rect.origin.x + rect.size.width/2.0)
let y = random(between: -rect.size.height / 2.0 , and: rect.origin.y + rect.size.height/2.0)
return CGPoint(x: x, y: y)
}
}
To stop this action, remove the key associated with it.

Choosing to spawn different SKSpriteNodes

I am making a game and I have objects which fall from the top of the screen to the bottom. I want to spawn choose between the objects and drop one of them. I currently the drop all at the same time.
func ShapePicker() -> SKSpriteNode{
let shapeArray = [purpleOctagon, coin, greenTriangle, orangeHexagon]
let MaxValue = self.size.width / 2 - 200
let MinValue = self.size.width / 3 * 0.95
let rangeMax = UInt32(MaxValue)
let rangeMin = UInt32(MinValue)
purpleOctagon.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(purpleOctagon)
greenTriangle.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(greenTriangle)
coin.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(coin)
return shapeArray[Int(arc4random_uniform(UInt32(shapeArray.count)))]
}
I would like the program to randomly .addChild because right now it just puts them on the screen.
Your code implies that you want all of them to be on the screen, and then one randomly drops... So you do want to continue to .addChild. What you want, is for them to NOT drop all at once.
So, you need to change the .physicsBody.pinned to true to keep them at the top of the screen.
Then, in your update() you can check for how much time has passed, etc, and after a certain # of seconds you can do an arc4random_uniform and use that result to change the .pinned property of one of the nodes (thus causing that one and that one only to fall).
So, if the coin is 0, triangle is 1, and octagon is 2, then, in your .update keep track of the time elapsed and after say 3 seconds, do a random check 0-2, and that check will perform:
switch result {
case 0: // coin
coin.physicsBody?.pinned = false // makes it drop
case 1: // triangle
...
Just make sure that your nodes are in the proper scope so that you can do the logic in update()
If I read your Q wrong, and you only want to spawn and then drop just one, then you would still need the above switch statement, but instead of changing physicsBody you would do .addChild instead.
so inside of your func would be more like:
// This can be global or inside of your GameScene:
var myGlobalCurrentTime: CFTimeInterval
override func update(currentTime: CFTimeInterval) {
myGlobalCurrentTime = myTimerUpdateTime(currentTime)
func myDropFunc() {
... // Initialize your nodes
let result = myRandomNumber(3)
switch result {
case 0:
coin.position
= CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax),
y: self.size.height)
self.addChild(coin)
case 1:
...
}
}
// Execute the func:
myDropFunc()
}
I'm still a bit confused by your code and question, so please clarify in the comments so I can update this answer if needed.
You can move the addChild call out of this function, it returns a SKSpriteNode, which you can then add.
let sprite = ShapePicker()
addChild(sprite)
Or, just add the one randomly chosen and return it. You don't need to addChild nodes that you're not planning on using

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

Swift - SpriteKit - Update SKAction.waitForDuration while running forever

I am trying to update the waitForDuration action in a sequence that is running forever.
override func didMoveToView(view: SKView) {
//code
runAction(SKAction.repeatActionForever(runSeq()))
}
func runSeq() -> SKAction{
var difficulty: CGFloat = 0.75
let updateAction = SKAction.runBlock({
self.runCount++
self.runCount %= 4
println(self.runCount)
if self.runCount == 0 {
difficulty -= 0.1
}
if self.children.count > 51{
println("You loose")
}else{
self.scoreLabel.text = String(format: "Score: %i", self.score)
}
})
let createAntAction = SKAction.runBlock({self.createAnt()})
var wait = SKAction.waitForDuration(NSTimeInterval(difficulty))
let seq = SKAction.sequence([createAntAction, wait, updateAction])
return seq
}
difficulty is the value that I want, and every 4 loops I want it to decrease by a certain amount. Currently, the starting value remains the same, and doesn't change, even though I am changing the difficulty value, and recalling the sequence of actions every time.
This is happening because wait is already established as .75, and nothing is changing that. SKAction.waitForDuration(NSTimeInterval(difficulty)) is not looking for a reference to difficulty, only a value, so you need to rethink how you want to code this.