I have a wall that generates and then moves Left (Pulls) and I'm curious on if I can somehow make it so that overtime it gets faster and faster. Is this possible?
Here is the code I'm using to pull the "wall":
func startMoving() {
let moveLeft = SKAction.moveByX(-300, y: 0, duration: 0.35)
runAction(SKAction.repeatActionForever(moveLeft))
}
This is what Generates the walls in case you need to know:
var generationTimer: NSTimer?
func startGeneratingWallsEvery(seconds: NSTimeInterval) {
generationTimer = NSTimer.scheduledTimerWithTimeInterval(seconds, target: self, selector: "generateWall", userInfo: nil, repeats: true)
This line of code is what starts the generator
wallGenerator.startGeneratingWallsEvery(0.5)
everything works, but I want to know how to make it so it starts off slow then gets faster overtime (makes it harder).
One option is to create a variable for the duration of the action and keep a counter that increments every time a wall is pulled. After a certain number of walls have been moved (5 in this example), you can reduce the duration by a certain amount.
var duration: NSTimeInterval = 0.35
var counter: NSUInteger = 0
func startMoving(duration: NSTimeInterval) {
let moveLeft = SKAction.moveByX(-300, y: 0, duration: duration)
runAction(SKAction.repeatActionForever(moveLeft))
counter += 1
if counter % 5 == 0 {
duration -= 0.01
}
}
I'm not sure what will happen if the SKAction duration is negative, so you may need to guard against that.
Related
I'm making a game in Spritekit, and there's a timer going down that keeps changing in the update function. Sometimes in my game, I change the speed of the physics world, and when that happens I would like for the timer that's going down to also change speed.
To be more specific, I'm changing the speed to 0.5.
So I'd like, in that period for the time to run down at half the speed it's going by.
Here's my code for running down the time. lastSwitch is called in the didMove function.
var lastSwitch = CFAbsoluteTimeGetCurrent()
override func update(_ currentTime: TimeInterval) {
let currentTime = CFAbsoluteTimeGetCurrent()
let timePassed = currentTime - lastSwitch
let timeLeft = totalTime - timePassed
//... bla bla
time.text = "\(timeLeft)"
}
nodes (scene is a node) have its own speed property, so just set that (self.speed = 0.5 for half speed). BTW, I am not sure what you are doing, but I would recommend not doing timing the way you are doing it, since scene time is different then real world time.
Instead, use a custom action:
func startTimer()
{
let duration = 1
let totalGameTime = SKAction.customAction(withDuration:duration)
{
[unowned self] (node,elapsedTime) in
let timeLeft = duration - elapsedTime
self.time.text = "\(timeLeft)"
}
self.run(totalGameTime,withKey:"totalGameTime")
}
This way when you use the speed property, the timer slows down with it.
So let's say you set your game speed to 0.5. It will take 2 seconds to complete the action provided because you do duration / scene speed, so 1 / 0.5 which is 2.
This is probably somewhat of a stupid question, as I am a total newbie in coding, BUT:
I have just programmed a timer in swift which is working fine, but the next step of my little project is to make three buttons that will connect to the timer, that calculates the percentage of the total time that has elapsed. for example for a ball possession calculator with the buttons "Home" for when the home team has the ball, "Away" for when the away team has the ball, and finally a button "not in play" for when the ball is out of play, which will pause the calculation of possession. Is there someone that can help me with connecting the buttons to the timer, and helping me with the code to calculate the percentage?
I have created two labels that will show the percentage. I hope someone can help this novice :) Thanks!
the timer:
#IBOutlet weak var lbl: UILabel!
var timer = Timer()
var minutes: Int = 0
var seconds: Int = 0
var timerIsOn = false
var stopwatchString: String = ""
#IBAction func start(_ sender: UIButton)
{
if timerIsOn == false {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: true)
}
timerIsOn = true
}
#IBAction func reset(_ sender: UIButton)
{
timer.invalidate()
seconds = 0
lbl.text = ("00:00")
timerIsOn = false
}
func action()
{
seconds += 1
lbl.text = String(seconds)
if seconds == 60 {
minutes += 1
seconds = 0
}
let secondsString = seconds > 9 ? "\(seconds)" : "0\(seconds)"
let minutesString = minutes > 9 ? "\(minutes)" : "0\(minutes)"
stopwatchString = "\(minutesString):\(secondsString)"
lbl.text = stopwatchString
Ok since you added more information on how you would like to do it i'll try to explain a few things. First of all i wouldn't be using Timer, since Timers in swift are meant for countdowns, which you aren't looking for. What I would do is try first make a working Stopwatch, I recommend you read this tutorial on how to do that.
If you have got a working StopWatch class, you need 3 instances of this class: The total time; The Home possess time; And the away possess time.
If you have these it will be fairly easy to get the percentage of them. The home percentage would be something like this:
homeStopWatch.elapsedTime/totalStopWatch.elapsedTime * 100
The away would be subtracting the former calculated valued from 100. I hope I could clarify your doubts a bit, and please ask if there is a misunderstanding.
Regards -Jorge
I have a class that displays a custom indeterminate progress indicator. Each timer update it simply increments the rotation of a UIImageView using CGAffineTransformRotate.
This all works, however, I noticed that when it is running, the background process that it is waiting for runs 50% slower - which is a huge penalty. For instance; instead of taking say 20 seconds to complete the processing it takes 30 seconds. Can someone recommend a solution with less performance penalty?
func show() {
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(0.03, target: self, selector: #selector(self.updateTimer(_:)), userInfo: nil, repeats: true)
}
func updateTimer(sender: NSTimer) {
iconView.transform = CGAffineTransformRotate(iconView.transform, 0.15)
}
Use Core Animation to animate the rotation. The window server will do all the work outside of your app's process.
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.fromValue = 0
animation.toValue = 2 * M_PI
animation.repeatCount = .infinity
animation.duration = 1.25
iconView.layer.addAnimation(animation, forKey: animation.keyPath)
I think you can use dispatch_source from #Rob's answer:
Do something every x minutes in Swift
Here is the code:
var timer: dispatch_source_t!
func startTimer() {
let queue = dispatch_queue_create("com.domain.app.timer", nil)
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 60 * NSEC_PER_SEC, 1 * NSEC_PER_SEC) // every 60 seconds, with leeway of 1 second
dispatch_source_set_event_handler(timer) {
// do whatever you want here
}
dispatch_resume(timer)
}
func stopTimer() {
dispatch_source_cancel(timer)
timer = nil
}
In my game, there's a class for a "wall" that's moving to the left. I want to change the speed of it based on count i that I added to touchesBegan method:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent {
count++
}
func startMoving() {
let moveLeft = SKAction.moveByX(-kDefaultXToMovePerSecond, y: 0, duration: 1 )
let move = SKAction.moveByX(-kDefaultXToMovePerSecond, y: 0, duration: 0.5)
if(count <= 10)
{
runAction(SKAction.repeatActionForever(moveLeft))
}
else
{
runAction(SKAction.repeatActionForever(move))
}
}
but it's not working. Can you help?
As I said there are a lot of changes which have to be done:
First let's change MLWall class and add a duration property which will be used in startMoving method:
var duration:NSTimeInterval = 0.5
Then still inside MLWall class change the init method:
init(duration: NSTimeInterval) {
self.duration = duration
//...
}
And change startMoving method to use this passed parameter:
func startMoving() {
let moveLeft = SKAction.moveByX(-kDefaultXToMovePerSecond, y: 0, duration: self.duration)
runAction(SKAction.repeatActionForever(moveLeft))
}
Those are changes inside Wall class. Now let's make some changes in WallGenerator class:
First WallGenerator class should be aware of how fast walls should go. So we are adding property to store that info:
var currentDuration: NSTimeInterval = 1 // I named it duration, because SKAction takes duration as a parameter, but this actually affects on speed of a wall.
After that the first method which has to be changed is startGeneratingWallsEvery(second:) into startGeneratingWallsEvery(second: duration:
//duration parameter added
func startGeneratingWallsEvery(seconds: NSTimeInterval, duration : NSTimeInterval) {
self.currentDuration = duration
generationTimer = NSTimer.scheduledTimerWithTimeInterval(seconds, target: self, selector: "generateWall", userInfo: nil, repeats: true)
}
Here, we are making a WallGenerator aware of desired wall's speed.
And the next method which has to be changed in order to use that speed is:
//duration parameter added
func generateWall() {
//...
//Duration parameter added
let wall = MLWall(duration: self.currentDuration)
//...
}
And there is a GameScene left. There, I've added a tapCounter property:
let debugLabel = SKLabelNode(fontNamed: "Arial") //I've also added a debug label to track taps count visually
var tapCounter = 0
Here is how you can initialize label if you want to see number of tap counts:
//Setup debug label
debugLabel.text = "Tap counter : \(tapCounter)"
debugLabel.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMaxY(frame)-50.0)
debugLabel.fontColor = SKColor.purpleColor()
self.addChild(debugLabel)
First I've changed the start method:
func start() {
//...
// adding duration parameter in method call
wallGenerator.startGeneratingWallsEvery(1,duration: 1)
}
The important part is : wallGenerator.startGeneratingWallsEvery(1,duration: 1) which says start generating walls every second with one second duration(which affects on node's speed).
Next, I've modified touchesBegan of the scene into this:
if isGameOver {
restart()
} else if !isStarted {
start()
} else {
tapCounter++
debugLabel.text = "Tap counter : \(tapCounter)"
if(tapCounter > 10){
wallGenerator.stopGenerating()
wallGenerator.startGeneratingWallsEvery(0.5, duration:0.5)
}
hero.flip()
}
Then, changed restart() method in order to restart the counter when game ends:
func restart() {
tapCounter = 0
//...
}
And that's pretty much it. I guess I haven't forgot something, but at my side it works as it should. Also, note that using NSTimer like from this GitHub project is not what you want in SpriteKit. That is because NSTimer don't respect scene's , view's or node's paused state. That means it will continue with spawning walls even if you think that game is paused. SKAction would be a preferred replacement for this situation.
Hope this helps, and if you have further questions, feel free to ask, but I guess that you can understand what's happening from the code above. Basically what is done is that WallGenerator has become aware of how fast their wall nodes should go, and Wall node has become aware of how fast it should go...
EDIT:
There is another way of changing walls speed by running an moving action with key. Then, at the moment of spawning, based on tapCounter value, you can access an moving action by the key, and directly change actions's speed property...This is probably a shorter way, but still requires some changes (passing a duration parameter inside Wall class and implementing tapCounter inside scene).
Try doing it like so :
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent {
count++
startMoving()
}
func startMoving() {
removeAllActions()
let moveLeft = SKAction.moveByX(-kDefaultXToMovePerSecond, y: 0, duration: count <= 10 ? 1.0 : 0.5)
runAction(SKAction.repeatActionForever(moveLeft))
}
As of I read comments below your question, you made a really unclear question, but nevertheless you have idealogical problems in your code.
Your touchesBegan method is implemented in the wall if I understood everything right, so it has no effect on newly generated walls. You have to move that logic to the scene and then spawn new walls with speed as a parameter, or at least make count a class var, so every wall can access that, but you still has to handle your touches in the scene, because now touch is handled when user taps directly in the wall.
So I am using an NSTimer to let the user know the app is working. The progress bar is set up to last 3 seconds, but when running, it displays in a 'ticking' motion and it is not smooth like it should be. Is there anyway I can make it more smooth - I'm sure just a calculation error on my part....
If anyone could take a look that would be great. Here is the code:
import UIKit
class LoadingScreen: UIViewController {
var time : Float = 0.0
var timer: NSTimer?
#IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
// Do stuff
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
}//close viewDidLoad
func setProgress() {
time += 0.1
progressView.progress = time / 3
if time >= 3 {
timer!.invalidate()
}
}
}
Edit: A simple 3 second UIView animation (Recommended)
If your bar is just moving smoothly to indicate activity, possibly consider using a UIActivityIndicatorView or a custom UIView animation:
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
UIView.animateWithDuration(3, animations: { () -> Void in
self.progressView.setProgress(1.0, animated: true)
})
}
Make sure your progressView's progress is set to zero to begin with. This will result in a smooth 3 second animation of the progress.
Simple animated progress (Works but still jumps a bit)
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIProgressView_Class/#//apple_ref/occ/instm/UIProgressView/setProgress:animated:
func setProgress() {
time += 0.1
progressView.setProgress(time / 3, animated: true)
if time >= 3 {
timer!.invalidate()
}
}
Option with smaller intervals. (Not recommended)
Set your timer to a smaller interval:
timer = NSTimer.scheduledTimerWithTimeInterval(0.001, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
Then update your function
func setProgress() {
time += 0.001
progressView.setProgress(time / 3, animated: true)
if time >= 3 {
timer!.invalidate()
}
}
For continues loader
timer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(setProgress), userInfo: nil, repeats: true)
and
func setProgress() {
time += 0.001
downloadProgressBar.setProgress(time / 3, animated: true)
if time >= 3 {
self.time = 0.001
downloadProgressBar.progress = 0
let color = self.downloadProgressBar.progressTintColor
self.downloadProgressBar.progressTintColor = self.downloadProgressBar.trackTintColor
self.downloadProgressBar.trackTintColor = color
}
It's hard to say exactly what the problem is. I would like to see the output if you put a print line in setProgress to print a timestamp. Is it actually firing every tenth of a second? My guess is that it is not.
Why not? Well, the timer schedules a run loop task in the main thread to execute the code in setProgress. This task cannot run until tasks in front of it in the queue do. So if there are long running tasks happening in your main thread, your timer will fire very imprecisely. My first suggestion is that this is perhaps what is happening.
Here is an example:
You start a timer to do something every second.
Immediately after, you start a long running main thread task (for example, you try to write a ton of data to a file). This task will take five seconds to complete.
Your timer wants to fire after one second, but your file-writing is
hogging the main thread for the next four seconds, so the timer can't fire
for another four seconds.
If this is the case, then to solve the problem you would either need to move that main thread work to a background thread, or else figure out a way to do it while returning to the run loop periodically. For example, during your long running main thread operation, you can periodically call runUntilDate on your run loop to let other run loop tasks execute.
Note that you couldn't just increment the progress bar fill periodically during the long running main thread task, because the progress bar will not actually animate its fill until you return to the run loop.
What about proper way for animating changes: animateWithDuration:animations: or CABasicAnimation. You can use this for creating smooth animations