SpriteView 不会在状态更改时暂停场景

问题描述 投票:0回答:3

问题

似乎

SpriteView
不会暂停
SKScene
,并将状态属性传递给
SpriteView(scene:isPaused:)
初始值设定项。

示例项目

我在 GitHub 上创建了一个 示例 Xcode 13 项目,该项目在 iOS 15 模拟器上运行,其中

ContentView
具有发送给子组件
@State var paused
SpriteView(scene: scene, isPaused: paused)
属性。状态属性通过“Paused:”按钮更改。属性发生变化,按钮上的文本也会更新,但场景永远不会被 SpriteView 暂停。

看起来 SpriteView 没有获取更新,也没有暂停其中的底层 SKView 和 SKScene。

所有相关代码都在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 = 2
    }
    
    override func update(_ currentTime: TimeInterval) {
        updates += 1
        label.text = "Updates in SKScene:\n\(updates)"
    }
}

struct ContentView: View {
    @State private var paused = false
    
    @StateObject private var scene: GameScene = {
        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 {
        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)
                Button("Paused: \(paused)" as String) {
                    paused.toggle()
                }.padding()
                Spacer()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

重启问题

我还创建了一个第二个 GitHub 项目,与第一个项目类似,但它也有一个“重新启动”按钮,显示相同的问题。

Restart”按钮应在按下时重新创建

SKScene
。 SKScene 在
SceneStore
ContentView
Text
视图中重新创建(将新时间分配给场景的
name
属性),但
SpriteView
不会更改场景。

SpriteView似乎将初始场景保留在内存中,并且不让它去用新场景替换它。这可以在控制台中看到,通过按两次重新启动按钮并查找类似“-- Scene 7:49:44 PM deinit --”之类的内容。

场景内

@Published var updates = 0
属性的更新也会停止(在屏幕顶部),因为创建的新场景不会添加到视图中,因此永远不会调用
SKScene.didMove(to view:)
方法。

相关代码位于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()
            }
        }
    }
}

问题/解决方法?

我错过了什么吗?或者这是一个错误?如果是这样,有什么解决办法吗?

ios swiftui sprite-kit
3个回答
3
投票

您是正确的,

isPaused
,当传递到
SpriteView
时,似乎不会影响暂停状态。

为了解决这个问题,我使用了:

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

您的第二个问题是一部分

SpriteView
问题和一部分
ObservableObject
问题。

要知道的一个重要一点是,嵌套的

ObservableObject
不会传播它们的状态,除非您手动调用
objectWillChange.send()
。因此,我使用组合来跟踪子对象上的
@Published
updates
变量,并在有更新时调用
objectWillChange

此外,当有新场景时,会再次调用

setupCombine
,这不仅会传播更新,还会提醒
View
场景已更改。

最后,因为场景不会重新加载,所以我使用了一个

id
,当创建新的
GameScene
时,它会发生变化——这迫使 SwiftUI 创建一个新的
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()
            }
        }
    }
}

0
投票

您可以检查暂停精灵套件场景

基本上每次单击暂停按钮时都必须暂停场景

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

这将暂停您的更新


0
投票

我不能超过@Callin和@jnpdx。我在尝试构建一个场景管理器时遇到了类似的问题,无法保持事件/触摸的正确状态,主要是在场景之间进行转换时。 使用

Observable
制作
id
我的主要 GameScene 类并将场景管理器保留为
@StateObject
解决了所有刷新/暂停问题。

希望有帮助,非常感谢

© www.soinside.com 2019 - 2024. All rights reserved.