SceneKit 游戏架构

SceneKit game architecture

我正在尝试为 SceneKit iOS 游戏设计架构。我在下面展示了我目前首选的两个 粗略 高级 object diagrams/graphs。

我目前只关注高级架构,即管理 Menu/GameLevel/Config/Paused 应用状态之间的转换并使应用 data-driven,以便它可以处理多个级别等。一次我得到这个工作,然后我将解决较低级别的体系结构,例如对于 GameLevel 状态,我将使用 GameLevelModel object 来表示实际的游戏关卡逻辑。

很高兴听到哪一个看起来更有前途,是否有任何明显的陷阱或需要避免的事情?

版本 A) 基于 MVC,带有自定义容器和嵌套 VCs

我试图在这个版本中保持接近 MVC 范例。

应用程序的行为在其不同状态 (Menu/GameLevel/Config/etc) 之间发生显着变化,因此我将使用永久容器视图控制器,它将 manage/transition 在多个嵌套 VC 之间, 按照 Apple "View Controller Programming Guide for iOS" 的 parent-child 关系。这些实际上是整个 MVCs.

因为 none Apple 的容器控制器(UINavigation-、UISplitScreen-、UITabBar-)是合适的(我需要整个屏幕)我将有一个自定义容器 VC (RootViewController)将始终有一个嵌套的 child VC。每个 child VC 的视图将始终完全覆盖容器的视图。 child VCs 之间的转换将由 RootVC 管理并由其 StateMachine 驱动。

每个 child VC(实际上是整个 MVC)在其相应状态变为当前状态时创建,然后在被下一个状态替换时释放,不再分配需要。类似于 UINavigationController 如何处理它包含的 VCs.

我对这个架构唯一的担心是它看起来有点笨重。 SceneKit 看起来像是设计用于 单个 SCNView,并在我们需要更改 3D 内容时在多个 SCNScenes 之间转换。这也提供了更多的过渡选项。

                         --------------------------
                         |   RootViewController   |
                         --------------------------
                           |  |   |        |    |
     ------------------    |  |   |        |    |      ------------------
     | GameLevelsData |-----  |   |        |    -------|     SCNView    |
     ------------------       |   |        |           ------------------
     ------------------       |   |        |
     |   PlayerData   |--------   |        |
     ------------------           |        |
                                  |        |
                       ----------------    |
                       | StateMachine |    |
                       ----------------    |
                         |                 |
                         |-(*MenuState)    |
                         |-(LevelState)    |
                         |-(ConfigState)   |
                                           |
                                           |
                         ------------------------
                         |  MenuViewController  |
                         ------------------------
      -----------------     |                |      ------------------
      |   MenuData    |------                -------|  MenuSCNView   |
      -----------------                             ------------------

   (child VCs for other States, Level-/Config-/etc are created as needed)
                                           |
                         ------------------------
                         |  LevelViewController |
                         ------------------------
     ------------------     |                |      ------------------
     | GameLevelModel |------                -------|  LevelSCNView  |
     ------------------                             ------------------

版本 B)Scene-based(一个 VC,多个场景)

我将只有一个视图控制器 (GameViewController) 及其视图 (SCNView),它们将持续整个应用程序生命周期。 (我将 没有 容器和嵌套 VC 在这里。)

应用程序状态 (Menu/GameLevel/Config/etc) 之间行为和内容的变化是通过场景和不同 TouchHandler 之间的唯一视图转换实现的(每个状态的 UIResponder sub类)给定 First Responder 状态时相关。

(Menu-、GameLevel- 等)TouchHandler 类 只会覆盖 touchesBegan/Moved/EndedcanBecomeFirstResponder,这样我就可以拦截触摸事件,而不会膨胀单个控制器和视图。 None 我的其他 object 是 UIResponders。我还没有完全测试这部分。

对于每个应用程序状态,将创建该状态所需的 objects,例如相应的场景、TouchHandler(在 GameLevelState 中时具有游戏级别逻辑的 GameLevelModel)等,并在进入下一个状态时释放。

我目前的问题是,当使用 SKTransition 在单个 SCNView 上的多个 SCNScenes 之间转换时,在 5-20 次转换后会出现内存泄漏。我尝试通过简化场景来解决问题,在解除分配之前删除所有操作和节点,但没有任何帮助。唯一避免泄露的方法就是避免SKTransition,直接将场景赋给View的场景属性scnView.scene = scnScene。所以这将需要我为自己的过渡设置动画。

                         --------------------------
                         |   GameViewController   |
                         --------------------------
                           |  |   |             |
     ------------------    |  |   |             |      ------------------
     | GameLevelsData |-----  |   |             -------|     SCNView    |
     ------------------       |   |                    ------------------
     ------------------       |   |                            |
     |   PlayerData   |--------   |                            |
     ------------------           |                            |
                                  |                            |
                       ----------------                        |
                       | StateMachine |                        |
                       ----------------                        |
                         |                            ------------------
                         |-(*MenuState)---------      |  MenuSCNScene  |
                         |-(LevelState)     |  |      ------------------
                         |-(ConfigState)    |  |
                                            |  |
                     --------------------   |  |
                     | MenuTouchHandler |---|  |
                     --------------------      |
                      -----------------        |
                      |   MenuData    |---------
                      -----------------

              (When entering, each state creates objects of corresponding
              dedicated subclasses for its Model, TouchHandler and Scene)
                                            |  |              |
                    --------------------    |  |     -------------------
                    | LevelTouchHandler |---|  |     |  LevelSCNScene  |
                    --------------------       |     -------------------
                     ------------------        |
                     | GameLevelModel |---------
                     ------------------

