Increasing/Decreasing waitForDuration( )? - swift

I am wondering if its possible to increase withForDuration( ) with a variable. Heres how I am trying to do it in short. This is also SpriteKit, and this is my first time with it so I am still a little unsure. While this snippet of code works to change the float it doesn't actually change the waitForDuration(difficulty)
var timer: NSTimer!
var difficulty = 1.0
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
player.position = CGPoint(x: size.width * 0.5, y: size.height * 0.25)
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2)
player.physicsBody?.dynamic = true
addChild(player)
physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "increaseDifficulty", userInfo: nil, repeats: true)
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addEnemy),
SKAction.waitForDuration(difficulty)
])
))
}
func increaseDifficulty() {
difficulty -= 0.1
}

One way to do this is to use the completionHandler of the runAction function. For example.
func addEnemyWithChangingDifficulty() {
let waitAction = SKAction.waitForDuration(difficulty)
let addEnemyAction = SKAction.runBlock { () -> Void in
self.addEnemy()
}
runAction(SKAction.sequence([addEnemyAction,waitAction]), completion: { () -> Void in
self.addEnemyWithChangingDifficulty()
})
}
Another way would be to use the update function to track the waitDuration.
var lastAddedTime : CFTimeInterval = 0
override func update(currentTime: CFTimeInterval) {
if currentTime - lastAddedTime > CFTimeInterval(difficulty) {
addEnemy()
lastAddedTime = currentTime
}
}

You're not getting your intended effect because the value of 'difficulty' is captured in the first declaration of your SKAction and it never refers to the outside declaration again.
Two ways to solve this:
Instead of doing SKAction.repeatActionForever(), dump the SKAction.sequence() in increaseDifficulty. You'll have to tweak the numbers a bit to make it work, but NSTimer is already running on repeat, you can use that instead.
The second way (not 100% sure about this) is to put the '&' symbol in front of difficulty. This passes difficulty by reference rather by value, which should give you the intended effect. I'm just not sure if Swift allows this, but in C++ that's what we can do.

Related

Animated curve line in Swift 3

I want to draw some bezier lines and I want to animate them with a wave effect,
Example
Do you have some ideas about how I can do this ? Bezier line is it the best method to do it ?
I found only 2 libs for this, but they are not really useful for what I need, I try to modify the code of one lib, unfortunately without success https://github.com/yourtion/YXWaveView
I found this lib, https://antiguab.github.io/bafluidview/ which does the work, but it written in obj-c, maybe you know something like this in swift
You can use a display link, a special kind of timer optimized for screen refresh rates, to change the path that is being rendered. The handler for the display link should calculate the amount of time that has elapsed and modify the path to be rendered accordingly. You can either use a CAShapeLayer to render the path, or you can use a custom UIView subclass. The shape layer is probably easier:
class ViewController: UIViewController {
private weak var displayLink: CADisplayLink?
private var startTime: CFTimeInterval = 0
/// The `CAShapeLayer` that will contain the animated path
private let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 3
return shapeLayer
}()
// start the display link when the view appears
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.layer.addSublayer(shapeLayer)
startDisplayLink()
}
// Stop it when it disappears. Make sure to do this because the
// display link maintains strong reference to its `target` and
// we don't want strong reference cycle.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopDisplayLink()
}
/// Start the display link
private func startDisplayLink() {
startTime = CACurrentMediaTime()
self.displayLink?.invalidate()
let displayLink = CADisplayLink(target: self, selector:#selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
}
/// Stop the display link
private func stopDisplayLink() {
displayLink?.invalidate()
}
/// Handle the display link timer.
///
/// - Parameter displayLink: The display link.
#objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let elapsed = CACurrentMediaTime() - startTime
shapeLayer.path = wave(at: elapsed).cgPath
}
/// Create the wave at a given elapsed time.
///
/// You should customize this as you see fit.
///
/// - Parameter elapsed: How many seconds have elapsed.
/// - Returns: The `UIBezierPath` for a particular point of time.
private func wave(at elapsed: Double) -> UIBezierPath {
let elapsed = CGFloat(elapsed)
let centerY = view.bounds.midY
let amplitude = 50 - abs(elapsed.remainder(dividingBy: 3)) * 40
func f(_ x: CGFloat) -> CGFloat {
return sin((x + elapsed) * 4 * .pi) * amplitude + centerY
}
let path = UIBezierPath()
let steps = Int(view.bounds.width / 10)
path.move(to: CGPoint(x: 0, y: f(0)))
for step in 1 ... steps {
let x = CGFloat(step) / CGFloat(steps)
path.addLine(to: CGPoint(x: x * view.bounds.width, y: f(x)))
}
return path
}
}
The only tricky part is writing a wave function that yields a UIBezierPath for a particular time and yields the desired effect when you call it repeatedly as time passes. In this one, I'm rendering a sine curve, where the amplitude and the offset vary based upon the time that has elapsed at the point that the path is generated, but you can do whatever you want in your rendition. Hopefully this illustrates the basic idea.
The above code yields:

