Better Time Management in SpriteKit
Tags: gamedev, swift, spritekit
Published: 2022-05-08 11:01:06

---
description: "Setup a ticker with Swift and SpriteKit to have relevant relevant time information like delta time available anywhere in your game code."
---

Time management and the usage of delta time is one of the most important and fundamental parts for most game projects as it ensures consistency of any on-screen transforms no matter what the device's frame rate is that the game runs on.

More or less any form of movement, rotation or time-related calculation should take delta time into account to ensure it's not dependent on the frame rate.

Without using delta time, a bullet in a game, as an example, would move at a different speed on a 120 fps device compared to a 60 fps device.

## Delta Time in SpriteKit

SpriteKit does not give us a delta time value out of the box, which is unfortunate considering how useful it is.

It's not that the game framework engineers at Apple are not aware of delta time, as the `GKComponent` in GameplayKit requires delta time in its `update(deltaTime:)` method. But you have to calculate the value yourself.

At the same time as it's unfortunate that SpriteKit does not provide delta time out of the box, it's also understandable that the GameplayKit engineers deemed `deltaTime` as a requirement when calling the update method, as a component will most likely need to use delta time in one way or another.

And of course, if not using GameplayKit but only plain SpriteKit, you are going to need to use a delta time for your on-screen calculations to ensure consistency no matter what frame rate the device runs on.

SKScene provides the current time value, so most likely you are using or have seen solutions similar to this to get the delta time value.

```swift
class GameplayScene: SKScene {
  /// Keeps track of how much time has passed since last game loop update.
  var lastUpdateTime: TimeInterval = 0

  override func update(_ currentTime: TimeInterval) {
    super.update(currentTime)

    // Get delta time since last time `update` was called.
    let deltaTime = calculateDeltaTime(from: currentTime)
  }

  /// Calculates time passed from current time since last update time.
  private func calculateDeltaTime(from currentTime: TimeInterval) -> TimeInterval {
    // When the level is started or after the game has been paused, the last update time is reset to the current time.
    if lastUpdateTime.isZero {
      lastUpdateTime = currentTime
    }

    // Calculate delta time since `update` was last called.
    let deltaTime = currentTime - lastUpdateTime

    // Use current time as the last update time on next game loop update.
    lastUpdateTime = currentTime

    return deltaTime
  }
}
```

In the life cycle of this `SKScene` we get the current time provided in the update method. By storing the current time value in the class property `lastUpdateTime`, we are able to keep track of how much time that has elapsed since the last time the update method was called, and by that we have our delta time value.

We can then take this value and keep passing it along to other methods or systems that need to use delta time for their calculations.

Which could look something like this.

```swift
override func update(_ currentTime: TimeInterval) {
  super.update(currentTime)
  let deltaTime = calculateDeltaTime(from: currentTime)

  // Update the bullet system that handle movement of all bullets on screen.
  bulletSystem.update(deltaTime: deltaTime)

  // Update each GameplayKit component system.
  for componentSystem in componentSystems {
    componentSystem.update(deltaTime: deltaTime)
  }
}
```

Each system, component or object that uses delta time will then have to accept the value in its method signature, and then pass it along further down the chain to its own methods that relies on delta time. You most likely will not want to keep all your logic in the update method, as it could grow massive, but structure the logic in dedicated methods.

We then end up with components like this.

```swift
class MoveComponent: GKComponent {
  // ... Initialization and references to other components of the entity here ...

  override func update(deltaTime seconds: TimeInterval) {
    super.update(deltaTime: seconds)

    move(deltaTime: seconds)
  }

  private funct move(deltaTime seconds: TimeInterval) {
    entity.position += speed * direction * CGFloat(seconds)
  }
}
```

Here we have a move method in the component that moves the entity's position at a speed and direction that is not dependent on the device frame rate as we multiply the resulting velocity with our delta time value. As delta time is passed as a `TimeInterval` we have to cast it to an appropriate format, like `CGFloat`.

Most games will of course not only use the update method in SKScene, but will need to use the other methods in the life cycle, such as `didSimulatePhysics` or `didFinishUpdate`.

Those methods do not get the current time injected, so we are going to have to promote the `deltaTime` property to a class property to be able to access and use delta time in those scenarios.

```swift
class GameplayScene: SKScene {
  var deltaTime: TimeInterval = 0

  override func update(_ currentTime: TimeInterval) {
    deltaTime = calculateDeltaTime(from: currentTime)
  }

  override func didSimulatePhysics() {
    // Update camera movemement.
    camera?.entity?.update(deltaTime: deltaTime)

    // Update each 'after physics' component system.
    for componentSystem in afterPhysicsComponentSystems {
      componentSystem.update(deltaTime: deltaTime)
    }
  }
}
```

While this is an approach that does the job, and SpriteKit provides us with the necessary data to deal with delta time, it's quite convoluted.

We are mixing the time logic with the scene logic. And as every calculation that affects what happens on the screen, which is a lot in most games, relies on delta time, we are going to have to keep passing the value around between systems, classes and methods.

We might also have multiple scenes, even running at the same time, which happens when transitioning between scenes, which would create multiple delta time calculations at the same time.

The more complex a game becomes, with more systems and classes, the more this approach will feel to start falling apart. Let's find a better way.

## Game Engines

Looking at modern game engines such as Unity and Unreal, they calculate delta time as a core functionality and the value is made available to use where you need it. It makes sense that any framework that targets game development makes this available.

If we for instance take a closer peek at Unity, it provides a Time class[^unity-time] that has static properties containing time-related data.

