首页 > 解决方案 > 使用 SpriteView(isPaused:) 从 SwiftUI 暂停 SpriteKit 场景,而不是每次都重新初始化它?

问题描述

我正在尝试使用SpriteView初始化程序中的isPaused:参数来暂停SwiftUI 中的状态属性更改时。SKScene

isPaused:但是当我在初始化程序中使用状态变量作为参数时,例如:

SpriteView(scene: scene, isPaused: showingLevelChooser)

而不仅仅是:

SpriteView(scene: scene)

每次变量更改时都会重新创建场景,这不是我想要的。我只想暂停游戏。

我很困惑这个isPaused:论点应该如何工作。SpriteView 文档中没有太多信息。

据我了解,这是因为 SwiftUI 重新创建了依赖于状态的视图。但是如果是这样的话,你怎么能从 SwiftUI 中暂停 SpriteKit 场景,而不是每次都重新初始化呢?

我在这里创建了一个示例 Xcode 13 项目:https ://github.com/clns/SpriteView-isPaused

正在屏幕SKScene上显示“经过的时间” ,以秒为单位。每次呈现 SwiftUI 工作表并且状态变量showingLevelChooser发生变化时,计时器都会从 0(零)开始,因为场景会重新初始化。

Visual_Demonstration

所有相关代码都在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")
            }
        }
    }
}

标签: iosswiftswiftuisprite-kitspriteview

解决方案


这里有两个问题需要注意。第一个是你想有一个参考你SKScene会坚持下去。将其作为计算属性View将不起作用(正如您所经历的那样),因为它View是可传递的,并且每次重新加载时,您都会得到一个新的SKScene.

对此有多种可接受的解决方案,但我选择将其封装SKScene在一个ObservableObject您可以引用的 viaStateObject中。您甚至可以尝试制作SKSceneanObservableObject本身(此处未显示)。

其次,您在屏幕上确定/显示“暂停”的逻辑将很难准确地看到,因为它总是只显示经过的时间——在暂停期间没有不计算时间的逻辑。我用一个显示更新次数的简单计数器替换了它。这样,您可以清楚地看出场景确实已经暂停(并且没有更新)。


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")
            }
        }
    }
}

推荐阅读