Changing score with a delay - swift

I want to create this effect, that the score is changing with a delay between every number, like the score on the game over screen in Flappy Bird.
In this example, it should start counting when I touch the screen.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
// Global declaration of objects
var scoreLabel = SKLabelNode()
var score:Int = 15
override func didMoveToView(view: SKView) {
/* Setup your scene here */
scoreLabel = SKLabelNode(fontNamed: "RubberBiscuitBold")
scoreLabel.fontSize = 50
scoreLabel.fontColor = SKColor.blackColor()
scoreLabel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
scoreLabel.zPosition = 1000
self.addChild(scoreLabel)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for var i = 0; i <= score; i++ {
scoreLabel.runAction(SKAction.waitForDuration(1))
scoreLabel.text = "\(i)"
print("\(i)")}
}

try something like this:
for var i = 0; i <= 10; i++ {
let seconds: Double = 0.5
let count = i
let delay = seconds * Double(NSEC_PER_SEC) // nanoseconds per seconds
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(i)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
print("\(count)")
scoreLabel.text = "\(count)" })
}

Alternatively, with a recursive function definition it gets a bit more compact (and hopefully usable)
func countUp(start: Int, end: Int, delay: Double) {
if start <= end {
let del = delay * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(del))
dispatch_after(time, dispatch_get_main_queue()) {
print("\(start)") // Your rendering code here
countUp(start + 1, end: end, delay: delay)
}
} else {
let del = delay * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(del))
dispatch_after(time, dispatch_get_main_queue()) {
print("I'm done!") // Your continuation code here
// Please not that you should, for clarity and maintenance, wrap your delayed execution code inside a function.....
}
}
}
countUp(10, end: 20, delay: 1)

Just an idea here.
What if you create an additional node between the pipes that are a bit smaller.
Make it so that once they collide it ups your score.
That would also bring you the delay you want.

Related

How to make SKAction Oscillation move faster and faster?

Looking to make an SKSprite (road in this case) move back and forth across the screen. I'd like for the road to move faster and faster every second. I implemented the following but it only sets the oscillation once and never gets updated. Is there any way I can make it oscillate faster and faster? Or is there a better way of doing this using something else?
var wallSpeedMultiplier = 1.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if !gameStarted {
titleView.removeFromParent()
tapToStart.removeFromParent()
gameStarted = true
let wait = SKAction.wait(forDuration: 1.0)
let block = SKAction.run({
[unowned self] in
wallSpeedMultiplier = wallSpeedMultiplier - 0.01
})
let sequence = SKAction.sequence([wait, block])
run(SKAction.repeatForever(sequence), withKey: "timer")
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: (30*wallSpeedMultiplier), midPoint: road!.position)
road!.run(SKAction.repeatForever(oscillate))
road!.run(SKAction.moveBy(x: size.width, y: 0, duration: 5))
}
}
extension SKAction {
static func oscillation(amplitude a: CGFloat, timePeriod t: CGFloat, midPoint: CGPoint) -> SKAction {
let action = SKAction.customAction(withDuration: Double(t)) { node, currentTime in
let displacement = a * sin(2 * π * currentTime / t)
node.position.x = midPoint.x + displacement
}
return action
}
}

Swift Countdown Function