By that, you never have to pass any time information around in your game code. Instead, you can at any time do a calculation like this, by just statically accessing the `deltaTime` property of `Time`.

```cs
var position += speed * direction * Time.deltaTime;
```

It's apparent that they decided that something so crucial and that is used so often in game development should be calculated under the hood and be easily available at any time, which making it static solves.

The time class in Unity does not only provide `deltaTime` as a static property but has a ton of different useful time-related information.

## Swift Class for Static Time

We have now examined the ordinary approach to dealing with delta time in SpriteKit as well as taken a peek at how modern game engines handle it. So let's aim for a similar approach in SpriteKit for a better way to easily get access to time information via static properties.

We'll take inspiration from Unity and see how we can have a `Time` class available in SpriteKit with static properties that are not tied to the current scene's update loop.

The static class will need to hold relevant properties together with an `update()` method to keep the values current.

```swift
/// Tracks the game's time-related information.
public enum Time {
  /// The time given at the beginning of this frame.
  public private(set) static var time = TimeInterval(0.0)

  /// The interval in seconds from the last frame to the current one.
  public private(set) static var deltaTime = TimeInterval(0.0)

  /// Called on the device's frame update to track time properties.
  static func update(at currentTime: TimeInterval) {
    //  If `time` is 0.0 the game has just started, so set it to `currentTime`.
    if time.isZero {
      time = currentTime
    }

    // Calculate delta time since `update(at:)` was last called.
    deltaTime = currentTime - time

    // Set `time` to `currentTime for next game loop update.
    time = currentTime
  }
}
```

That gives us the `Time` class with the static properties and a method to keep them up to date. Now we just need a place to call the `update()` method. The first thought might be to call it from `update()` in `SKScene`. Which would work most of the time.

We do get one problem if we do it from `SKScene`, at some times we might have more than one scene running, for instance when transitioning between two scenes. That would cause a race condition when then they would both update `Time`, which will give us an incorrect `deltaTime` for one of the scenes.

We also want to keep our code clean and not have to remember to call `Time.update()` in every scene class we create, as well as we want to avoid polluting the scene class with code that doesn't have to be there.

Luckily `SKView` has a delegate property that we can use for this. The delegate property takes an `SKViewDelegate`[^skviewdelegate] object, which gives us the hook we need to have a place to calculate time values outside the scene class.

Let's create our own `Ticker` class, which is an `SKViewDelegate` so we can update our `Time` class. The `view()` method in `SKViewDelegate` is called just before the `update()` method in `SKScene`, so it's a perfect place for us to use to update time properties.

```swift
/// Assign to the `SKView` to update game time properties for each frame.
public class Ticker: NSObject, SKViewDelegate {
  public func view(_ view: SKView, shouldRenderAtTime time: TimeInterval) -> Bool {
    // Update time properties.
    Time.update(at: time)

    // By returning true the game runs at the full frame rate specified with preferredFramesPerSecond.
    return true
  }
}
```

And finally, we need to assign the delegate to the SKView, during the game's initialization process.

I prefer to have a `GameManager` class where I do all my early initialization. I instantiate my `GameManager` immediately from my `ViewController` where I pass in the `SKView` that will present my different game scenes.

That is the perfect spot to assign the custom delegate that will ensure that the time properties stay up to date without having to manage that on a per-scene basis.

```swift
class GameManager {
  /// Ticker that updates game time properties.
  let ticker = Ticker()

  init(view: SKView) {
    // Assign the game time ticker to the view.
    view.delegate = ticker

    // ... Other game setup code would go here...
  }
}
```

And there we have it, a rock-solid reusable solution that gives us easy access to time properties like delta time from anywhere in our game code.

Whenever we need to do a delta time based calculation, we can now at any time use `Time.deltaTime` as part of the calculation.

## Further Improvements

With a central location for our time properties, it doesn't have to end with delta time. There are plenty of time-related properties that are useful for game development. If we look at Unity's `Time` class as a reference, we could replicate more of the properties there and keep decorating our own `Time` class.

Let's say we also want to have access to the frame count in our game code.

```swift
public enum Time {
  /// The total number of frames since the start of the game.
  public private(set) static var frameCount = 0

  static func update(at currentTime: TimeInterval) {
    // ... previous code in the update method here ...

    // Increase frame count since game started.
    frameCount += 1
  }
}
```

We can now at any time access `Time.frameCount` if we need to read this value.

The final tweak, let's extend our value types operators to handle `TimeInterval`.

Previously in this post we multiplied speed with delta time and had to cast the time value to CGFloat, `CGFloat(seconds)`. As we are going to use the delta time value in many places, it would be convenient if we didn't have to cast it every time. Instead, we can extend types we are going to use with delta time, such as `CGFloat` and `CGVector` to use `TimeInterval`.

Here's an example of extending `CGFloat` so we can multiply a `CGFloat` value directly with a `TimeInterval` without having to cast it every time.

```swift
public extension CGFloat {
  static func * (lhs: CGFloat, rhs: TimeInterval) -> CGFloat {
    return lhs * CGFloat(rhs)
  }
}
```

By that, we are now able to write clean game code like this.

```swift
movement = speed * Time.deltaTime
```

What a beauty!

[^unity-time]: [Unity API: Time](https://docs.unity3d.com/ScriptReference/Time.html). Static properties that provides time information in Unity.
[^skviewdelegate]: [SKViewDelegate](https://developer.apple.com/documentation/spritekit/skviewdelegate). Take custom control over the view's render rate.