Changing the time interval of a Timer in swift spritekit

So, I have this timer that is setup to run a specific function (which are both shown below) on a time interval variable called 'frequency' when I try and change the timeinterval variable frequency to a lower number based on the score number it doesn't seem to change the rate at which it fires it just seems to fire at the same time even if the frequency is changed to a lower number
override func didMove(to view: SKView) {
Timer.scheduledTimer(timeInterval: frequency, target: self, selector: #selector(GameScene.spawnFallingOjects), userInfo: nil, repeats: true)
}
func spawnFallingOjects() {
if (GameState.current == .playing || GameState.current == .blackstone) {
guard usingThirdEye == false else { return }
let scoreLabel = childNode(withName: "scoreLabel") as! Score
let lane = [-100, -50 , 0, 50, 100]
let duration = 3.0
switch scoreLabel.number {
case 0...50:
frequency = 6.0
print("frequency has changed: \(frequency)")
case 51...100:
frequency = 4.5
print("frequency has changed: \(frequency)")
case 101...200000:
frequency = 1.1
print("frequency has changed: \(frequency)")
default:
return
}
let randomX = lane[Int(arc4random_uniform(UInt32(lane.count)))]
let object:Object = Object()
object.createFallingObject()
object.position = CGPoint(x: CGFloat(randomX), y: self.size.height)
object.zPosition = 20000
addChild(object)
let action = SKAction.moveTo(y: -450, duration: duration)
object.run(SKAction.repeatForever(action))
}
}
How do I make the timer fire faster when the frequency number changes to a lower number? should I recreate the timer at the end of the function?
You should actually avoid using Timer, Sprite kit has its own time functionality, and Timer does not work well with it and is a real pain to manage.
Instead, use SKAction's to wait and fire:
let spawnNode = SKNode()
override func didMove(to view: SKView) {
let wait = SKAction.wait(forDuration:frequency)
let spawn = SKAction.run(spawnFallingObjects)
spawnNode.run(SKAction.repeatForever(SKAction.sequence([wait,spawn])))
addChild(spawnNode)
}
Then to make it faster, just do:
switch scoreLabel.number {
case 0...50:
spawnNode.speed = 1
print("speed has changed: \(spawnNode.speed)")
case 51...100:
spawnNode.speed = 1.5
print("speed has changed: \(spawnNode.speed)")
case 101...200000:
spawnNode.speed = 2
print("speed has changed: \(spawnNode.speed)")
default:
return
}
The timeInterval property of Timer is a readonly property. (And your code is not trying to write a new frequency into the property...)
should I recreate the timer at the end of the function?
Nearly yes. Just you have no need to do it at the end.
With changing your method header like this:
func spawnFallingOjects(_ timer: Timer) {
You can access the fired Timer through the parameter timer, so you may need to write something like this just after switch scoreLabel.number {...}:
if frequency != timer.timeInterval {
//Invalidate old Timer...
timer.invalidate()
//And then allocate new one
Timer.scheduledTimer(timeInterval: frequency, target: self, selector: #selector(GameScene.spawnFallingOjects), userInfo: nil, repeats: true)
}
You can modify the fireDate property of an existing Timer (in case which still isValid), but recreating a Timer instance is not a heavy operation (comparing to creating an SKSpriteNode instance), so recreating a new Timer seems to be a little bit easier.

How to implement a SpriteKit timer?

I am currently trying to implement a timer for my sprite kit game, but I don't get it working. The initial value of the timer always remains the same.
I am assuming I need to update the label somehow/somewhere, but I don't know HOW and WHERE :? I don't get the point. Any ideas?
Here is my code within my GameScene Class
let levelTimerLabel = SKLabelNode(fontNamed: "Chalkduster")
var levelTimerValue: Int = 500
var levelTimer = NSTimer()
func startLevelTimer() {
levelTimerLabel.fontColor = SKColor.blackColor()
levelTimerLabel.fontSize = 40
levelTimerLabel.position = CGPoint(x: size.width/2, y: size.height/2 + 350)
addChild(levelTimerLabel)
levelTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: Selector("levelCountdown"), userInfo: nil, repeats: true)
levelTimerLabel.text = String(levelTimerValue)
}
func levelCountdown(){
levelTimerValue--
}
I would stick to SKActions for these kind of tasks in SpriteKit due to fact that NSTimer is not affected by scene's, or view's paused state, so it might lead you into troubles. Or at least, it will require from you to implement a pause feature in order to pause your timers in certain situations, like when user pause the scene, or receive a phone call etc. Read more here about SKAction vs NSTimer vs GCD for time related actions in SpriteKit.
import SpriteKit
class GameScene: SKScene {
var levelTimerLabel = SKLabelNode(fontNamed: "ArialMT")
//Immediately after leveTimerValue variable is set, update label's text
var levelTimerValue: Int = 500 {
didSet {
levelTimerLabel.text = "Time left: \(levelTimerValue)"
}
}
override func didMoveToView(view: SKView) {
levelTimerLabel.fontColor = SKColor.blackColor()
levelTimerLabel.fontSize = 40
levelTimerLabel.position = CGPoint(x: size.width/2, y: size.height/2 + 350)
levelTimerLabel.text = "Time left: \(levelTimerValue)"
addChild(levelTimerLabel)
let wait = SKAction.waitForDuration(0.5) //change countdown speed here
let block = SKAction.runBlock({
[unowned self] in
if self.levelTimerValue > 0{
self.levelTimerValue--
}else{
self.removeActionForKey("countdown")
}
})
let sequence = SKAction.sequence([wait,block])
runAction(SKAction.repeatActionForever(sequence), withKey: "countdown")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Stop the countdown action
if actionForKey("countdown") != nil {removeActionForKey("countdown")}
}
}
Sounds like you need to update the label everytime levelTimerValue is changed. The easiest way would be something like this.
func levelCountdown(){
levelTimerValue--
levelTimerLabel.text = String(levelTimerValue)
}

Repeating an action forever with a global function

I have a rectangle that needs to be constantly moving up, but is also declared globally like so so that I can call it in multiple places:
var obstacle = SKNode!
override func didMoveToView {
obstacle = rectangle()
}
func rectangle() -> SKNode {
let rect = SKSpriteNode(imageNamed: "Rectangle#x2")
rect.size = CGSizeMake(30, 30)
rect.position = CGPointMake(210, -250)
rect.physicsBody?.categoryBitMask = PhysicsCatagory.littleRect
rect.physicsBody?.contactTestBitMask = PhysicsCatagory.bigRect
rect.physicsBody?.collisionBitMask = 0
rect.physicsBody = SKPhysicsBody(rectangleOfSize: rect.size)
rect.physicsBody?.dynamic = true
rect.physicsBody?.affectedByGravity = false
rect.runAction(
SKAction.moveByX(0, y: 1200,
duration: NSTimeInterval(6.5)))
addChild(rect)
return rect
}
When I attempt to run it as an action repeating forever like so, i get the error "cannot convert value of type SKNode to argument runBlock" :
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(rectangle),
SKAction.waitForDuration(4.0)])))
So is there a way to declare this sort of action for a function set up like this? Thank you in advance.
First of all, this var obstacle = SKNode! will produce an error. You should declare an implicitly unwrapped optional like this:
var obstacle:SKNode!
About the main question (without analyzing the logic of what code actually does,)...You are passing an instance of SKNode class to +runBlock: method (which accepts a closure), thus the error. To fix this, you have to pass a closure, like this:
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock({[unowned self] in self.rectangle()}),
SKAction.waitForDuration(4.0)])))
}