I am attempting to create a countdown timer for a game using SpriteKit, but whenever I try to run countDown(), my game freezes. I am pretty sure my logic is correct here. I do not know what is going on.
func countDown(){
let countDownWait = SKAction.wait(forDuration: 1.0)
repeat {
self.run(countDownWait){
self.countDownTime -= 1
}
} while (self.countDownTime > 0)
if self.countDownTime == 0{
self.runGameOver()
}
}
you can do some checking in the update func for time passed or use a SKAction to track time similar to what you were doing in your code
let someLabel = SKLabelNode()
func countdown() {
var offset: Double = 0
for x in (0...10).reversed() {
run(SKAction.wait(forDuration: offset)) {
someLabel.text = "\(x)"
if x == 0 {
//do something when counter hits 0
//self.runGameOver()
}
else {
//maybe play some sound tick file here
}
}
offset += 1.0
}
}
Here's how I solved this problem for my Swift/SpriteKit 'Breakout' application. I wanted a countdown from 5 to 1 onthe main game screen but before the ball started to move. I Added these functions and then a call to countdown(5) at the end of didMoveToView: Notice the ball.physicsBody!.applyImpulse(CGVectorMake(20, 20)) as the last step of endCountdownwhich starts the ball and effectively starts the game.
func countdown(count: Int) {
countdownLabel.horizontalAlignmentMode = .Center
countdownLabel.verticalAlignmentMode = .Baseline
countdownLabel.position = CGPoint(x: size.width/2, y: size.height*(1/3))
countdownLabel.fontColor = SKColor.whiteColor()
countdownLabel.fontSize = size.height / 30
countdownLabel.zPosition = 100
countdownLabel.text = "Launching ball in \(count)..."
addChild(countdownLabel)
let counterDecrement = SKAction.sequence([SKAction.waitForDuration(1.0),
SKAction.runBlock(countdownAction)])
runAction(SKAction.sequence([SKAction.repeatAction(counterDecrement, count: 5),
SKAction.runBlock(endCountdown)]))
}
func countdownAction() {
count--
countdownLabel.text = "Launching ball in \(count)..."
}
func endCountdown() {
countdownLabel.removeFromParent()
ball.physicsBody!.applyImpulse(CGVectorMake(20, 20))
}
Try this solution to run countdown...
var seconds = 7200
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
runTimer()
}
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(self.updateTimer)), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
if seconds < 1 {
timer.invalidate()
//Send alert to indicate time's up.
} else {
seconds -= 1
timerLabel.text = timeString(time: TimeInterval(seconds))
}
}
func timeString(time:TimeInterval) -> String {
let hours = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
return String(format:"%02i:%02i:%02i", hours, minutes, seconds)
}
Have you considered using a timer? Here is a good tutorial. It might take you 20 minutes to go through it or so, but it goes into detail about starting, stopping, pausing, and more for timers.

How do I stop my timers at 0 automatically in SpriteKit?

