运用一个优化过的动画系统、物理引擎和事件处理机制创作以精灵为基础的2D游戏。
Overview
SpriteKit是一个图形渲染和动画基础库,你可以用它给任意的纹理图片(或者说精灵)做动画。SpriteKit提供一个传统的渲染循环在决定帧内容和渲染帧之间交替进行。帧的内容和这些内容怎么变化由你决定。SpriteKit则利用图形硬件高效的渲染这些帧。SpriteKit为在你的内容上应用任意的动画和改变做了优化。这个设计让SpriteKit更适合那些要求更加灵活的动画处理的游戏和App。
精灵内容的绘制是通过在一个精灵View的里面呈现
Scenes
动画和渲染是通过一个SKView对象运行的。你把这个view放在一个window里面,然后在它上面渲染内容。因为它是一个view,它的内容能够在视图层级里和其他view相结合。
你游戏里面的内容被组织到一些scene里面去,这些scene通过SKScene对象呈现。一个scene持有一些精灵和其他将要渲染的内容。一个scene也实现每帧逻辑和内容处理。在任意给定的时间,视图呈现一个scene。只要一个scene被呈现出来,它的动画和每帧逻辑也自动执行。
为了使用SpriteKit创建一个游戏或者app,你可以创建一个SKScene类的子类,或者创建一个scene代理来执行主要的游戏相关任务。例如,你可能创建单独的scene类来展示一个主菜单,游戏界面和游戏结束后的内容。你可以在窗口中轻松的使用单独的SKView对象并且在不同的scene之间切换。当你切换场景时,你看可以使用SKTransition类在两个场景之间做动画。
一个节点树定义了在一个场景中出现什么
SKScene类是SKNode类的后代。当使用SpriteKit时,node是构成一切内容的基石,scene对象是一个节点对象树的根节点。scene和它的后代们决定绘制哪些内容,以及怎么渲染。
每个节点在坐标系里的位置都被它的父节点指定。一个节点也应用其他属性到它的内容和它子节点的内容上面。例如,当一个节点被旋转,它所有的子节点也会被旋转。你可以用节点树制作一个复杂的图像,然后通过调整最高层节点的属性来旋转,缩放,混合整个图像。
SKNode类不会绘制任何东西,但是它把它的一些属性应用到它的后代上。每种类型的可绘制内容通过一个不同的SpriteKit子类表现。一些其他的节点子类不绘制它们自己的内容,但是修改它们的后代的行为。例如,你可以用SKEffectNode对象来应用一个CoreImage滤镜到一个场景里的一整个节点树上。通过精确地控制节点树的结构,你可以决定节点被渲染的顺序。
所有的节点对象都是可响应对象,继承自UIResponder或者NSResponder,因此你可以继承任何节点类,并且创建新的类来接受用户输入。视图类自动扩展响应链来包含场景中的节点树。
想要了解更多信息,自己查看SKNode文档吧。
纹理(Textures,或者叫做贴图)持有可复用的图形数据
纹理由SKTexture对象表示,是一些用来渲染精灵的共享图像。无论什么时候你需要应用同样的图像到多个精灵上,就使用纹理。通常你通过加载存储在你的app bundle的图像文件创建纹理。然而,SpriteKit也可以在运行时从其他资源为你创建纹理,包括Core Graphics图像或者通过渲染一个节点树到一个纹理上。
SpriteKit通过处理加载纹理的底层代码从而使他们对图形硬件可用来简化纹理管理。纹理管理被SpriteKit自动管理。然而,如果你的游戏使用大量图像,你可以通过掌控这个过程中的一部分来提升性能。你主要通过明确的告知SpriteKit加载一个纹理来实现。
一个纹理图册是一组被用于你的游戏里的相关纹理。例如,你可能用一个纹理图册来存储所有需要给一个角色做动画的纹理或者所有需要用于渲染一个游戏背景的瓷砖。SpriteKit用纹理图册来提升渲染性能。
想要了解更多信息,自己查看SKTexture和SKTextureAtlas文档吧。
节点执行action(动作)来给内容做动画
一个场景的内容通过action来做动画。每一个action都是一个SKAction类的对象,你告诉node去执行action。然后,当场景处理动画帧的时候,action被执行。一些动作在一个动画帧之内就完成了,其他的一些动作在完成之前会在多个动画帧内造成变化。action最常见的用途就是对节点的属性进行动态变化。例如,你可以创建一些动作,它们能够移动一个节点,拉伸或者旋转它,或者让它透明。然而,action也可以改变节点树,播放声音,或者执行自定义代码。
action非常有用,但是你也可以组合一些action来创造更复杂的效果。你可以创建几组动作同时运行或者按照一定的顺序运行。你也可以让这些动作自动重复。
场景(scenes)也可以执行自定义的每帧处理。你重写你的场景子类的一些方法来执行额外的游戏任务。例如,如果一个节点每帧都需要移动,你可能每帧都直接调整它的一些属性,而不是用一个动作来完成这件事。
想要了解更多信息,自己查看SKAction文档吧。
在你的场景里添加刚体(Physics Bodies)和结合点来模仿物理效果
尽管你可以控制你场景里每一个节点的确切位置,你还常常想要这些节点之间相互作用,相互碰撞,并且在这个过程中带来速度的变化。你可能也希望做一些没有被动作系统处理的事情,例如模仿重力和其他力。为了这个,你创建刚体(SKPhysicsBody)并且绑定到你的场景里面的节点上去。每一个刚体都被定义了形状,大小,质量和其他的一些物理特征。场景在一个绑定的SKPhysicsWorld对象里定义了全局的物理模拟特征。你用这个物理世界对象为整个模拟定义重力和速度。
当刚体被包含在场景里面时,这个场景会在这些刚体上模拟物理规律。一些力,例如摩擦力和重力,是被自动应用的。通过添加SKFieldNode对象到场景中,其他的一些力可以被自动应用到多个刚体上。你也可以通过直接修改它的速度或者添加力或者脉冲到它上面来直接影响一个区域体。每一个刚体的加速度和速度都经过计算,刚体相互碰撞。然后,模拟结束之后,相关节点的位置和旋转都被更新。
你可以精确控制物理效果相互作用。例如,你可以指定一个特殊的物理区域节点只影响场景里面的一小部分刚体。你也能够决定哪些刚体能相互碰撞,并且单独地决定哪些交互能引起你的App做出响应。你用这些回调来添加游戏逻辑。例如,当一个节点的刚体被其他的刚体击中的时候,你的游戏可能摧毁这个节点。
你也可以用物理世界去找到场景中的刚体,并且用一个连接点(SKPhysicsJoint)来把这些刚体连在一块儿。被连起来的刚体根据连接点的种类不同在一起模拟不同的样式。
想要了解更多信息,请查看Simulating Physics
。
开始使用SpriteKit
SpriteKit把内容作为一个分级的节点树实现。一个节点树由一个作为根节点的场景和其他提供内容的节点组成。一个场景的每一帧都被处理并渲染到一个视图上去。场景执行动作并且模拟物理规律,这都会改变树的内容。然后场景被SpriteKit高效的渲染。
开始学习SpriteKit之前,你因该以下面的顺序看看这些类,然后再去看库里的其他类:
创建你的第一个场景
SpriteKit内容被置于一个window里面,就像其他可见内容一样。SpriteKit内容由SKView类渲染。一个SKView对象渲染的内容被称为一个场景,也就是一个SKScene对象。场景参与响应链并且拥有其他能够使他们适用于游戏的特色。
因为SpriteKit内容由一个视图对象渲染,你可以把这个视图和视图层级里的其他视图结合起来。例如,你可以使用标准的控制按钮并且把他们放在你的SpriteKit视图上面。或者,你可以给精灵添加互动来实现你自己的按钮,怎么选择由你决定。这个例子后面,你将会看到如何在场景上面实现互动。
一个SKView对象可以当做子视图添加到一个UIView对象上去,或者你可以通过storyboard,使用自定义类,或者代码,明确地把你的view controller的视图转换成一个SceneKit视图。下面的列表向你展示则怎么重写一个view controller的viewDidLoad()方法来把它的视图转换成一个SKView对象:
- 把一个view controller的view转换成一个SKView
override func viewDidLoad() {
super.viewDidLoad()
view = SKView()
}
var skView: SKView {
return view as! SKView
}
SpriteKit视图创建之后,展示内容的下一步就是创建一个场景。正常情况下,你会为你需要的每一个场景创建SKScene子类,但是为了简便,下面的代码仅仅实例化一个被view展现的新的场景对象:
- 创建并展示一个场景
let scene = SKScene(size: CGSize(width: 1024, height: 768))
override func viewWillAppear(_ animated: Bool) {
skView.presentScene(scene)
}
为了在SpriteKit里展示内容,相关的节点被添加到这个场景或者它的子场景里。这个例子中的最后一步是展示一个label,创建一个SKLabelNode对象,然后把它添加到场景中去:
- 添加一个label到场景中
let label = SKLabelNode(text: "SpriteKit")
label.position = CGPoint(x: scene.size.width / 2,
y: scene.size.height / 2)
scene.addChild(label)