Home > Net >  SpriteView doesn't pause scene on state change
SpriteView doesn't pause scene on state change

Time:10-18

Problem

It seems that SpriteView-not-pausing

All the relevant code is in SpriteView-not-updating

The relevant code for this one is in ContentView.swift:

import SwiftUI
import SpriteKit

class GameScene: SKScene, ObservableObject {
    @Published var updates = 0
    private let label = SKLabelNode(text: "Updates in SKScene:\n0")
    
    override func didMove(to view: SKView) {
        addChild(label)
        label.numberOfLines = 4
        label.position = CGPoint(x: 0, y: -100)
    }
    
    override func update(_ currentTime: TimeInterval) {
        updates  = 1
        label.text = "Updates in SKScene:\n\(updates)\nScene created at:\n\(name!)"
    }
    
    deinit {
        print("-- Scene \(name!) deinit --")
    }
}

class SceneStore : ObservableObject {
    @Published var currentScene: GameScene
    
    init() {
        currentScene = SceneStore.createScene()
    }
    
    func restartLevel() {
        currentScene = SceneStore.createScene()
    }
    
    // MARK: - Class Functions
    
    static private func createScene() -> GameScene {
        let scene = GameScene()
        scene.size = CGSize(width: 300, height: 400)
        scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        scene.scaleMode = .fill
        scene.name = Date().formatted(date: .omitted, time: .standard)
        return scene
    }
}

struct ContentView: View {
    @EnvironmentObject private var sceneStore: SceneStore
    @EnvironmentObject private var scene: GameScene
    @State private var paused = false
    
    var body: some View {
        if #available(iOS 15.0, *) {
            print(Self._printChanges())
        }
        return ZStack {
            SpriteView(scene: scene, isPaused: paused).ignoresSafeArea()
            VStack {
                Text("Updates from SKScene: \(scene.updates)").padding().foregroundColor(.white)
                Text("Scene created at: \(scene.name!)" as String).foregroundColor(.white)
                Button("Restart") {
                    sceneStore.restartLevel()
                }.padding()
                Button("Paused: \(paused)" as String) {
                    paused.toggle()
                }
                Spacer()
            }
        }
    }
}

Question / Workaround?

Am I missing something? Or is this a bug? If so, is there any workaround?

CodePudding user response:

You are correct that isPaused, when passed to SpriteView, does not seem to affect the paused state.

To get around that, I used:

.onChange(of: paused) { newValue in
  sceneStore.currentScene.isPaused = newValue
}

Your second issue is one-part SpriteView problem and one part ObservableObject problem.

One important bit to know is that nested ObservableObjects do not propagate their state unless you manually call objectWillChange.send(). So, I've used Combine to track the @Published updates variable on the child object and call objectWillChange whenever there's an update.

Also, when there's a new scene, setupCombine is called again, which not only propagates the updates, but also alerts the View that the scene has changed.

Finally, because the scene doesn't get reloaded otherwise, I'm using an id that changes when a new GameScene is made -- this forces SwiftUI to create a new SpirteView.

import SwiftUI
import Combine

@main
struct SpriteView_not_updatingApp: App {
    @StateObject private var sceneStore = SceneStore()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(sceneStore)
        }
    }
}

class GameScene: SKScene, ObservableObject {
    @Published var updates = 0
    var id = UUID()
    private let label = SKLabelNode(text: "Updates in SKScene:\n0")
    
    override func didMove(to view: SKView) {
        addChild(label)
        label.numberOfLines = 4
        label.position = CGPoint(x: 0, y: -100)
    }
    
    override func update(_ currentTime: TimeInterval) {
        updates  = 1
        label.text = "Updates in SKScene:\n\(updates)\nScene created at:\n\(name!)"
    }
    
    deinit {
        print("-- Scene \(name!) deinit --")
    }
}

class SceneStore : ObservableObject {
    var currentScene: GameScene
    
    var cancellable : AnyCancellable?
    
    init() {
        currentScene = SceneStore.createScene()
        setupCombine()
    }
    
    func restartLevel() {
        currentScene = SceneStore.createScene()
        setupCombine()
    }
    
    func setupCombine() {
        cancellable = currentScene.$updates.sink { _ in
            self.objectWillChange.send()
        }
    }
    
    // MARK: - Class Functions
    
    static private func createScene() -> GameScene {
        let scene = GameScene()
        scene.size = CGSize(width: 300, height: 400)
        scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        scene.scaleMode = .fill
        scene.name = Date().formatted(date: .omitted, time: .standard)
        return scene
    }
}

struct ContentView: View {
    @EnvironmentObject private var sceneStore: SceneStore
    @State private var paused = false
    
    var body: some View {
        if #available(iOS 15.0, *) {
            print(Self._printChanges())
        }
        return ZStack {
            SpriteView(scene: sceneStore.currentScene, isPaused: paused)
                .id(sceneStore.currentScene.id) //<-- Here
                .ignoresSafeArea()
                .onChange(of: paused) { newValue in
                    sceneStore.currentScene.isPaused = newValue //<-- Here
                }
            VStack {
                Text("Updates from SKScene: \(sceneStore.currentScene.updates)").padding().foregroundColor(.white)
                Text("Scene created at: \(sceneStore.currentScene.name!)" as String).foregroundColor(.white)
                Button("Restart") {
                    sceneStore.restartLevel()
                }
                .padding()
                Button("Paused: \(paused)" as String) {
                    paused.toggle()
                }
                Spacer()
            }
        }
    }
}

CodePudding user response:

You can check Pausing a sprite kit scene

Basically you have to pause the scene every time you click the pause button

    @State private var paused = false {
        didSet {
            self.scene.isPaused = paused
        }
    }

That will pause your update

  • Related