提前致谢。

我不太理解你的术语,尤其是 "child MVC" 的概念。 iOS 中的首字母缩略词 MVC 通常是指模型-视图-控制器模式,在这种情况下我不知道子 MVC 是什么。

多个 SCNView 没有问题,选项 B 中显示您将它们用于不同的目的。请记住,您不必在 SCNView 中完成所有操作。您仍然可以使用 UIView/NSView,甚至是 SKView。将 SKView 用于菜单和控件是一种常见的方法。

实施您自己的触摸处理程序和过渡动画在我看来是矫枉过正。使用那里的东西。这就是 SceneKit 的用途。

我希望看到您将游戏逻辑从视图控制器逻辑中分离出来。考虑一下当您想将游戏转移到 tvOS 或 macOS 时会发生什么。如果游戏逻辑在 VC 中,您将有很多工作要做。将它们分开还可以避免 MVC 的其他含义之一,"Massive View Controller",试图一次处理所有任务的臃肿混乱。

许多在线教程和代码示例将所有内容都放在一个 game/view 控制器中 class,虽然这对于简单的示例和玩具问题来说很好,但这并不是一种有助于清洁代码的做法,很好维护和单元可测试性。如果我的游戏模型逻辑在单独的 class 中(可能拥有状态机),我可以轻松地为那部分编写单元测试。

我将解释一个我实际从事的真实游戏设计,它基于 Apple 为 SceneKit 提供的示例代码的设计。

正如@HalMuelller 正确指出的那样,您需要将游戏逻辑提取到独立于平台的层中,即使您正在为一个平台编写游戏,这也是一个好主意,因为这将有助于您进行测试。

如上图 class 所示:

右侧是整个游戏逻辑,以 Game class 开始,它实际上用作场景和物理委托。这只是一个普通的旧 ObjC 或 Swift class。

Game class 的主要职责之一是根据 SceneKit/Model IO 支持的文件或根据您的情况以编程方式构建场景 SCNScene

Game class 也指其他 Model class 类,例如加载 3D 骨骼动画的 AnimationPlayer class里面包含了他的统计数据等球员数据,这里的Player指的是比赛中的一些球员,比如棒球运动员。

你还可以看到PlayerEntityclass是由BatterComponent组成的,这是基于GamePlayKit的Entity-Component设计。现在 BatterComponent 有一个 StateMachine 来处理击球手在不同状态之间的转换。

另一个重要的组件是 BatterControlComponent,只有在用户击球时才将其添加到实体中。这也是一个依赖于平台的组件,因此您可以在 macOs/iOS 目标中独立编写它。同样,击球手控制组件由状态机支持,因为状态机也可用于处理控制转换。此控件实现为 SpriteKit 场景,覆盖在 SCNScene.

之上

如果您正在投球,那么您可以使用 PitcherComponent 由另一个状态机等支持的玩家实体进行配置。


在class图的左边,你可以看到一个GameViewController,它设置了一个SCNView,还实例化了一个Game对象和这个游戏对象的场景是您的游戏视图控制器场景视图呈现的内容。您还要确保将场景视图委托设置为游戏对象。


你必须认真参考Apple的SceneKit示例代码和GameKit示例代码。在 Apple 开发人员中,示例代码部分搜索:SceneKitGameplayKit


总的来说,您必须根据您的游戏要求,使用普通的旧 Swift/ObjC classes 和 GamePlayKit 中的适当模式设计一个独立于平台的游戏逻辑层。


关于生产使用的说明。看到 this memory leak issue and ,所以我不太确定 SceneKit 是否已经准备好迎接黄金时段,但 Google 是你的朋友。话虽如此,如果您已经在 Apple 平台上工作,SceneKit 真的很棒,因此如果您没有使用任何游戏引擎并且将编写游戏作为一种爱好或学习游戏,那么学习新游戏引擎的周转时间会更少编程。我相信 SceneKit 每年都在改进,在接下来的 2 个版本(WWDC 17、18)中,它应该是一个真正完善的 Apple 平台游戏引擎。根据我的经验,这些是我的 2 美元,YMMV。