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

nwlls2ji  于 12个月前  发布在  iOS
关注(0)|答案(2)|浏览(118)

问题

SpriteView似乎没有将state属性传递给SpriteView(scene:isPaused:)初始化器来暂停SKScene

示例项目

我创建了一个运行在iOS 15模拟器上的sample Xcode 13 project on GitHub,其中ContentView带有@State var paused属性,该属性发送给子SpriteView(scene: scene, isPaused: paused)。状态属性通过“Paused:”按钮进行更改。属性更改,按钮上的文本更新,但SpriteView从未暂停场景。
看起来SpriteView没有拾取更新,也没有暂停它内部的底层SKView和SKScene。
x1c 0d1x的数据
所有相关代码都在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()
    }
}

字符串

重启问题

我还创建了一个second GitHub project,它与第一个类似,但它也有一个“重启”按钮,显示了同样的问题。
当按下“重启”按钮时,应该会重新创建SKScene。SKScene会在SceneStoreContentViewText视图中重新创建(为场景的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()
            }
        }
    }
}

问题/解决方法?

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

3htmauhk

3htmauhk1#

您是正确的,isPaused在传递给SpriteView时似乎不会影响暂停状态。
为了解决这个问题,我使用了:

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

字符串
第二个问题是一部分SpriteView问题和一部分ObservableObject问题。
需要知道的重要一点是,嵌套的ObservableObject s不会传播它们的状态,除非您手动调用objectWillChange.send()。因此,我使用了合并来跟踪子对象上的@Publishedupdates变量,并在有更新时调用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()
            }
        }
    }
}

qcuzuvrc

qcuzuvrc2#

您可以查看Pausing a sprite kit scene
基本上你每次点击暂停按钮都要暂停场景

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

字符串
这将暂停您的更新

相关问题