此时此刻,你已经十分清楚Sprite Kit可以做什么以及它是如何做的。你知道如何将节点添加到场景并让那些节点执行动作——创建游戏可玩性(gameplay)的构建块(building blocks)。你可能会错过的是更大的蓝图。也就是说,你需要了解如何使用Sprite Kit规划并开发游戏和工具。为了发挥Sprite Kit的最大功效,你需要知道:
· 如何组织你的游戏到场景和过渡中
· 何时子类化Sprite Kit类
· 如何存储你游戏的数据和美术
· 如何使用构建你自己的工具来创建Sprite Kit内容并导出该内容供你的游戏使用
SpriteKit提供了不仅仅是你的游戏的图形层,它还提供使Sprite Kit易于集成到你的自定义游戏工具的功能。通过集成Sprite Kit到你的游戏工具,你可以在工具中构建你的内容,并直接读取到你的游戏引擎中。数据驱动的设计,允许美工、游戏设计师和游戏程序员协作构建游戏的内容。
场景是创建Sprite Kit内容的基石。当你开始一个新的游戏项目时,你的任务是定义需要哪些场景以及何时在这些场景之间发生过渡。场景通常代表出现在玩家前的游戏或内容的模式。通常情况下,很容易看出你何时需要一个新的场景:如果你的游戏需要更换屏幕上的所有内容,过渡到一个新的场景。
设计你的游戏的场景及它们之间的转换,与在一个传统的iOS应用程序的视图控制器的作用相似。在一个iOS应用程序中,内容由视图控制器实现。每个视图控制器创建一组视图的集合来绘制内容。起初,一个视图控制器由窗口展示。然后,当用户与视图控制器的视图交互时,可能会触发一个到另一个视图控制器及其内容的过渡。例如,在表视图中选择一个项目可能会弹出一个细节视图控制器来显示所选项目的内容。
场景没有默认的行为,像一个传统的iOS应用中的storyboadrd那样。相反,你定义并实现场景的行为。这些行为包括:
· 何时创建新的场景
· 每个场景的内容
· 何时在场景之间的发生过渡
· 用于执行过渡的视觉效果
· 数据如何从一个场景传输到另一个场景
例如,你可以实现一个类似于一个segue的模型,新的场景总是在这里的过渡上实例化。或者,你可以设计你的游戏引擎使用它坚持维持的场景。每一种方法都有它的优点:
· 如果场景在每次过渡发生时都要实例化,它始终是在一个干净的已知的状态中创建。这意味着,你不必担心重置场景的任何内部状态,这些状态往往可以有细微bug。
· 如果场景是持久性的,那么你就可以过渡回到场景,并让它恢复你离开该场景时的相同的状态。这种设计适用于任何类型的你需要在多个场景的内容之间快速过渡的游戏。
通常情况下,一个新的场景会是分阶段开发的。在开始时,你可能会使用测试的应用程序和实验的想法来了解Sprite Kit是如何工作的。但后来,随着你的游戏变得更加复杂,你的场景需要适应。
在测试应用程序和一些简单的游戏中,你的逻辑和代码都在场景子类中。场景操作的节点树和树中每个节点的内容,根据需要而运行动作或改变其他行为。该项目是如此地简单,以致所有的代码都可以留在一个单一的类中。
项目的第二阶段时,通常在渲染或游戏逻辑开始变得更长或更复杂时发生。在这个阶段,你通常开始抽离(break out)特定的行为并在其他类中实现它们。例如,如果你的游戏包括一个摄像头的概念,你可能会创建一个CameraNode
类来封装相机的行为。然后,你可能创建其他节点类封装其他行为。例如,你可能会创建单独的节点类来表示在你的游戏中的单元(units)。
在最复杂的项目中,人工智能等概念变得更加重要。在这些设计中,你可能最终创建独立于Sprite Kit工作的类。这些类的对象进行代表场景的工作,但并不特别依赖于它。这些类通常从你的Sprite Kit子类中提取,在你意识到你许多方法实现游戏逻辑而并没有真正触及任何Sprite Kit内容的时候。
在Sprite Kit渲染一帧时,它剔除(cull)所有在屏幕上不可见的节点。从理论上讲,这意味着你可以简单地保持所有内容到场景,并让Sprite Kit做所有的工作来管理它。而对于适度渲染要求的游戏,这样的设计也许就足够了。但是,随着你的游戏变得更大和更复杂,你需要通过Sprite Kit做更多的工作以确保良好的性能。
通常情况下,一个节点需要成为节点树的一部分,因为:
· 它有一个相当不错的机会在不久的将来被渲染
· 该节点运行准确的游戏操作所需要的动作
· 节点具有准确的游戏操作所需要的物理体
当一个节点不符合这些要求中的任意一个,通常最好是把它从树上移除,特别是如果它自身有许多子节点。例如,发射器节点往往添加特殊效果,而根本不影响游戏。它们发射大量的粒子,所以它们的渲染代价可能很昂贵。如果你有大量的发射器在场景中,但在屏幕外,场景可能需要处理数百或数千无形的节点。更好的做法是移除发射节点,直到它们变得可见。
通常,剔除算法的设计要根据自己的游戏。例如:
· 在赛车游戏中,玩家通常是围绕赛道以一个一致的方向行驶。正因为如此,你通常可以预测在不久的将来将看到什么样的内容并预加载它。随着玩家通过赛道前进,你可以移除玩家不再可以看到的节点。
· 在冒险游戏中,玩家可能处在一个滚动的环境,并允许他们能够往任意方向移动。当玩家在世界中移动时,你也许可以预测到哪种地形在附近而哪种地形不在。然后,只有包含局部内容的地形。
当内容总是被一次添加和移除时,考虑使用一个中间(interim)节点对象来收集的所有内容。这允许你调用一个单一的方法就添加或移除一个大组内容。
在你首次设计一个Sprite Kit游戏时,可能看起来像是场景类是做了很多的工作。调整(tuning)你的应用程序的部分过程,是决定场景是否执行一个任务,或是否应该由游戏中的一些其他对象这样做。例如,你可能会在以下时候考虑将工作移到另一个对象:
· 内容或应用程序逻辑由多个场景共享
· 内容或应用程序逻辑的建立(set up)特别昂贵,且只需要执行一次
例如,如果你的游戏在所有的游戏玩法(gameplay)中使用相同的纹理,你可以创建一个特殊的加载类,在启动时运行一次。你执行一次加载纹理的工作,然后让它们留在内存中。如果场景对象被删除并重新创建来重启游戏,纹理并不需要被重新加载。
设计新的游戏需要你子类化SKScene
类。然而,Sprite Kit中的其他节点类也设计成可子类化,这样你就可以添加自定义的行为。例如,你可能会为子类化SKSpriteNode
类来添加你的游戏专用的AI逻辑。或者,你可能会子类化SKNode
类来创建一个类实现场景中的一个特定的绘图层。如果你想直接在一个节点中实现交互性(interactivity),你必须创建一个子类。
当你设计一个新的节点类时,有一些重要的针对于Sprite Kit的实现细节需要了解。但是,你还需要考虑新的类在游戏中的作用,以及类的对象如何与其他对象交互。你需要创建定义良好(well-defined)的类接口和调用约定(convention)来允许对象交互操作(interoperate),而没有微妙的bug减缓你的开发进程。
下面是创建自己的子类时要遵循的重要指南:
· 所有的标准节点类都支持NSCopying
与NSCoding
协议。如果你的子类添加新的属性或实例变量,那么你的子类也应该实现这些行为。这种支持是必不可少的,如果你打算在你的游戏中复制节点或使用归档来构建你自己的游戏工具。
· 虽然的节点与视图类似,但你不可以添加新的绘图行到节点类。你必须通过节点的现有方法和属性开展工作。这意味着,要么控制节点自身的属性(如改变精灵的纹理),要么添加额外的节点并控制它们的行为。在任意一种情况下,你需要考虑你的类将如何与其他部分的代码相互作用。你可能需要构建自己的调用约定,以避免细微的渲染bug。举例来说,一个通用的约定是,一个对于创建并管理自己的子节点的节点对象,应避免添加子节点到该节点对象上。
· 在许多情况下,期望添加在场景的预处理和后处理阶段可以调用的方法。这可以让你从你的场景子类中移除这些行为,并移到为一个特定的游戏对象处理所有行为的类中。
· 如果你想在一个节点类中实现事件处理,则必须为iOS和OS X 实现单独的事件处理的代码。在OS X上SKNode
类继承自NSResponder
,而在
iOS上继承自UIResponder的
。
· 在一些游戏的设计中,你可以依赖这样的事实:特定组合的类总是要在一个特定的场景一起使用。在其他设计中,你可能想要创建可用于在多个场景中的类。复用对你的设计越重要,你应该花越多的时间为对象设计干净的接口来互相作用。当两个类是互相依赖的,使用代理(delegation)打破这种依赖。大多数情况下,你这样做是通过在你节点上定义一个代理和代理要实现的协议(protocol)。你的场景(或另一个节点,如节点的父节点)实现了这个协议。这允许在多个场景中复用你的节点类,而不需要知道场景类。
构建节点树的很大一部分工作是组织需要绘制的图形内容。首先需要绘制什么?最后需要绘制什么?这些东西是如何渲染的?
在设计节点树时,考虑了以下建议:
· 不要直接添加内容节点或物理体到场景。相反,添加一个或多个SKNode
对象到节点树来代表你的游戏中不同层次的内容,然后再使用这些层对象。使用层,可以让你精确控制每一层的行为。例如,你可以旋转某一层的内容而不旋转所有的场景的内容。它也让你通过其他代码移除或替换部分场景渲染变得更容易。例如,如果游戏分数和其他信息显示在提示显示层(heads-up display layer),那么在你想进行截图时可以移除该层。在一个非常复杂的应 用程序中,你可能会持续使用这种模式,通过添加子节点到一个层节点让节点树越来越深。
当你使用层来组织你的内容时,考虑层之间如何彼此交互。他们知不知道任何有关对方内容的东西?更高层次的层为了渲染它自己的内容,是否需要知道任何有关较低层次的层如何渲染的东西?
· 有节制地使用切割节点和效果节点。两者都非常强大,但也可能开销昂贵,特别是当它们一起嵌套在节点树内的时候。
· 只要有可能,一起渲染的节点,应该使用相同的混合模式。如果一个节点的所有的子节点使用相同的混合模式和纹理图册,那么Sprite Kit通常可以在一个单一绘图通道(drawingpass)中绘制这些精灵。另一方面,如果子节点组织起来,以致每个新的精灵的绘图模式发生变化,那么Sprite Kit可能以每个精灵一个绘图通道的方式执行,这是相当低效的。
· 当设计发射器效果时,尽可能使用低粒子出生率。粒子并不是免费的,每个粒子都添加渲染和绘制的开销。
· 默认情况下,精灵和其他内容使用alpha混合模式进行。如果精灵的内容是不透明的,例如一个背景图像,使用SKBlendModeReplace
混合模式。
· 使用游戏逻辑和匹配Sprite Kit的坐标和旋转约定(convention)的美术资产。这意味着定向插图到右边。如果你定向插画到其他方向,你将需要转换美术使用的约定和Sprite Kit使用的约定之间的角度。例如,如果插图定向为向上,那么你将在角度上添加PI/2
弧度来从Sprite Kit的约定转换到你的美术的约定,反之亦然。
· 在SKView
类中开启诊断信息。使用的帧率作为常规的性能诊断,并使用节点和绘图通道计数来进一步了解如何呈现内容。你还可以使用Instruments及其OpenGL诊断工具,查看更多关于你的游戏时间花在哪里的信息。
· 在各种真实的硬件设备上测试你的游戏。在许多情况下,每个Mac或iOS设备上的CPU资源和GPU资源的平衡是不同的。在多个设备上进行测试,帮助你确定你的游戏是否在大多数设备上运行良好。
在任意给定的时间,你的游戏管理大量的数据,包括场景中的节点的位置。但它也包括静态数据,如:
· 美术资产及正确渲染插图所需的数据
· 水平(level)或拼图(puzzle)布局
· 用于配置游戏的数据(如怪物的速度和它攻击时造成多大损害)
只要有可能,尽量避免直接在游戏代码中嵌入你的游戏数据。数据更改时,你不得不重新编译游戏,这通常意味着程序员要参与设计变更。相反,数据应该对于代码保持独立,这样一个游戏设计师或美工可以直接更改数据。
存储游戏数据最好的地方要依赖于数据在你的游戏哪里使用。对于与Sprite Kit无关的数据,用属性列表存储在你的应用程序bundle中是一个很好的解决方案。然而,对于Sprite Kit数据,你有另一种选择。因为所有Sprite Kit类都支持归档,你可以为重要的Sprite Kit对象简单地创建归档,然后在你的游戏中包含这些档案。例如,你可以:
· 存储游戏关卡作为一个场景节点的归档。此归档包括场景、节点树中它所有的后代节点以及它们所有的连接的物理体、联合和动作。
· 为特定的预配置节点存储个别的归档,比如每个怪物节点。然后,当需要创建一个新的怪物时,你从归档中加载它。
· 存储已保存的游戏作为场景归档。
· 构建自己的工具来让归档可以编辑。然后,你的游戏设计师和美工可以用这些工具来创建你的游戏对象和并使用你的游戏能读取的格式归档它们。你的游戏引擎和工具将共享通用的类。
· 你可以存储Sprite Kit数据到属性列表中。你的游戏加载属性列表,并用它来创建游戏资产。
以下是一些使用归档的指南:
· 使用节点的userData
属性来存储游戏相关的数据,尤其是如果你不实现自己的子类。
· 避免硬编码引用到特定的节点。相反,给令人关注的节点一个独特的name
属性并在节点树中搜索它们。
· 不能归档调用块的自定义动作。你需要创建并添加那些动作到你的游戏中。
· 大多数节点对象提供所有必要的属性,以确定它们是什么,以及他们是如何配置的。然而,动作和物理体没有。这意味着,当开发自己的游戏工具时,你不能简单地归档动作和物理体并使用这些档案来储存你的工具数据。相反,档案只应该是来自你的游戏工具的最终输出。
下表描述了Sprite Kit编程指南的变更。
日期 |
说明 |
2013-06-10 |
介绍了如何使用Sprite Kit来实现游戏和其他任意的具有动画特性的应用程序的新文件。 |