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()
}
}
}
}
我错过了什么吗?或者这是一个错误?如果是这样,有什么解决办法吗?
您是正确的,
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()
}
}
}
}
您可以检查暂停精灵套件场景
基本上每次单击暂停按钮时都必须暂停场景
@State private var paused = false {
didSet {
self.scene.isPaused = paused
}
}
这将暂停您的更新
我不能超过@Callin和@jnpdx。我在尝试构建一个场景管理器时遇到了类似的问题,无法保持事件/触摸的正确状态,主要是在场景之间进行转换时。 使用
Observable
制作 id
我的主要 GameScene 类并将场景管理器保留为 @StateObject
解决了所有刷新/暂停问题。
希望有帮助,非常感谢