Home > Net >  Pause a SpriteKit scene from SwiftUI using SpriteView(isPaused:), without reinitializing it every ti
Pause a SpriteKit scene from SwiftUI using SpriteView(isPaused:), without reinitializing it every ti

Time:10-05

I am trying to use the isPaused: argument in the Visual_Demonstration

All relevant code is in ContentView.swift.

class GameScene: SKScene {
    private let label = SKLabelNode(text: "Time Elapsed:\n0")
    private var lastUpdateTime : TimeInterval = 0
    
    override func didMove(to view: SKView) {
        addChild(label)
    }
    
    override func update(_ currentTime: TimeInterval) {
        if (self.lastUpdateTime == 0) {
            self.lastUpdateTime = currentTime
        }
        
        let seconds = Int(currentTime - lastUpdateTime)
        label.text = "Time Elapsed:\n\(seconds)"
        label.numberOfLines = 2
    }
}

struct ContentView: View {
    @State private var showingLevelChooser = false
    
    var scene: SKScene {
        let scene = GameScene()
        scene.size = CGSize(width: 300, height: 400)
        scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        scene.scaleMode = .fill
        return scene
    }
    
    var body: some View {
        ZStack {
            SpriteView(scene: scene, isPaused: showingLevelChooser)
                .ignoresSafeArea()
            VStack {
                Button("Level Chooser") {
                    showingLevelChooser.toggle()
                }
                Spacer()
            }
        }
        .sheet(isPresented: $showingLevelChooser) {
            VStack {
                Button("Cancel") {
                    showingLevelChooser.toggle()
                }
                Text("Level Chooser")
            }
        }
    }
}

CodePudding user response:

There are two issues to take care of here. The first is that you want to have a reference to your SKScene that will stick around. Making it a computed property of the View will not work (as you're experiencing) because the View is transitive and every time it gets reloaded, you'll get a new SKScene.

There are multiple acceptable solutions to this, but I chose to encapsulate the SKScene in an ObservableObject that you can hold a reference to via StateObject. You could even experiment with making the SKScene an ObservableObject itself (not shown here).

Secondly, your logic for determine/showing the "pause" on screen was going to be a little tough to accurately see, because it always just displayed the elapsed time -- there was no logic involved to not count the time during the pause. I replaced it with a simple counter that shows the number of updates. That way, you can clearly tell that the scene has indeed been paused (and doesn't update).


class SceneStore : ObservableObject {
    var scene = GameScene()
    
    init() {
        scene.size = CGSize(width: 300, height: 400)
        scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        scene.scaleMode = .fill
    }
}

class GameScene: SKScene {
    private let label = SKLabelNode(text: "Updates: 0")
    private var updates = 0
    
    override func didMove(to view: SKView) {
        addChild(label)
    }
    
    override func update(_ currentTime: TimeInterval) {
        updates  = 1
        label.text = "Updates: \(updates)"
        label.numberOfLines = 2
    }
}

struct ContentView: View {
    @State private var showingLevelChooser = false
    @StateObject private var sceneStore = SceneStore()
    
    var body: some View {
        ZStack {
            SpriteView(scene: sceneStore.scene, isPaused: showingLevelChooser)
                .ignoresSafeArea()
            VStack {
                Button("Level Chooser") {
                    showingLevelChooser.toggle()
                }
                Spacer()
            }
        }
        .sheet(isPresented: $showingLevelChooser) {
            VStack {
                Button("Cancel") {
                    showingLevelChooser.toggle()
                }
                Text("Level Chooser")
            }
        }
    }
}
  • Related