Varying value inside runAction() not changing

I have a run action loop that spawns an enemy then waits and spawns another. The intention is that the higher the score the quicker the enemies spawn.
But the current code has the enemies spawn at the same rate no matter what. I update the rate in the override func update(currentTime: NSTimeInterval) method all the time, so I don't know what's wrong.
override func update(currentTime: NSTimeInterval){
spawnRate = 2.0-(0.1*(Double)(((Double)(Score))/(10.0)))
if(spawnRate < 0.5){
spawnRate = 0.5
}
}
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addEnemy),
SKAction.waitForDuration(spawnRate)
])
))
}
What is happening currently is that you've:
stored duration paramter inside an action
reused that action within sequence over and over again
So nothing actually changes. To solve this, one way would be to use recursive call and change duration parameter each time:
import SpriteKit
class GameScene: SKScene {
let shape = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 20, height: 20))
var delay:NSTimeInterval = 1.0
override func didMoveToView(view: SKView) {
recursiveMethod()
}
func recursiveMethod(){
let recursive = SKAction.sequence([
SKAction.waitForDuration(delay),
SKAction.runBlock({self.recursiveMethod();println("delay \(self.delay)")})
])
runAction(recursive)
}
override func update(currentTime: NSTimeInterval) {
if(delay > 0.2){delay = delay - 0.001}
}
}
The problem is that This:
SKAction.sequence([
SKAction.runBlock(addEnemy),
SKAction.waitForDuration(spawnRate)
])
is made one's and then just repeated forever.
try to put the code in a
runAction(SKAction.repeatActionForever(runblock(spawnAndWait()))
and then make a seperate func.
spawnAndWait() { // put here the code for spawning the enemy and waiting spawnrate duration.
b.t.w
Put the code that updates the spawnRate where the score is increased. Because that is more efficient. then in the update method because that is constantly called.