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.
Related
I have a CAEmitterLayer instance that I want to pause and then resume multiple times.
I have found various ways to do this using two CAEmitterLayer extension functions:
public func pause() {
speed = 0.0 // Freeze existing cells.
timeOffset = convertTime(CACurrentMediaTime(), from: self)
lifetime = 0.0 // Stop creating new cells.
}
and
public func resume() {
speed = 1.0
beginTime = convertTime(CACurrentMediaTime(), from: self) - timeOffset
timeOffset = 0.0
lifetime = 1.0
}
The first occasion of using emitterLayer.pause() and emitterLayer.resume() works perfectly.
However, from the second occasion onwards, whenever I use emitterLayer.pause(), the emitterCells jump slightly forward in time.
Can anybody out there help me resolve this jumping problem, please?
I needed to adjust the timeOffset in the pause() method. This is a working extension for pausing and resuming a CAEmitterLayer instance:
extension CAEmitterLayer {
/**
Pauses a CAEmitterLayer.
*/
public func pause() {
speed = 0.0 // Freeze the CAEmitterCells.
timeOffset = convertTime(CACurrentMediaTime(), from: self) - beginTime
lifetime = 0.0 // Produce no new CAEmitterCells.
}
/**
Resumes a paused CAEmitterLayer.
*/
public func resume() {
speed = 1.0 // Unfreeze the CAEmitterCells.
beginTime = convertTime(CACurrentMediaTime(), from: self) - timeOffset
timeOffset = 0.0
lifetime = 1.0 // Produce CAEmitterCells at previous rate.
}
}
Use as:
var emitterLayer = CAEmitterLayer()
/// Configure as required
emitterLayer.pause()
emitterLayer.resume()
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
override func update(currentTime: NSTimeInterval) {
// ...
}
how to stop currentTime in update method?
I want to calculate time since game starts.
when player clicks pause button, current scene is paused successfully.
but, currentTime in update is moving.
When the scene is paused, update will not be called, try this code:
class GameScene: SKScene {
override func didMove(to view: SKView) {
let wait = SKAction.wait(forDuration: 2)
let pause = SKAction.run { self.isPaused = true }
self.run(SKAction.sequence([wait, pause]))
}
override func update(_ currentTime: TimeInterval) {
print("update")
}
}
"update" will be printed for the first 2 seconds only. This proves that pausing the scene will stop update. You are probably not pausing the scene, but a node in the scene on which all actions are run.
In addition to that, implementing a pause screen by pausing the scene isn't such a good idea because the user can't leave the pause screen by tapping if the scene is paused. Also, you can't show cool animations in the pause screen.
What I usually do is to have a background node. Every game sprite are added as a child of the background node. In the pause screen, the background pauses but the scene is still running. I then add the pause screen spirte as a direct child of the scene so that you can still interact with the pause screen.
You don't really need to "stop" the update method. You just need to check whether the game is paused in the method. If it is, return immediately.
If you want to measure how much time has passed since the player started the game, you can also use Date objects. Create a Date at the start of the game and another Date when the user pauses. Call the timeIntervalSince method and there you go!
I've implemented pause like the following, I think the code is self-explenatory but I've added some comments:
// BaseScene inherits from SKScene but adds some methods, e.g. for dealing with
// controller input.
class GameScene : BaseScene {
// The previous update time is used to calculate the delta time.
// The delta time is used to update the Game state.
private var lastUpdateTime: NSTimeInterval = 0
override func update(currentTime: CFTimeInterval) {
// NOTE: After pausing the game, the last update time is reset to
// the current time. The next time the update loop is entered,
// a correct delta time can then be calculated using the current
// time and the last update time.
if lastUpdateTime <= 0 {
lastUpdateTime = currentTime
} else {
let deltaTime = currentTime - lastUpdateTime
lastUpdateTime = currentTime
Game.sharedInstance.update(deltaTime)
}
}
// A method on BaseScene that is called when the player presses pause
// on controller.
override func handlePausePress(forPlayer player: PlayerIndex) {
paused = true
lastUpdateTime = 0
}
}
My Game singleton only makes use of the delta time (time difference since last call) when updating the game state.
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.
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.