SceneKit游戏架构

8

我正在尝试为一个SceneKit iOS游戏设计架构。下面展示了两个我目前首选的想法的草图,它们是当前高层次对象图。

目前我只关注高层次架构,即管理菜单/游戏等级/配置/暂停应用程序状态之间的转换,并使应用程序数据驱动,以便它可以处理多个等级等。一旦我完成这个,然后我将解决更低层次的架构,例如对于GameLevel状态,我将有一个GameLevelModel对象来表示实际的游戏等级逻辑。

很高兴听到哪个看起来更有前途,有什么明显的缺陷或需要避免的事情?

版本A)基于MVC,带自定义容器和嵌套视图控制器

我试图在此版本中保持接近MVC范例的模式。

应用程序在不同状态(菜单/游戏等级/配置等)之间的行为发生了显着变化,因此我将使用一个永久容器视图控制器,它将管理/转换几个嵌套的视图控制器,在父子关系中,如苹果的“iOS视图控制器编程指南”所述。这些将是完整的MVC。

由于苹果的任何容器控制器(UINavigation-, UISplitScreen-, UITabBar-)都不适用(我需要整个屏幕),因此我将有一个自定义的容器VC(RootViewController),它将始终具有一个嵌套的子VC。每个子VC的View将始终完全覆盖容器的View。子VC之间的转换将由RootVC管理,并由其状态机驱动。

每个子VC(实际上是完整的MVC)在其对应状态成为当前状态时创建,然后在它被下一个状态的VC替换并且不再需要时被解除分配。类似于UINavigationController如何处理其包含的VC。

我的唯一担忧是这种架构似乎有点沉重。SceneKit看起来是为使用单个SCNView并在需要更改3D内容时转换多个SCNScene而设计的。这也提供了更多的过渡选项。

                         --------------------------
                         |   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)基于场景(一个VC,多个场景)

我将只有一个视图控制器(GameViewController),它带有它的视图(SCNView),并且它们将持续整个应用程序的生命周期。 (这里不会有容器和嵌套的VC。)

在应用程序状态之间的行为和内容变化(菜单/游戏级别/配置等)是通过唯一的视图在场景和不同的TouchHandlers之间过渡实现的(每个状态的UIResponder子类)在相关时给予第一响应者状态。

(菜单,游戏级别等)TouchHandler类将仅覆盖touchesBegan/Moved/EndedcanBecomeFirstResponder,以便我可以拦截触摸事件而不会使单个控制器和视图膨胀。我的其他对象都不是UIResponders。我还没有测试这部分。

随着每个应用程序状态,将创建该状态所需的对象,例如相应的场景、TouchHandler、(当处于GameLevelState时具有游戏级别逻辑的GameLevelModel)等,并在进入下一个状态时进行解除分配。

我目前遇到的问题是,在使用SKTransition在单个SCNView上转换多个SCNScene时,经过5-20次转换后会出现内存泄漏。我尝试通过简化场景、在解除分配之前删除所有操作和节点来解决,但没有任何帮助。避免泄漏的唯一方法是避免使用SKTransition,并直接将场景分配给视图的场景属性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 |---------
                     ------------------

感谢您提前阅读。

我会在http://gamedev.stackexchange.com/上提出这个问题。 - David Lee
谢谢David,我也是这么想的,首先在那里寻找答案和解决方案,但是几乎没有找到什么,然后在这里检查,stackoverflow上有更多的东西。例如,在GameDev上搜索“SceneKit”只有27个结果,而在这里我得到了3400个 :) - Sulevus
2个回答

7
我将解释一个真实的游戏设计,这是我正在工作的,基于苹果提供的SceneKit示例代码设计而来。
正如@HalMuelller所指出的,您需要将游戏逻辑提取到平台无关的层中,即使您只为一个平台编写游戏,这也是一个好主意,因为它将有助于测试。

enter image description here

