【问题标题】:SpriteKit transition between scenes without resetting gameSpriteKit 在场景之间转换而无需重置游戏
【发布时间】:2016-08-02 05:16:01
【问题描述】:

我如何在不重置游戏的情况下在场景之间转换,因为 didMoveToView() 被调用并重新初始化了我的所有实例变量。例如,我有一个游戏场景和一个商店场景。当我从商店场景过渡到游戏时,游戏会重置。有没有办法防止这种情况发生,或者如何在场景之间转换时保持游戏的相同状态?

【问题讨论】:

    标签: swift sprite-kit skscene


    【解决方案1】:

    您有很多选项可以在游戏场景中保持持久状态。我列出了我使用过的两种方法。

    选项 A:保持对场景的引用

    当一个场景被换成一个新场景时,该场景通常会从内存中完全删除。如果您在其他地方保留场景对象的引用并呈现该引用,则不会丢失任何数据。

    为了随着时间的推移保持引用(并在需要时再次呈现场景),我建议使用具有静态实例的场景呈现器类,如下所示:

    class SceneCoordinator {
        static var shared = SceneCoordinator()
    
        var gameScene : GameScene?
        var shopScene : ShopScene?
    }
    

    当您初始化一个GameScene 时,也将它注册到您的SceneCoordinator.shared.gameScene = self。然后,当从另一个场景过渡时,您可以呈现您存储在协调器类中的实例。

    didMoveToView() 再次出现时仍会在场景中调用。您可以将所有初始化代码移动到一个单独的函数中,创建一个新的实例 var,例如 var isInitialized = false,并且仅在它为 false 时才初始化您的内容(并在初始化后将其设置为 true)。

    这种方法的问题是场景对象很昂贵,这意味着您可能会因为不允许释放场景而增加大量开销。

    选项 B:模型结构

    更好的方法(也允许在您的应用关闭后更轻松地重新初始化场景)是创建游戏状态的数据模型,并提供一个函数来从您的模型对象创建 GameScene。

    这种方法更符合 Model-View-Controller 设计模式,让你的场景和数据更加轻量。

    如:

    struct GameModel {
        var coins : Int
    }
    
    class GameScene : SKScene {
        var state : GameModel
    
        convenience init(size: CGSize, state: GameModel) {
            self.state = state
            // set up scene content from state
        }
    
        // lots of fun game scene logic
    
        func playerCollectedCoin() {
            state.coins += 1
        }
    
        func moveToShopScene() {
            // init a new shop scene with the state of this scene
            let shop = ShopScene(size: self.view!.bounds.size, state: self.state)
            (self.view as! SKView).presentScene(scene)
        }
    }
    
    class ShopScene : SKScene {
        var state : GameModel
    
        convenience init(size: CGSize, state: GameModel) {
            self.state = state
            // set up scene content from state
        }
    
        // lots of fun shop scene logic
    
        func playerSpentCoins(amount: Int) {
            state.coins -= amount
        }
    
        func moveToGameScene() {
            // init a new game scene with the updated state of this scene
            let game = GameScene(size: self.view!.size, state: self.state)
            (self.view as! SKView).presentScene(game)
        }
    }
    

    【讨论】:

    • 这个例子中的这些便利初始化是什么?你不需要在 SKScene 中有 didMoveToView 吗?另外,如果您想要多个模型怎么办?像 Person 模型、Enemy 模型等。你的场景会引用每个模型吗?或者它只会引用一个 GameModel,然后你的 GameModel 会引用每一个?试图了解我未来应用的优秀设计。
    • didMoveToView() 不需要在 SKScene 子类中被覆盖。它通常被覆盖以设置内容。但同样,它不是必需的。我在这里的模型示例相当简单,您的模型对象会更复杂,包括在场景被丢弃后重新初始化场景所需的一切(包括敌人位置列表等)。创建额外的模型结构将有助于保持清洁。除了传递结构之外,您还可以遵循 Alessandro 的建议并保留模型信息的共享实例。
    • 方便初始化器允许您定义一个新的初始化器,该初始化器从中调用指定的初始化器。所以这里方便的 init(size: model:) 会调用 self.init(size:) 然后使用模型信息来设置游戏内容。
    • 鉴于我的 GameScene 中现在已经有如此多的模型数据,最好的选择是什么。我必须重构一切,这将是一场噩梦。
    • 如果您的 GameScene 中已经有很多数据,并且不想丢失它,则必须维护对它的引用。我正在扩展答案的 A 部分,以包含有关执行此操作的正确方法的更多详细信息。
    【解决方案2】:

    关于你的问题,我想把你的注意力集中在三个方面:

    • 1) 型号
    • 2) 共享实例管理器
    • 3) 子类化

    1) 您可以创建一个包含共享属性的类,而不是每次都在您的 SKSceneSKNode 中重复相同的共享属性(结构、变量或自定义类..)。

    2) 拥有一些共享实例管理器对您的项目来说非常舒适:例如,具有您项目的所有管理器(共享实例类)的属性的游戏管理器(共享实例类)(下面的示例):

    • 网络管理员(如果 WIFI/3G/LTE 可用并存储值,则每次调用他的更新方法时检查)
    • 音频管理器(提供有关背景音乐、效果、音量的共享属性...
    • 通知管理器(显示任何类型的弹出窗口:成功、警告、错误...)
    • 文件管理器(保存到文件并从文件中读取,清除目录...)
    • 等等..

    还有您的游戏属性模型,例如设置和游戏变量。

    3) 如果您在场景中需要 rootNode (添加元素以暂停而不是另一个,将对象添加到同一个锚定节点,制作背景..),您可以继承自定义 @ 987654324@ 具有此属性。


    一些代码显示你的项目如何出现,从 GameManager 开始:

    class GameManager: NSObject {
        static let sharedInstance = GameManager()
        var allProducts = Products()
    }
    

    您的 GameScene 类实例可以像这样访问 GameManager 的单个实例:

    class GameScene: SKScene {
        let gameManager = GameManager.sharedInstance
        override func didMoveToView(view: SKView) {
           print("the current number of shoes is \(self.gameManager.allProducts.shoes)")
        } 
    }
    

    您的 ShopScene 类实例可以像这样访问同一个 GameManager 实例:

     class ShopScene: SKScene {
            let gameManager = GameManager.sharedInstance
            override func didMoveToView(view: SKView) {
                print("the current number of shoes is \(self.gameManager.allProducts.shoes)")
        }  
    }
    

    【讨论】:

    • 我对设计模式不太了解,我很难理解这个例子。为什么 GameManager 有自己的静态实例?那么 GameManager 中的所有变量不都需要是静态的吗?
    • 第一个问题:在 swift 2.x 中实现共享实例(仅启动一次的实例)是正确的语法。第二个问题:不,你可以声明你的普通变量,它是类本身一个静态实例,但他的属性可以随意声明。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多