Keep in mind this is in Sprite Kit. Basically I want a way to have my in-game upgrades on a timer that allows the user to get the upgrade after the timer has finished. The user would touch the button once to start the timer, and once it gets to zero, it stops itself. I can't get it to stop, however.
import Foundation
import SpriteKit
var timer1Cycle = 0
var timer1Time = 10000
var timer1 = Timer()
var timer1IsOn = false
class SkillPointsScene: SKScene{
func updateTimer1() {
timer1Time -= 1
timer1Label.text = " \(timer1Time) :Kepler-22b units"
}
func startTimer1(){
if timer1IsOn == false {
timer1 = Timer.scheduledTimer(timeInterval: 0.0004, target: self, selector: #selector(SkillPointsScene.updateTimer1), userInfo: nil, repeats: true)
timer1IsOn = true
}
}
func stopTimer1() {
timer1.invalidate()
timer1Time = 0
timer1Label.text = "Research Complete"
timer1IsOn = false
}
Here is the image button to start counting down from 10000:
let oneLifeImage = SKSpriteNode(imageNamed: "gun1")
let timer1Label = SKLabelNode(fontNamed: "theBoldFontt")
// runs as soon as the screen loads every time
override func didMove(to view: SKView) {
if timer1Time <= 0 {
self.stopTimer1()
}
let background = SKSpriteNode(imageNamed: "background")
background.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
background.zPosition = 0
self.addChild(background)
timer1Label.text = " "
timer1Label.fontSize = 35
timer1Label.fontColor = SKColor.white
timer1Label.position = CGPoint(x: self.size.width*0.5, y: self.size.height*0.82)
timer1Label.zPosition = 100
self.addChild(timer1Label)
oneLifeImage.setScale(1)
oneLifeImage.position = CGPoint(x: self.size.width/2, y: self.size.height*0.75)
oneLifeImage.zPosition = 2
self.addChild(oneLifeImage)
And here is where the button is touched:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let wait = SKAction.wait(forDuration: 1.5)
if oneLifeImage.contains(pointOfTouch){
if timer1Cycle == 0 {
timer1Time = 10000
}
if timer1Cycle == 1 {
timer1Time = 25000
}
if timer1IsOn == false {
startTimer1()
timer1Cycle -= 1
}
I get it to start the count down, but it never stops on its own. I don't know what to do now; I can't find any information on this because it is in Swift 3 with Sprite Kit, not the normal single view that everybody seems to know how to use.

Swift: Attemped to add a SKNode which already has a parent:

I know why I'm getting that error, but I can't figure out a way around it. I'm trying to have objects come appear and then be removed and the player should try to tap them before they're removed, but everytime the next node is about to appear it crashes. If i declare it inside its func then it all comes out but I can't tap on it...
Code:
let step = SKSpriteNode()
override func didMoveToView(view: SKView) {
physicsWorld.contactDelegate = self
backgroundColor = UIColor.feelFreeToColor()
self.color = self.randomNumbersInt(3)
self.showBars()
self.showScore()
let spawn = SKAction.runBlock {
//self.color = self.randomNumbersInt(3)
self.showSteps()
}
let delay = SKAction.waitForDuration(1.5)
let spawnDelay = SKAction.sequence([spawn , delay])
let spawnDelayForever = SKAction.repeatActionForever(spawnDelay)
self.runAction(spawnDelayForever)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
var location = touch.locationInNode(self)
if self.nodeAtPoint(location) == step {
self.score += 1
}
}
}
func showSteps() {
let createSteps = SKAction.moveByX(0, y: -self.frame.height - 30 , duration: 10)
let removeSteps = SKAction.removeFromParent()
step.color = colors[randomNumbersInt(3)]!
step.size = CGSize(width: 275, height: 30)
step.position = CGPoint(x: self.frame.width * 0.5, y: self.frame.height * 0.75)
step.physicsBody = SKPhysicsBody(rectangleOfSize: step.size)
step.physicsBody?.categoryBitMask = Config.PhysicBodyType.Steps.rawValue
step.physicsBody?.affectedByGravity = false
step.physicsBody?.dynamic = false
step.zPosition = 1
step.runAction(SKAction.repeatActionForever(SKAction.sequence([createSteps, removeSteps])))
addChild(step)
}
In your showSteps function, declare the step node inside it, not at the top of your code, and also give it a name:
func showSteps() {
let step = SKSpriteNode()
...
step.name = "step"
step.color = colors[randomNumbersInt(3)]!
// etc
}
In your touchesBegan method, you have this if statement:
if self.nodeAtPoint(location) == step {
self.score += 1
}
You want to remove that node that you have hit, but now you can just check the name property like so:
if self.nodeAtPoint(location)?.name == "step" {
self.nodeAtPoint(location)?.removeFromParent()
self.score += 1
}
Please note that I am not super fluent in Swift, but I think you will probably need the ? in your if statement, as it might not exist (such as if you didn't tap on a specific node). Somebody more familiar with Swift is free to correct me.

How do you trigger actions with NSTimer in Spritekit?

I'm trying to get my "Player" (A circle in the middle) to increase in size once the screen is touched by running a timer.
Once the timer is over 0 seconds, it increases in size. Once the timer is over 3 seconds, it decreases to its original scale size and once the timer is over 7 seconds, it resets and this repeats forever.
What am I doing wrong?
import SpriteKit
class GameScene: SKScene {
var Center = SKSpriteNode()
var Player = SKSpriteNode()
var timer = NSTimer()
var seconds = 0
override func didMoveToView(view: SKView) {
Center = SKSpriteNode(imageNamed: "Center")
Center.size = CGSize(width: 80, height: 80)
Center.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(Center)
Player = SKSpriteNode(imageNamed: "Player")
Player.size = CGSize(width: 80, height: 80)
Player.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
self.addChild(Player)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
timer = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: #selector(GameScene.playerScaleUp), userInfo: nil, repeats: true)
}
func playerScaleUp(){
if seconds > 0{
Player.runAction(SKAction.scaleBy(4, duration: 2))
}
}
func playerScaleDown(){
if seconds > 3{
Player.runAction(SKAction.scaleBy(-4, duration: 2))
}
}
func resetScale(){
if seconds > 7{
timer.invalidate()
}
}
override func update(currentTime: CFTimeInterval) {
}
}
This can be done in a few different ways:
1) Using SKActions (which my example uses)
2) Using update: method and its passed currentTime parameter
3) Using NSTimer which I wouldn't recommend because it is not affected by scene's, view's or node's paused state, so it can lead you into troubles in the future. Read more in this StackOverflow answer
4) Well, probably some more, but I will stop here.
My example uses SKAction. This means that I don't really check how much time is passed, but rather I organize actions into sequences (where actions are executed sequentially) and into groups (where actions are organized parallel). Means I use SKActions like I am playing with LEGO's :)
Here is the code ...I left debugging code intentionally, because it can help you to learn how you can use SKActions in different situations.
class GameScene: SKScene, SKPhysicsContactDelegate {
let player = SKSpriteNode(color: .blackColor(), size: CGSize(width: 50, height: 50))
var timePassed = 0.0 {
didSet {
self.label.text = String(format: "%.1f",timePassed)
}
}
let label = SKLabelNode(fontNamed: "ArialMT")
override func didMoveToView(view: SKView) {
/* Debugging - Not really needed, I added it just because of a better example */
let wait = SKAction.waitForDuration(0.1)
let updateLabel = SKAction.runBlock({[unowned self] in self.timePassed += wait.duration})
self.label.position = CGPoint(x: frame.midX, y: frame.midY+100.0)
addChild(self.label)
let timerSequence = SKAction.sequence([updateLabel, wait])
self.runAction(SKAction.repeatActionForever(timerSequence), withKey: "counting")
//This is used later, at the end of scaleUpAndDownSequence to reset the timer
let resetTimer = SKAction.runBlock({[unowned self] in self.timePassed = 0.0})
/* End Debugging */
self.player.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(self.player)
let waitBeforeScaleToOriginalSize = SKAction.waitForDuration(3.0)
let waitBeforeRestart = SKAction.waitForDuration(4.0)
let scaleUp = SKAction.scaleTo(2.0, duration: 1)
let scaleDown = SKAction.scaleTo(1.0, duration: 1)
let scaleUpGroup = SKAction.group([waitBeforeScaleToOriginalSize, scaleUp])
let scaleDownGroup = SKAction.group([scaleDown, waitBeforeRestart])
//One cycle, scale up, scale down, reset timer
let scaleUpAndDownSequence = SKAction.sequence([scaleUpGroup, scaleDownGroup, resetTimer])
let loop = SKAction.repeatActionForever(scaleUpAndDownSequence)
self.player.runAction(loop, withKey: "scalingUpAndDown")
}
}
So here, I have two groups of actions:
1) scaleUpGroup
2) scaleDownGroup
scaleUpGroup group of actions has two actions in it: a scaleUp action, and an action which says how much to wait before the scale down action should occur. Because we want scaling up to happen immediately, we run it in parallel with the waitBeforeScaleToOriginalSize.
Same logic goes for scaleDownGroup. When scaleUpGroup is finished (its duration is determined by the longest action in the group) we start scaleDownGroup which scales down the player to its default size, and waits certain amount of time to repeat the whole thing.
Here is the result:
I start an animation on touch ( I've removed that code) and as you can see, scale up animation starts immediately, then after 3 seconds the player get scaled down to its original size, and after 4 seconds the whole animation repeats (player gets scaled up again etc).