【问题标题】:SpriteKit - update energy over time across multiple scenesSpriteKit - 在多个场景中随时间更新能量
【发布时间】:2019-02-20 00:12:53
【问题描述】:

我有一个游戏,我想随着时间的推移更新“能量”条。所以每 5 分钟玩家应该多获得一个能量点来使用。我知道如何在单个场景中执行此操作,但无论您在哪个场景中以及在背景中,这都应该更新,但我正在努力解决如何最好地实现这一点。

对于背景,我想我已经了解了这一点。当应用程序被发送到后台时,我会存储时间,当它再次进入前台时,我会获取当前时间并计算出它们应该获得多少能量。

我正在努力解决跨场景管理并相应更新 UI 的最佳方式(我计划在所有屏幕的左上方显示)。我的第一个想法是有一个单例对象来读取存储的能量级别并有一个计时器(在精灵套件更新循环之外)。这每 5 分钟运行一次并更新能量水平。然后它可以发布一个通知,以便任何非 SpriteKit 视图都可以显示当前能量,类似地,任何 SpriteKit 场景都可以使用更新循环来读取值并相应地显示它。但我不确定这是否是一种有效的方法。

我是否应该只将一个对象附加到 appdelegate 来执行类似于 Singleton 的操作。这可以让我轻松比较应用程序再次激活的时间(例如,刷新能量水平需要 2.5 分钟)

任何人都可以建议这是一个好方法还是有更好的方法来实现这一点?

【问题讨论】:

    标签: swift sprite-kit


    【解决方案1】:

    如果我理解正确的话,你有两个问题;一种是关于在任何场景下都显示能量条,另一种是用于计算应用程序关闭期间获得的能量。

    关于第一个问题(全场景显示能量条)你可以看看SKReferenceNode

    我在其中一款带有全局菜单的游戏中所做的是: 我为菜单创建了一个单独的 SpriteKit 场景(.sks 文件)。然后我在 GameScene.sks 文件中添加了 SKReferenceNode。在 GameScene.swift 文件中,我只是将 ReferenceNode 视为普通的 SKSpriteNode,如下所示:

    class GameScene: SKScene {
    
    // Reference node added in GameScene.sks
    var playerHealthBar : SKSpriteNode!
    
    
    override func didMove(to view: SKView) {
    
        // Reference node
        playerHealthBar = self.childNode(withName: “//playerHealthBar”) as? SKSpriteNode
    
     }
    

    }

    使用 SKReferenceNode,您可以在我们所有的场景中实现该节点。然后,您将能够随时更新节点。我使用这种方法在我的游戏中创建全局菜单栏(例如,可以显示用户金币、健康点等)

    对于有关 playerHealthBar 的数据(例如健康点),您可以从 UserDefaults 加载/保存。

    至于第二个问题,一个想法是在 UserDefaults 中存储一个名为 lastActive 的变量。当应用程序进入后台时更新此值。

    当应用程序进入前台时,您需要将当前时间与lastActive 变量进行比较。

    看看这个:

     func timeSpentAwayInSeconds() -> Int {
        // We then check when the player was last active
        let lastActive = UserDefaults.standard.object(forKey: "lastActive")
    
        // Then we compare that to the current date
        let elapsedTime = Date().timeIntervalSince(lastActive as! Date)
    
        // Covert that to seconds
        let elapsedTimeInSeconds : Int = Int(elapsedTime)
    
        return elapsedTimeInSeconds
    }
    

    我希望这会有所帮助。如果它解决了您的问题,请标记为已回答:-)

    编码愉快!

    【讨论】:

    • 我认为它比实际创建这样一个对象的位置更具概念性。即它是否应该是驻留在appdelegate中的单例是我能想到的,我不知道在SpriteKit的概念中是否有更好的设计模式来实现这一点
    • 您能否举例说明如何更新全局菜单栏等?在使用 referenceNode 时,我的能量值在移动到新场景时会被重置(我假设是因为它似乎再次重新加载了引用)。感觉更像是我需要走一条不同的路线来处理这个问题
    • 我已经用更详细的解释和示例代码更新了我的帖子。
    • 我认为这里有一些交叉线,我知道如何在多个场景中使用参考节点进行显示,但这是管理与此相关的数据的最佳方式。最终我可以将它保存在一个符合 NSCoding 的对象中。但是,每个场景是否应该在加载时创建这个“playerData”的新实例,或者我应该只有一个(可能附加到 appDelegate),记住值将每 5 分钟更新一次,然后我应该使用类似的东西KVO 还是仅仅依靠 update 方法来读取最新的值?
    • 啊,我的错。我会在我们的班级之外声明一个dictionary,当应用程序打开时,您可以将所有游戏数据加载到该字典中。通过这种方式,您将能够访问所有场景中的所有游戏数据。然后当应用程序关闭,进入后台等时,您可以将字典的数据保存到 ex.用户默认值。我希望这是有道理的。如果您需要一些示例代码,请告诉我。
    【解决方案2】:

    这是您想使用Timer 的少数情况之一。

    let date = Date().addingTimeInterval(5)
    let timer = Timer(fireAt: date, interval: 0, target: self, selector: #selector(runCode), userInfo: nil, repeats: false)
    RunLoop.main.add(timer, forMode: .common)
    

    这是因为你依赖于现实世界的时间,而不是游戏时间。

    现在请记住,您需要跟踪计时器的任何失火,因为您正在做其他事情。您的 runCode 函数应该链接到您正在更新的场景的代表,并且也不应该增加。相反,它应该检查您的代码上次触发的时间,并将其与您的代码触发的当前时间进行比较,以确定要添加多少点。它的行为方式应该与您离开和返回应用程序时使用的代码完全相同。

    现在,如果您需要执行任何 UI 更新,我建议将其附加到您附加到场景的 SKAction,这样如果您的场景处于暂停状态或未加载,它将在场景恢复时触发。这也保证了您的 UI 更改是在主线程上完成的,而不是您的计时器可能在的任何线程上。

    现在,根据您设计游戏的方式,我会推荐另一种方法。如果您有多个场景都具有相同的叠加层,那么我建议叠加层甚至不是 SpriteKit 的一部分。相反,创建一个位于 SpriteKit 场景之上的 UIView 层。这个视图的作用是获取不需要在场景之外的每一帧更新的节点,从而使您的场景能够更快地执行,因为它必须在更少的节点之间进行渲染、排序和过滤。最重要的是,您只需要更新 1 个视图,而不是 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
      相关资源
      最近更新 更多