转自:https://www.bbsmax.com/A/WpdKoZ8odV/
SpriteKit,iOS/Mac游戏制作的新纪元
这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看这篇总览。本文仅作为个人记录使用,也欢迎在许可协议范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用RSS或邮件方式订阅本站,这样您将能在第一时间获取本站信息。
本文涉及到的WWDC2013 Session有
- Session 502 Introduction to Sprite Kit
- Session 503 Designing Games with Sprite Kit
SpriteKit的加入绝对是iOS 7/OSX 10.9的SDK最大的亮点。从此以后官方SDK也可以方便地进行游戏制作了。
如果你在看这篇帖子,那我估计你应该稍微知道一些iOS平台上2D游戏开发的东西,比如cocos2d,那很好,因为SpriteKit的很多概念其实和 cocos2d非常类似,你应该能很快掌握。如果上面这张图你看着眼熟,或者自己动手实践过,那更好,因为这篇文章的内容就是通过使用SpriteKit 来一步一步带你重新实践一遍这个经典教程。如果你既不知道cocos2d,更没有使用游戏引擎开发iOS游戏的经验,只是想一窥游戏开发的天地,那现 在,SpriteKit将是一个非常好的入口,因为是iOS SDK自带的框架,因此思想和用法上和现有的其他框架是统一的,这极大地降低了学习的难度和门槛。
什么是SpriteKit
首先要知道什么是Sprite
。 Sprite的中文译名就是精灵,在游戏开发中,精灵指的是以图像方式呈现在屏幕上的一个图像。这个图像也许可以移动,用户可以与其交互,也有可能仅只是 游戏的一个静止的背景图。塔防游戏中敌方源源不断涌来的每个小兵都是一个精灵,我方防御塔发出的炮弹也是精灵。可以说精灵构成了游戏的绝大部分主体视觉内 容,而一个2D引擎的主要工作,就是高效地组织,管理和渲染这些精灵。SpriteKit是在iOS7 SDK中Apple新加入的一个2D游戏引擎框架,在SpriteKit出现之前,iOS开发平台上已经出现了像cocos2d这样的比较成熟的2D引擎 解决方案。SpriteKit展现出的是Apple将Xcode和iOS/Mac SDK打造成游戏引擎的野心,但是同时也确实与IDE有着更好的集成,减少了开发者的工作。
Hello SpriteKit
废话不多说,本文直接上实例教程来说明SpriteKit的基本用法。
好吧,我要做的是将非常风靡流行妇孺皆知的raywenderlich的经典cocos2d教程使 用全新的SpriteKit重新实现一遍。重做这个demo的主要原因是cocos2d的这个入门实在是太经典了,包括了精灵管理,交互检测,声音播放和 场景切换等等方面的内容,麻雀虽小,却五脏俱全。这个小demo讲的是一个无畏的英雄抵御外敌侵略的故事,英雄在画面左侧,敌人源源不断从右侧涌来,通过 点击屏幕发射飞镖来消灭敌人,阻止它们越过屏幕左侧边缘。在示例中用到的素材,可以从这里下载。另外为了方便大家,整个工程示例我也放在了github上,传送门在此。
配置工程
首先当然是建立工程,Xcode5提供了SpriteKit模板,使用该模板建立新工程,名字就叫做SpriteKitSimpleGame好了。
新建一个SpriteKit工程
因为我们需要一个横屏游戏,所以在新建工程后,在工程设定的General标签中,把Depoyment Info中Device Orientation中的Portrait勾去掉,使应用只在横屏下运行。另外,为了使之后的工作轻松一些,我们可以选择在初始的view显示完成,尺 寸通过rotation计算完毕之后再添加新的Scene,这样得到的Scene的尺寸将是宽480(或者568)高320的size。如果在 appear之前就使用bounds.size添加的话,将得到宽320 高480(568)的size,会很麻烦。将ViewController.m中的-viewDidLoad:
方法全部替换成下面的-viewDidAppear:
。
|
|
然后编译运行,应如果一切正常,该显示类似于下面的画面,每点击画面时,会出现一架不停旋转的飞机。
SpriteKit正常运行
加入精灵
SpriteKit是基于场景(Scene)来组织的,每个SKView(专门用来呈现SpriteKit的View)中可以渲染和管理一个SKScene,每个Scene中可以装载多个精灵(或者其他Node,之后会详细说明),并管理它们的行为。
现在让我们在这个Scene里加一个精灵吧,先从我们的英雄开始。首先要做的是把刚才下载的素材导入到工程中。我们这次用资源目录(Asset Catalog)来管理资源吧。点击工程中的Images.xcassets
, 以打开Asset Catalog。将下载解压后Art文件夹中的图片都拖入到打开的资源目录中,资源目录会自动根据文件的命名规则识别图片,1x的图片将用于 iPhone4和iPad3之前的非retina设备,2x的图片将用于retina设备。当然,如果你对设备性能有信心的话,也可以把1x的图片删除 掉,这样在非retina设备中也将使用2x的高清图(画面上的大小自然会帮你缩小成2x的一半),以获取更好的视觉效果。做完这一步后,工程的资源目录 会是这个样子的:
将图片素材导入工程中
开始coding吧~默认的SpriteKit模板做的事情就是在ViewController的self.view(这个view是一个SKView, 可以到storyboard文件中确认一下)中加入并显示了一个SKScene的子类实例MyScene。正如在做app开发时我们主要代码量会集中在 ViewController一样,在用SpriteKit进行游戏开发时,因为所有游戏逻辑和精灵管理都会在Scene中完成,我们的代码量会集中在 SKScene中。在MyScene.m中,把原来的-initWithSize
替换成这样:
|
|
- 因为默认工程的Scene背景偏黑,而我们的主角和怪物也都是黑色的,所以先 设定为白色。SKColor只是一个define定义而已,在iOS平台下被定义为UIColor,在Mac下被定义为NSColor。在 SpriteKit开发时,尽量使用SK开头的对应的UI类可以统一代码而减少跨iOS和Mac平台的成本。类似的定义还有SKView,它在iOS下是 UIView的子类,在Mac下是NSView的子类。
- 在SpriteKit中初始化一个精灵很简单,直接用
SKSpriteNode
的+spriteNodeWithImageNamed:
,指定图片名就行。实际上一个SKSpriteNode中包含了贴图(SKTexture对象),颜色,尺寸等等参数,这个简便方法为我们读取图片,生成SKTexture,并设定精灵尺寸和图片大小一致。在实际使用中,绝大多数情况这个简便方法就足够了。 - 设 定精灵的位置。SpriteKit中的坐标系和其他OpenGL游戏坐标系是一致的,屏幕左下角为(0,0)。不过需要注意的是不论是横屏还是竖屏游 戏,view的尺寸都是按照竖屏进行计算的,即对于iPhone来说在这里传入的sizewidth是320,height是480或者568,而不会因 为横屏而发生交换。因此在开发时,请千万不要使用绝对数值来进行位置设定及计算(否则你会死的很难看啊很难看)。
- 把player加入到当前scene中,addChild接受SKNode对象(SKSprite是SKNode的子类),关于SKNode稍后再说。
运行游戏,yes~主角出现在屏幕上了。
在屏幕左侧添加了一个精灵
源源不断涌来的怪物大军
没有怪物的陪衬,主角再潇洒也是寂寞。添加怪物精灵的方法和之前添加主角没什么两样,生成精灵,设定位置,加到scene中。区别在于怪物是会移动的 & 怪物是每隔一段时间就会出现一个的。
在MyScene.m中,加入一个方法-addMonster
|
|
- 计算怪物的出生点(移动开始位置)的Y值。怪物从右侧屏幕外随机的高度处进入屏幕,为了保证怪物图像都在屏幕范围内,需要指定最小和最大Y值。然后从这个范围内随机一个Y值作为出生点。
- 设定出生点恰好在屏幕右侧外面,然后添加怪物精灵。
- 怪物要是匀速过来的话太死板了,加一点随机量,这样怪物有快有慢不会显得单调
- 建立SKAction。SKAction可以操作SKNode,完成诸如精灵移动,旋转,消失等等。这里声明了两个SKAction,
actionMove
负责将精灵在actualDuration
的时间间隔内移动到结束点(直线横穿屏幕);actionMoveDone
负责将精灵移出场景,其实是run一段接受到的block代码。runAction
方法可以让精灵执行某个操作,而在这里我们要做的是先将精灵移动到结束点,当移动结束后,移除精灵。我们需要的是一个顺序执行,这里sequence:可以让我们顺序执行多个action。
然后尝试在上面的-initWithSize:
里调用这个方法看看结果
|
|
在游戏中加入会动的敌人
Cool,我们的游戏有个能动的图像。知道么,游戏的本质是什么?就是一堆能动的图像!
只有一个怪物的话,英雄大大还是很寂寞,所以我们说好了会有源源不断的怪物..在-initWithSize:
的4之后加入以下代码
|
|
这里声明了一个SKAction的序列,run一个block,然后等待1秒。用这个动作序列用-repeatActionForever:
生成一个无限重复的动作,然后让scene执行。这样就可以实现每秒调用一次-addMonster
来 向场景中不断添加敌人了。如果你对Cocoa(Touch)开发比较熟悉的话,可能会说,为什么不用一个NSTimer来做同样的事情,而要写这样的 SKAction呢?能不能用NSTimer来达到同样的目的?答案是在对场景或者精灵等SpriteKit对象进行类似操作时,尽量不要用 NSTimer。因为NSTimer将不受SpriteKit的影响和管理,使用SKAction可以不加入其它任何代码就获取如下好处:
- 自动暂停和继续,当设定一个SKNode的
paused
属 性为YES时,这个SKNode和它管理的子node的action都会自动被暂停。这里详细说明一下SKNode的概念:SKNode是 SpriteKit中要素的基本组织方式,它代表了SKView中的一种游戏资源的组织方式。我们现在接触到的SKScene和SKSprite都是 SKNode的子类,而一个SKNode可以有很多的子Node,从而构成一个SKNode的树。在我们的例子中,MyScene直接加在SKView中 作为最root的node存在,而英雄或者敌人的精灵都作为Scene这个node的子node被添加进来。SKAction和node上的各种属性的的 作用范围是当前这个node和它的所有子node,在这里我们如果设定MySecnen这个node(也就是self)的paused
属性被设为YES的话,所有的Action都会被暂停,包括这个每隔一秒调用一次的action,而如果你用NSTimer的话,恭喜,你必须自行维护它的状态。 - 当SKAction依附的结点被从结点树中拿掉的时候,这个action会自动结束并停止,这是符合一般逻辑的。
编译,运行,一切如我们所预期的那样,每个一秒有一个怪物从右侧进入,并以不同的速度穿过屏幕。
添加了源源不断滚滚而来的敌人大军
奥特曼打小怪兽是天经地义的
有了英雄,有了怪兽,就差一个“打”了。我们打算做的是在用户点击屏幕某个位置时,就由英雄所在的位置向点击位置发射一枚固定速度的飞镖。然后这每飞镖要是命中怪物的话,就把怪物从屏幕中移除。
先来实现发射飞镖吧。检测点击,然后让一个精灵朝向点击的方向以某个速度移动,有很多种SKAction可以实现,但是为了尽量保持简单,我们使用上面曾经使用过的moveTo:duration:
吧。 在发射之前,我们先要来做一点基本的数学运算,希望你还能记得相似三角形之类的概念。我们的飞镖是由英雄发出的,然后经过手指点击的点,两点决定一条直 线。简单说我们需要求解出这条直线和屏幕右侧边缘外的交点,以此来确定飞镖的最终目的。一旦我们得到了这个终点,就可以控制飞镖moveTo到这个终点, 从而模拟出发射飞镖的action了。如图所示,很简单的几何学,关于具体的计算就不再讲解了,要是算不出来的话,请考虑call你的中学数学老师并负荆 请罪以示诚意。
通过点击计算飞镖终止位置
然后开始写代码吧,还记得我们之前点击会出现一个飞机的精灵么,找到相应的地方,MyScene.m里的-touchesBegan:withEvent:
:,用下面的代码替换掉原来的。
|
|
- 为飞镖设定初始位置。
- 将点击的位置转换为node的坐标系的坐标,并计算点击位置和飞镖位置的偏移量。如果点击位置在飞镖初始位置的后方,则直接返回
- 根据相似三角形计算屏幕右侧外的结束位置。
- 移 动飞镖,并在移动结束后将飞镖从场景中移除。注意在移动怪物的时候我们用了两个action(actionMove和actionMoveDone来做移 动+移除),这里只使用了一个action并用带completion block移除精灵。这里对飞镖的这种做法是比较简明常见高效的,之前的做法只是为了说明action的
sequence:
的用法。
运行看看现在的游戏吧,我们有英雄有怪物还有打怪物的小飞镖,好像气氛上已经开始有趣了!