【问题标题】:Swift: How to handle view controllers for my gameSwift:如何为我的游戏处理视图控制器
【发布时间】:2016-08-22 17:16:39
【问题描述】:

当我开发基于 SpriteKit 的游戏时,我有一个关于视图控制器以及如何以简洁的方式处理它们的一般性问题。

到目前为止我做了什么:

  • 故事板仅用于定义视图控制器
  • SKScene 由 presentScene 在每个视图控制器(Home、LevelSelection、Game)中呈现
  • 在每个视图控制器中,我调用 performSegueWithIdentifier 并使用我在视图控制器之间的情节提要中定义的标识符
  • 我在 SKScene 上使用 SKSpritenode 等以编程方式显示的所有内容
  • 在故事板上,我只有定义了 segue 关系和标识符的视图控制器
  • 我在 viewDidDisappear 中所做的所有事情都是因为它似乎是正确取消我的 SKScene 的唯一方法

我的问题是:

  • 每次我切换到另一个视图时,我的内存都会增加,因为视图控制器被重新初始化,旧的控制器一直留在堆栈中
  • 我不清楚如何处理视图控制器之间的 segue,在一些教程页面上我看到人们使用导航控制器,其他人正在使用某些视图控制器的强引用并在视图控制器中使用单例模式为了决定是初始化视图控制器还是只显示它
  • 我的视图控制器没有取消初始化,我知道我的主视图不能,因为它是初始视图,但既然 ios 无论如何都会重新初始化它,为什么不卸载它呢?

基于 Swift 的游戏使用 SpriteKit 处理视图控制器的正确方法是什么?下面你可以看到我的初始视图控制器(主页)显示了一个带有简单播放按钮的 SKScene,该按钮调用 play() 函数来继续选择关卡

import UIKit
import SpriteKit

class Home : UIViewController {
    private var scene : HomeScene!

    override func viewDidLoad() {
        print(self)
        super.viewDidLoad()
        self.scene = HomeScene(size: view.bounds.size)
        self.scene.scaleMode = .ResizeFill
        let skView = view as! SKView
        skView.showsFPS = true
        skView.showsNodeCount = true
        skView.ignoresSiblingOrder = true

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(play), name: Constants.Events.Home.play, object: nil)

        skView.presentScene(self.scene)
    }

    override func viewDidDisappear(animated: Bool) {
        super.viewDidDisappear(animated)
        let v = view as! SKView
        self.scene.dispose()
        v.presentScene(nil)
        NSNotificationCenter.defaultCenter().removeObserver(self)
        self.scene = nil
        self.view = nil
        print("home did disappear")
    }

    func play() {
        self.performSegueWithIdentifier("home_to_levelselection", sender: nil)
    }

    deinit {
        print("Home_VC deinit")
    }
}

【问题讨论】:

  • 对我来说听起来像是糟糕的关卡设计,除非您计划将多个视图用于多种用途,坚持使用 1 个视图控制器和 1 个 skview,并仅使用 presentScene 在不同场景之间跳转
  • 一如既往,你是堆栈溢出的主人。当我见到你时,总是正确的建议。
  • 有趣,我只是在做我从所有教程甚至下载的应用程序模板中看到的内容。我总是看到多个视图,例如 Home、Settings、GameScreen 和 GameOver。只用一个 ViewController 处理所有事情是不是太难了? @Knight0fDragon 你能告诉我一些我可以读到这个想法的网站吗?
  • 不幸的是我没有,但是不,在 1 个视图控制器中处理所有事情并不难。我能想到的最简单的方法是您的视图控制器,您的视图几乎是 Sprite Kit 环境的外壳,您应该将场景文件视为您的“视图控制器”。唯一的问题是你还没有一个很好的 GUI 设置来处理多个场景,就像你对故事板所做的那样。也许在 XCode 的未来更新中,场景编辑器会允许它。
  • 你没看我的回答吗?

标签: ios swift uiviewcontroller sprite-kit


【解决方案1】:

你的方式似乎很复杂,基本上呈现 3 个场景。它不是你应该为 SpriteKit 游戏做的,你只需要 1 个视图控制器(GameViewController)。

从 GameViewController(例如 HomeScene)加载您的第一个场景,仅此而已。 直接在 HomeScene 中创建您的 playButton 和其他 UI。为您的 UI 使用 SpriteKit API(SKLabelNodes、SKNodes、SKSpriteNodes 等)。

