精灵是用于创建大部分场景内容的基本构建块,所以在转到其他Sprite Kit节点类之前先了解精灵是有用的。精灵用SKSpriteNode
对象表现。一个SKSpriteNode
对象,要么绘制成一个由纹理映射(mapped)的矩形,要么绘制成一个彩色无纹理的矩形。纹理精灵更常见,因为它们代表了你把自定义插图引进场景的主要方式。这个自定义插图可能代表你的游戏的人物角色、背景元素甚至是用户界面元素。但基本的策略是一致的。一个美工创建图像,然后你的游戏加载它们作为纹理。然后你用那些纹理创建精灵,并把它们添加到场景中。
创建一个纹理精灵的最简单方法是让Sprite Kit为你创建的纹理和精灵。你把插图存储在应用程序bundle中,然后在运行时加载它。清单2-1显示了这个代码是多么的简单。
清单2-1 从存储在bundle中的图像创建一个纹理的精灵
当你以这种方式创建一个精灵,你可以免费得到很多的默认行为:
· 精灵以匹配纹理尺寸的框架(frame)来创建。
· 精灵以它的位置为中心来渲染。精灵的frame
属性指定的矩形定义了它所涵盖的面积。
· 精灵纹理在帧缓冲区(framebuffer)中是半透明的(alpha-blended)。
· 一个SKTexture
对象被创建并附加到精灵上。此纹理对象每当精灵节点在场景中时自动加载纹理数据,它是可见的,而且对渲染场景是必要的。稍后,如果精灵从场景中移除或不再可见,如果需要那些内存用于其他用途,Sprite Kit可以删除纹理数据。这种自动内存管理简化但并不能消除在管理你游戏中的美术资产(art assets)方面你需要做的工作。
默认的行为给你一个有用的基础来创建一个基于精灵的游戏。你已经懂得了足够的知识去添加插图到你的游戏,创建精灵,并运行这些精灵的动作来做一些有趣的事情。随着精灵屏幕内外移动,Sprite Kit尽可能有效地管理纹理和绘制动画的帧。如果这对你已经足够,就花点时间去探索你能对精灵做些什么。或者继续阅读SKSpriteNode
类得到更深入的理解。一路上,你将获得其功能以及如何与美工和设计师交流这些功能的深入理解。并且你会学到更高级的使用纹理的方式以及如何提高基于纹理的精灵的性能。
你可以使用精灵的每个属性独立配置四个不同的渲染阶段:
· 可以移动精灵的框架,使纹理中的不同点放置在精灵节点的位置。参阅“使用锚点移动精灵的框架”。
· 可以调整精灵的尺寸。你控制当精灵的尺寸与纹理的尺寸不匹配时纹理如何应用到精灵。参阅“调整精灵的尺寸”。
· 可以在精灵的纹理应用到的精灵时对它着色。请参阅“对精灵着色”。
· 精灵可以使用其他的混合(blend)模式来结合其内容和帧缓冲区的内容。自定义的混合模式对发光(lighting)和其他特效是有用的。请参阅“混合精灵到帧缓冲区中”。
通常情况下,配置精灵执行定位、调整尺寸、着色、混合这四个步骤要根据用于创建精灵纹理的插图。这意味着你很少脱离插图设置属性值。你与美工合作以确保你的游戏配置精灵与插图匹配。
下面是一些也行你可以遵循的策略:
· 在你的项目中用硬编码值创建精灵。这是最快但在长期最不理想的方法,因为这意味着每当美术资产变动时必须更改代码。
· 使用Sprite Kit创建自己的工具,让你可以微调精灵的属性值。一旦你有一个你想要的方式配置的精灵,保存精灵到归档中。你的游戏在运行时使用归档创建精灵。
· 在存储在你的应用程序bundle的属性列表中存储配置数据。当精灵加载时,加载属性列表并使用它的值来配置精灵。这允许美工提供正确的各个值并在不改变代码的情况下进行更改。
默认情况下,精灵的框架及其纹理的中心定位在精灵的位置上。然而,你可能想纹理的不同部分出现在节点的位置。经常要作出这样的决定因为纹理描绘的游戏元素不是纹理图像的中心。
精灵节点的的anchorPoint
属性决定框架的哪一点定位在精灵的位置上。锚点在单位坐标系(unit coordinate system)中指定,如图2-1所示。单位坐标系的原点位于框架的左下角,而(1,1
)位于
框架的右上角。精灵的锚点默认为(0.5,0.5
)
,对应于框架的中心。
图2-1 单位坐标系
虽然你想要移动框架,你这样做是因为你想纹理的相应部分处于位置的中点。图2-2显示了一对纹理图像。在第一个图像中,默认的锚点在纹理位置的中心。第二个相反地,选择了图像的顶部一个点。你可以看到,当精灵旋转时纹理图像会围绕这一点旋转。
图2-2 改变精灵的锚点
清单2-2显示了如何将火箭的锚点放在前锥体处。通常,你在精灵初始化时设置锚点,因为它与插图对应。然而,你可以在任何时候设置此属性。框架会被立即更新,并且屏幕上的精灵会在场景下一次渲染时更新。
清单2-2 设定精灵的锚点
精灵的frame属性的尺寸是由其他三个属性的值决定的:
· 精灵的size
属性指定
精灵基准(无缩放)尺寸。当一个精灵使用代码清单2-1初始化时,这个属性的值被初始化为于精灵的纹理的尺寸相等。
· 然后基准尺寸根据精灵从SKNode
类继承来的xScale
与yScale
属性进行缩放。
例如,如果精灵的基准尺寸是32 ×
32
像素,而它的xScale
的值为1.0
且yScale
的值为2.0
,精灵的框架的尺寸是32
× 64
像素。
注:场景中精灵的祖先的缩放值也用于缩放精灵。这将改变精灵的有效尺寸,而不改变它的实际框架的值。请参阅“节点的很多属性适用于其后代”。
当精灵的框架大于它的纹理时,纹理被拉伸以覆盖框架。一般情况下,纹理会在整个框架中被均匀地拉伸,如在图2-3中所示。
图2-3 纹理位伸以覆盖精灵的框架
然而,有时你想使用精灵构建用户界面元素,如按钮或健康指示器。通常,这些元素包含固定尺寸的元素,如结束端点(end caps),它不应该被拉伸。在这种情况下,使部分的纹理不拉伸,然后拉伸纹理框架剩下的其余部分。
精灵的centerRect
属性控制缩放行为,该属性在纹理的单位坐标中指定。默认值是一个覆盖整个纹理的矩形,这就是为什么整个纹理被拉伸到整个框架的原因。如果指定了一个只涵盖了部分的纹理的矩形,你就创建了一个3x3
的
网格。在网格中的每个盒子有其自己的缩放行为:
· 网格的四个角中的纹理绘制的部分不进行任何缩放。
· 网格的中心在两个方向缩放。
· 中间的上下部分仅水平缩放。
· 中间的左右部分仅垂直缩放。
图2-4展示了一个纹理的特写视图,你可能会用它来绘制用户界面按钮。实际元素是28点×28点。四个角是12×12像素而中心是4×4像素。
图2-4 可伸缩的按钮纹理
清单2-3显示了这个按键精灵将如何初始化。centerRect属性根据纹理的中心矩形来计算。
清单2-3 设置中心矩形以调整拉伸行为
图2-5展示了即使在该按钮以不同的尺寸绘制时四个角仍保持不变。
图2-5 对不同尺寸的按钮应用按钮纹理
在把纹理应用到精灵之前,你可以使用color
和colorBlendFactor
属性
对它着色。默认情况下的颜色混合因子为0.0
,这表明纹理未经更改地使用。当你增加这个数字,更多的纹理颜色就会被混合颜色替换。例如,在你的游戏中的怪物受到伤害时,你可能要添加一个红色的色调(tint)给角色。清单2-4显示了如何将色调应用于精灵。
清单2-4 着色精灵的颜色
你也可以使用动作让颜色和颜色混合因素成为动画。清单2-5显示了如何短暂地给精灵调色,然后让它恢复正常。
清单2-5 颜色变化的动画
渲染的最终阶段是把精灵的纹理融合(blend)到其目标帧缓冲区。默认行为使用纹理的alpha值融合纹理与目标像素。但是,当你想添加其他的特效到场景时你可以使用其他融合模式。
你可以使用BlendMode
属性来控制精灵的融合行为。例如,附加混合模式在把多个精灵结合在一起时很有用,比如开枪(fire)或发光(lighting)。清单2-6显示了如何使用附加混合改变混合模式。
清单2-6 使用附加混合模式模拟发光
虽然Sprite Kit可以在你创建一个精灵时为你创建纹理,但在一些更复杂的游戏中,你需要对纹理有更多的控制。例如,你可能需要做以下任何一项:
· 多个精灵之间共享一个纹理。
· 在精灵创建后更改它的纹理。
· 通过一系列的纹理让精灵动起来。
· 用不直接存储在应用程序bundle中的数据创建纹理。
· 把节点树渲染成纹理。例如,你可能要对游戏设置进行截屏,在玩家完成了关卡(level)后展示给他或她。
你通过直接使用SKTexture
对象可以做所有这些事情。纹理对象是可应用于精灵的可复用的图像。你可以创建纹理对象独立于创造精灵。然后,你可以使用纹理对象来创建新的的精灵或改变现有精灵的纹理。它跟Sprite Kit为你创建纹理相似,但你对过程有更多的控制权。
清单2-7显示了一个类似清单2-1中显示的例子,但使它用纹理对象。在这种情况下,代码一次创建了多支火箭,全部来自相同的纹理。通常情况下,你会加载一次纹理,并保持对它的强引用,以便每次需要创建一个新的精灵时都可以使用它。
清单2-7 从bundle中加载纹理
纹理对象本身只是实际的纹理数据的一个占位符。纹理数据占用(intensive)更多的资源,所以当使用它的精灵在屏幕上且可见时,Sprite Kit只保存它在内存中。
通常情况下,存储在你的应用程序bundle的美术资产是不相干的图像,却是一起用于相同精灵 的图像的集合。例如,下面是一些常见的美术资产的集合:
· 一个角色的动画帧
· 用来创建游戏关卡或者迷宫的地形瓦片(terrain tiles)
· 用于用户界面控件的图像,如按钮、开关和滑块
如果你把这些逻辑分组看成单独的纹理,Sprite Kit和图形硬件必须运行得更加艰难来渲染场景,而且游戏的性能可能会受到影响。所以,Sprite Kit使用纹理图册把相关的图像收集起来。你指定哪些资产一起使用,然后Xcode会自动构建纹理图册。然后在你的游戏加载纹理图册时,Sprite Kit可以更好地管理性能和内存使用。
Xcode可以自动为你从图像集合构建纹理图册。欲了解更多信息,请参阅纹理图册帮助。
在创建一个纹理图册时,在收集太多的纹理与太少的纹理到图册之间,有一个平衡的做法。如果你使用的项目数量不足,那么纹理之间切换的开销可能仍然太大。如果你把太多的图像放在一个单一的图册中,那更多的纹理数据会存储在内存中。因为Xcode为你构建图册,它可以相对容易地在不同的图册配置之间切换。对你的纹理图册不同的配置做实验,并选择为你提供最佳性能的结合。
清单2-7中的代码,也可以用来从纹理图册中加载纹理。Sprite Kit首先搜索指定的文件名的图像文件,但如果它没有找到,那么它会在内置到应用程序bundle里面任何纹理图册内部搜索。这意味着,在你的游戏中你不必作出任何编码的更改来支持它。此设计还为美工提供了这样的能力,试验新的纹理而不需要重新构建(rebuild)你的游戏。美工把纹理拖放到应用程序bundle中,就可以自动发现它们(覆盖任何之前内置到纹理图册的版本)。一旦美工对纹理满意了,然后你就可以将它们添加到项目中且合并到你的纹理图册中。
如果你想显式使用纹理图册,你可以使用SKTextureAtlas
类。首先,你使用图册的名称创建一个纹理图册对象。然后,使用图册中存储的图像文件的名字查看各自的纹理。清单2-8显示了一个这样的例子。它采用了纹理图册装截一个角色的多个动画帧。代码加载这些帧,并将它们存储在一个数组中。
清单2-8 加载散步动画的纹理
如果你已经有一个SKTexture
对象,你可以创建新的纹理引用它的一部分。这是非常有效的,因为新的纹理对象引用内存中相同的纹理数据。这个功能跟纹理图册是类似的。通常情况下,如果你的游戏已经有了自己的自定义纹理图册格式,你就可以这样使用。在这种情况下,你负责存储这些存储在自定义纹理图册中的各个图像的坐标。
清单2-9显示了如何提取部分的纹理。矩形的坐标在单位坐标空间中。
代码清单2-9 使用纹理的一部分
除了从应用程序bundle加载纹理,你还可以从其他来源创建纹理:
· 使用SKTexture
初始化方法通过内存中正确格式化的像素数据、核心图像或对现有的纹理应用一个Core Image过滤器来创建纹理。
· SKView
类的textureFromNode
方法可以把一个节点树的内容渲染成纹理。纹理被指定好尺寸,以便它可以包含节点的内容和所有它的可见后代节点。
当你从应用程序bundle中的文件之外的其他来源创建一个纹理时,纹理数据不能被清除,因为Sprite Kit不保留用于生成纹理的原始数据的引用。基于这个原因,你应该有节制地使用这些纹理。一旦不再需要它们,马上移除对它们的强引用。
texture
属性指向它当前的纹理。你可以将此属性更改为指向一个新的纹理。下一次场景渲染一个新的帧时,它会用新的纹理来渲染。每次你更改纹理时,为了与新的纹理一致,你可能还需要更改其他的精灵属性,如
size
、anchorPoint
和centerRect
。一般,确保所有的插图都一致会更好,这样相同的值可用于所有的纹理会。也就是说,纹理应该有一个一致的尺寸和锚点定位,让你的游戏并不需要更新纹理以外的其他任何东西。
因为动画是一个非常常见的任务,你可以使用动作让一个精灵的一系列纹理都动起来。清单2-10中的代码显示了如何使用清单2-8创建的动画帧数组让精灵的纹理动起来。
清单2-10 通过一系列的纹理形成动画
Sprite Kit提供了渠道(plumbing),让你的活动或改变精灵的纹理。它不利用你的动画系统的特定设计。但是,这意味着你需要决定精灵可能会需要什么样的动画,并设计自己的动画系统来让这些动画在运行时切换。例如,一个怪物可能有步行,战斗,停顿(idle)和死亡的动画序列。你的代码来决定何时在这些序列之间切换。
使用Sprite Kit的一个主要优点是它自动为你执行了大量的内存管理。Sprite Kit从图像文件加载纹理,将这些数据转换成图形硬件可以使用的格式,并将其上传到图形硬件。Sprite Kit很擅长于确定当前帧纹理是否需要渲染。如果纹理不在内存中,它会加载纹理。如果纹理在内存中并且有一段时间没有使用,纹理数据会被丢弃,以便可以加载其他需要的纹理。
如果一次有太多没加载纹理的精灵变为可见,它可能无法在一个单一的动画帧内加载所有这些纹理。纹理加载的延迟可能会导致帧速率突然丢失,这是对用户可见的。Sprite Kit提供了在精灵变为可见之前预加载纹理的选项。因为你非常熟悉你的游戏的设计,你往往更清楚地知道什么时候即将要使用一套新的纹理。例如,在一个滚动的游戏中,当用户在宇宙间移动时,你知道玩家即将进入宇宙的哪一部分。然后你可以在动画的每一帧加载三两个纹理,这样当玩家到达那里时纹理已经在内存中了。清单2-11显示了如何加载纹理。
清单2-11 预加载纹理
预加载代码的正确设计要依赖于你的游戏的引擎。这里有两种可能设计要考虑:
· 当玩家开始一个新的关卡,预加载这个关卡的所有纹理。游戏被划分成各个关卡,每个关卡能保持所有纹理资产同时在内存中。这保证了所有纹理在游戏开始前就加载好,消除任何纹理加载的延迟。
· 如果一个游戏需要比可以适合内存更多的纹理,你需要动态地预加载纹理。通常,这意味着当你能确定它很快就需要会才预加载纹理。例如,在赛车游戏中,玩家总是在在同一方向移动,所以你预加载玩家即将看到的部分赛道的纹理。纹理在后台加载,取代赛道中最旧的纹理。在一个允许玩家时刻控制的冒险游戏中,你可能必须临时加载更多的纹理。
虽然纹理精灵是使用SKSpriteNode
类的最常见的方式,你也可以不用精灵创建精灵节点。类的在精灵缺乏纹理时发生变化:
· 没有纹理可拉伸,所以centerRect
参数被忽略。
· 没有着色步骤,color
属性用作精灵的颜色。
· 颜色的alpha
分量被用来确定精灵如何融合到缓冲区。
其他属性(size
、anchorPoint和blendMode
)照旧不变。
现在你对精灵知道更多了,请尝试以下一些活动:
· 在一个纹理图册中添加插图到你的项目。请参阅“创建一个纹理图册”。
· 加载纹理图册,并用它来创建新的精灵。请参阅“载入纹理从纹理图册”。
· 通过多帧动画让精灵动起来。请参阅清单2-10。
· 更改你的精灵的属性,看看它们的绘图行为怎么变化。请参阅“自定义纹理精灵。”
你可以在Sprite Tour示例中找到一些有用的代码。