正如您在上面的类图中所看到的:
右侧是整个游戏逻辑,从一个名为Game的类开始,它实际上作为场景和物理代理。这只是一个普通的ObjC或Swift类。 Game类的主要职责之一是构建场景SCNScene,可以从SceneKit / Model IO支持的文件中构建,也可以根据您的情况进行编程构建。 Game类还涉及其他Model类,例如加载3D骨骼动画的Animation和包含玩家数据(例如统计数据)的Player类,在这里的Player指的是游戏中的某个玩家,例如棒球运动员。
您还可以看到有一个PlayerEntity类,由BatterComponent组成,这是基于GamePlayKit中的实体-组件设计。现在,BatterComponent具有一个StateMachine,用于处理击球手在各种状态之间的转换。
另一个重要组件是BatterControlComponent,仅在用户击球时将其添加到实体中。这也是一个平台相关的组件,因此您可以在macOS / iOS目标中独立编写此组件。同样,击球控制组件由状态机支持,因为状态机还可用于处理控件转换。该控件被实现为SpriteKit场景,作为SCNScene上层的覆盖物。
如果您正在投球,则使用PitcherComponent配置玩家实体,该组件由另一个状态机支持,以此类推。
在类图的左侧,您可以看到一个GameViewController,它设置了一个SCNView,并实例化了一个Game对象,该游戏对象的场景是您的游戏视图控制器场景视图呈现的内容。您还确保将场景视图委托设置为游戏对象。
你必须认真参考苹果的SceneKit和GameKit示例代码。在苹果开发者中心,示例代码部分搜索:SceneKitGameplayKit

总体而言,您必须使用纯粹的Swift/ObjC类和适当的模式从GamePlayKit中设计一个平台无关的游戏逻辑层,以满足您的游戏需求。


关于生产使用的注意事项。请参见此内存泄漏问题动画问题,因此我不确定SceneKit是否已经准备好了投入使用,但是Google是你的朋友。话虽如此,如果您已经在苹果平台工作,那么SceneKit真的很棒,因此相对于学习新的游戏引擎,周转时间会更短,如果您没有使用任何游戏引擎并正在编写游戏作为爱好或学习游戏编程,那么SceneKit非常适合您。我相信SceneKit每年都在改进,在接下来的2个版本(WWDC 17、18)中,它应该成为一个真正完善的针对苹果平台的游戏引擎。这些是基于我的经验的看法,可能因人而异。

感谢回复。我编辑(实际上是重写)了我的问题,以使其更清晰。目前我正在研究的架构只是高层部分。希望通过自上而下的方式将其设计为数据驱动型应用程序。一旦我完成这个,我将详细研究游戏级别模型(在图表中将其命名为GameLevelModel),并考虑采纳您的建议。我已经研究了苹果的样例和实体组件方法,但我可能只会使用简单的类层次结构来表示游戏对象,即2-3个类:玩家与敌人的飞船以及可收集物品。 - Sulevus
你是否计划使用SKTransition在SCNScenes之间进行转换?这可能是由你的GameViewController驱动的。我经常在5-20个场景转换后出现内存泄漏。然而,我必须提到我使用iOS 9,以便在我的发布中涵盖它,这些问题现在可能已经在iOS 10中得到解决。 - Sulevus
我已经编辑过,并附有有关生产使用的注释,涵盖了内存泄漏和动画问题。 - 3d-indiana-jones
将此回复标记为已接受,因为Dmsurti根据他的知识回答得最好,尽管仍有一些问题未解决,即:1)在单个SCNView上转换场景时可能会出现内存泄漏,2)游戏对象可能会变得过大,不过这可以通过组合来解决。 - Sulevus

0

我不太明白你的术语,特别是“子MVC”的概念。在iOS中,MVC缩写通常指模型-视图-控制器模式,在这种情况下,我不知道什么是子MVC。

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

实现自己的触摸处理程序和转换动画在我看来有些过度了。使用已有的功能即可。这就是SceneKit的用途。

我希望看到您将游戏逻辑与视图控制器逻辑分开。考虑一下当您想将游戏移植到tvOS或macOS时会发生什么。如果游戏逻辑在VC中,您将需要进行大量的解开操作。将它们分开还避免了MVC的另一个含义,“Massive View Controller”,这是一个试图同时处理所有任务的臃肿混乱。

许多在线教程和代码示例将所有内容都放在一个游戏/视图控制器类中,虽然这对于简单的示例和玩具问题来说是可以的,但它不是一种有利于编写干净代码、良好维护和可单元测试性的实践。如果我的游戏模型逻辑在一个单独的类中(可能拥有状态机),我可以轻松地为该部分编写单元测试。

谢谢,Hal。我编辑了问题(实际上是重写了),以使其更清晰。通过“child MVC”,我指的是嵌套在容器VC中的MVC,就像父子或容器嵌套关系一样。苹果经常在他们的iOS视图控制器编程指南中使用“child VC”,而我显然记得它为“child MVC”,因为我们有效地嵌套了控制器管理的整个MVC。关于游戏逻辑,我将在GameLevelModel对象中拥有它(从LevelData重命名)。我正在查看顶层架构,使应用程序数据驱动,但仍未到达游戏级别模型。 - Sulevus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接