你永远不应该在 SpriteKit 中真正使用 UIKit(UIButtons、UILabels)。有一些例外,比如可能使用 UICollectionViews 来制作大量级别的选择菜单,但基本的 UI 应该使用 SpriteKit API 完成。

谷歌有很多关于如何创建精灵工具包按钮、如何使用 SKLabelNodes 等的教程。Xcode 有一个 SpriteKit 级别编辑器,因此您可以在视觉上与故事板相似。

从 HomeScene 过渡到 LevelSelect 场景和从 GameScene 过渡,反之亦然。它超级容易做到。

/// Home Scene
class HomeScene: SKScene {

  ...

   func loadLevelSelectScene() {

       // Way 1
       // code only, no XCode/SpriteKit visual level editor used
       let scene = LevelSelectScene(size: self.size) // same size as current scene 

       // Way 2
       // with xCode/SpriteKit visual level editor
       // fileNamed is the LevelSelectScene.sks you need to create that goes with your LevelSelectScene class. 
       guard let scene = LevelSelectScene(fileNamed: "LevelSelectScene") else { return }        


       let transition = SKTransition.SomeTransitionYouLike
       view?.presentScene(scene, withTransition: transition)
    }  
}

/// Level Select Scene
class LevelSelectScene: SKScene {
   ....

     func loadGameScene() {

        // Way 1
        // code only, no XCode/SpriteKit visual level editor used
        let scene = GameScene(size: self.size) // same size as current scene 

        // Way 2
        // with xCode/SpriteKit visual level editor
        // fileNamed is the GameScene.sks you need to create that goes with your GameScene class. 
        guard let scene = GameScene(fileNamed: "GameScene") else { return }


       let transition = SKTransition.SomeTransitionYouLike
       view?.presentScene(scene, withTransition: transition)
    } 
}

/// Game Scene
class GameScene: SKScene {
   ....
}

我强烈建议您从头开始使用故事板和 ViewController 方法,只使用不同的 SKScene 和 1 个 GameViewController。

希望对你有帮助

【讨论】:

  • 谢谢,效果很好。我现在只有一个视图控制器和多个场景。每次我呈现新场景并在我跳回来时重新启动时,我实际上都会处理当前场景。唯一的例外是当我点击我的 GameScene 上的暂停按钮时,我会在 GameScene 暂停时显示一些弹出视图,我会弄清楚如何做到这一点。但这个问题得到了回答。
  • 太棒了,不客气。关于弹出菜单,最简单和最干净的方法是创建 SKSpriteNode 或 SKNode 的子类。给它你想要的大小,然后将你的暂停标签、恢复按钮等添加到该类。不仅仅是在需要时将菜单类添加到场景中。您可以使用 SKActions 对其进行动画处理、淡入等等。
  • 非常感谢,我会试试的。
  • 不客气。如果您遇到问题,请再问一个问题。
【解决方案2】:

转到转场并在您不希望将前一个视图控制器保留在堆栈中的任何地方使用 Show Detail Segues。请记住,每当您返回到这些视图控制器时,您都必须适当地重新初始化它们。

如果您注意,每次看到视图出现时都会加载viewDidAppear,而在您当前的设置下,只会在最初调用viewDidLoad,如果您返回到viewController,则只有viewDidAppear会被调用。

当您使用转场从viewController 转换出来时,会调用prepareForSegue,但仅当您使用显示详细信息转场(或具有这些特定属性的自定义转场)时才会调用deinit(),因为正如您所说,视图已加载到内存中,因此可以更轻松地检索它。

【讨论】:

  • 我知道您已经尝试回答这个问题,但建议 OP 反对这种方法更好,因为它不是您在 SpriteKit 中应该做的。
  • @crashoverride777 为什么使用 SpriteKit 会使用不同的方法?保持视图状态更重要?
  • 哦,嗯,我回答了他的问题。你给了他一个更好的问题并回答了它=P
  • 对不起,我只是说,最好推荐正确的方法,而不是使用错误的方法。 SpriteKit 与 UIKit 不同。在 UIKit 中,您将 ViewControllers 用于 UI,而在 SpriteKit 中,您将 SKScenes 用于 UI(只有 1 个呈现场景的 viewController)。当您转换到新场景时,场景会自动解除分配,并且由于您只有 1 个视图控制器,因此不需要解除分配。看看苹果示例游戏 DemoBots。 OP 尝试制作 SpriteKit 游戏的方式不正确。
  • 在需要使用它的一周内,我会比下一个人更了解它。我只是非常专注于我目前的技能。
猜你喜欢
  • 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
相关资源
最近更新 更多