Sprite Kit教程:初学者

Sprite Kit教程:初学者


在iOS 7中内置了一个新的Sprite Kit框架,该框架主要用来开发2D游戏。目前已经支持的内容包括:精灵、很酷的特效(例如视频、滤镜和遮罩),而且还集成了物理库等许多东西。iOS 7中附带了一个非php

阅读器

Sprite Kitnode

转自破船之家,原文:Sprite Kit Tutorial for Beginnersios

 
目录
Sprite Kit的优势和缺点
Sprite Kit vs Cocos2D-iPhone vs Cocos2D-X vs Unity
Hello, Sprite Kit!
横屏显示
移动怪兽
发射炮弹
碰撞检测: 概述
碰撞检测: 实现
收尾
何去何从?
 
在iOS 7中内置了一个新的Sprite Kit框架,该框架主要用来开发2D游戏。目前已经支持的内容包括:精灵、很酷的特效(例如视频、滤镜和遮罩),而且还集成了物理库等许多东西。iOS 7中附带了一个很是棒的Sprite Kit示例工程,名字叫作Adventure。不过这个示例工程稍微有点复杂,不太适合初学者。本文的目的就是作一个关于Sprite Kit使用的初级教程。
 
经过本文,你能够从头至尾的学习到如何为你的iPhone建立一个简单又有趣的2D游戏。若是你看过咱们以前的教程:Simple Cocos2D game教程,你会发现很是的类似。在开始以前,请确保已经安装了最新版本的Xcode(5.X),里面支持Sprite Kit以及iOS 7。
 
Sprite Kit的优势和缺点
首先,我想指出在iOS中开发2D游戏Sprite Kit并非惟一的选择,下面咱们先来看看Sprite Kit的一些优势和缺点。
 
Sprite Kit的优势:
一、它是内置到iOS中的,所以并不须要下载额外的库或者其它一些外部依赖。而且它是由苹果开发的,因此对于它的支持和更新咱们能够放心。
二、它内置的工具支持纹理和粒子。
三、它可让你作一些其它框架很难作到的事情,例如把视频当作精灵同样处理,或者使用很酷的图形效果和遮罩。
 
Sprite Kit的缺点:
一、若是使用了Sprite Kit,那么你将被iOS生态圈所绑架,致使你没法很容易对你开发的游戏移植到Android上。
二、Sprite Kit如今还处于初始阶段,此时提供的功能尚未别的框架丰富,例如Cocos2D。最缺的东西应该是暂不支持写自定义的OpenGL代码。
 
Sprite Kit vs Cocos2D-iPhone vs Cocos2D-X vs Unity
此时,你可能在想“我该选择使用哪一个2D框架呢?”这取决于你的实际状况,下面是个人一些想法:
 
一、若是你是一个初学者,而且只关注于iOS,那么就使用内置的Sprite Kit吧,它很是容易学习,而且彻底能够把工做作好。
二、若是须要写本身的OpenGL代码,那么仍是使用Cocos2D,或者其它框架吧,目前Sprite Kit并不支持自定义OpenGL代码。
三、若是要进行跨平台开发,那么选择Cocos2D-X或者Unity。Cocos2D-X很是出色,能够用它来构建2D游戏。Unity则更加的灵活(例如,若是有须要的话,你能够在游戏中添加一些3D效果)。
 
看到这里,若是你还想要继续了解Sprite Kit的话,请继续往下读吧。
 
Hello,Sprite Kit!
下面咱们就开始利用Xcode 5内置的Sprite Kit模板来构建一个简单的Hello World工程吧。
 
启动Xcode,选择File\New\Project,接着选中iOS\Application\SpriteKit Game模板,而后单击Next:
 
输入Product Name为SpriteKitSimpleGame,Devices选择iPhone,接着单击Next:
 
选择工程保存的路径,而后点击Create。而后点击Xcode中的播放按钮来运行工程。稍等片刻,能够看到以下运行画面:
跟Cocos2D相似,Sprite Kit也是按照场景(scenes)来构建的,这至关于游戏中的”levels”和”screens”。例如,你的游戏中可能会有一个主游戏区的场景,以及一个世界地图的一个场景。
 
若是你观察一下建立好的工程,会发现SpriteKit Game模板已经建立好了一个默认的场景MyScene。如今打开MyScene.m,里面已经包含了一些代码,其中将一个lable放到屏幕中,而且添加了:当tap屏幕时,会在屏幕上新增一个旋转的飞船。
 
在本教程中,咱们主要在MyScene中写代码。不过在开始写代码以前,须要进行一个小调整——让程序以横屏的方式运行。
 
横屏显示
首先,在Project Navigator中单击SpriteKitSimpleGame工程以打开target设置,选中SpriteKitSimpleGame target。而后在Deployment Info中,不要勾选Portrait,只选中Landscape和Landscape Right,以下所示:
 
编译并运行工程,会看到以下运行画面:
下面咱们试着添加一个忍者(ninja)。
 
首先,下载此工程的资源文件,并将其拖拽到Xcode工程中。确保勾选上“Copy items into destination group’s folder (if needed)”和SpriteKitSimpleGame target。
 
接着,打开MyScene.m,并用下面的内容替换之:
  1. #import "MyScene.h" 
  2.  
  3. // 1 
  4. @interface MyScene () 
  5. @property (nonatomic) SKSpriteNode * player; 
  6. @end 
  7.  
  8. @implementation MyScene 
  9.  
  10. -(id)initWithSize:(CGSize)size { 
  11.     if (self = [super initWithSize:size]) { 
  12.  
  13.         // 2 
  14.         NSLog(@"Size: %@", NSStringFromCGSize(size)); 
  15.  
  16.         // 3 
  17.         self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0]; 
  18.  
  19.         // 4 
  20.         self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"]; 
  21.         self.player.position = CGPointMake(100, 100); 
  22.         [self addChild:self.player]; 
  23.  
  24.     } 
  25.     return self; 
  26.  
  27. @end 
 
咱们来看看上面的代码。
 
1.为了给player(例如忍者)声明一个私有变量,在这里建立了一个私有的interface,以后能够把这个私有变量添加到场景中。
2.在这里打印出了场景的size,至于什么缘由很快你就会看到了。
3.在Sprite Kit中设置一个场景的背景色很是简单——只须要设置backgroundColor属性,在这里将其设置位白色。
4.在Sprite Kit场景中添加一个精灵一样很是简单,只须要使用spriteNodeWithImageNamed方法,并把一副图片的名称传递进去就能够建立一个精 灵。接着设置一下精灵的位置,而后调用addChild方法将该精灵添加到场景中。在代码中将忍者的位置设置为(100, 100),该位置是从屏幕的左下角到右上角计算的。
 
编译并运行,看看效果如何…
呀!屏幕是白色的,并无看到忍者。这是为何呢?你可能在想设计之初就是这样的,实际上这里有一个问题。
 
若是你观察一下控制台输出的内容,会看到以下内容
  1. SpriteKitSimpleGame[3139:907] Size: {320, 568} 
 
可能你会认为场景的宽度是320,高度则是568——实际上恰好相反!
 
咱们来看看具体发生了什么:定位到ViewController.m的viewDidLoad方法:
  1. - (void)viewDidLoad 
  2.     [super viewDidLoad]; 
  3.  
  4.     // Configure the view. 
  5.     SKView * skView = (SKView *)self.view; 
  6.     skView.showsFPS = YES; 
  7.     skView.showsNodeCount = YES; 
  8.  
  9.     // Create and configure the scene. 
  10.     SKScene * scene = [MyScene sceneWithSize:skView.bounds.size]; 
  11.     scene.scaleMode = SKSceneScaleModeAspectFill; 
  12.  
  13.     // Present the scene. 
  14.     [skView presentScene:scene]; 
 
上面的代码中利用view的边界size建立了场景。不过请注意,当viewDidLoad被调用的时候,在这以前view已经被添加到 view层次结构中了,所以它尚未响应出布局的改变。因此view的边界可能还不正确,进而在viewDidLoad中并非开启场景的最佳时机。
 
提醒:要想了解更多相关内容,请看由Rob Mayoff带来的最佳解释。
 
解决方法就是将开启场景代码的过程再靠后一点。用下面的代码替换viewDidLoad:
  1. - (void)viewWillLayoutSubviews 
  2.     [super viewWillLayoutSubviews]; 
  3.  
  4.     // Configure the view. 
  5.     SKView * skView = (SKView *)self.view; 
  6.     if (!skView.scene) { 
  7.       skView.showsFPS = YES; 
  8.       skView.showsNodeCount = YES; 
  9.  
  10.       // Create and configure the scene. 
  11.       SKScene * scene = [MyScene sceneWithSize:skView.bounds.size]; 
  12.       scene.scaleMode = SKSceneScaleModeAspectFill; 
  13.  
  14.       // Present the scene. 
  15.       [skView presentScene:scene]; 
  16.     } 
 
编译并运行程序,能够看到,忍者已经显示在屏幕中了!
如上图所示,能够看到坐标系已经正确了,若是想要把忍者的位置设置为其中间靠左,那么在MyScene.m中用下面的代码来替换设置忍者位置相关的代码:
  1. self.player.position = CGPointMake(self.player.size.width/2, self.frame.size.height/2); 
 
移动怪兽
接下来,咱们但愿在场景中添加一些怪兽,让忍者进行攻击。为了让游戏更有趣一点,但愿怪兽可以移动——不然没有太大的挑战!OK,咱们就在屏幕的右边,离屏的方式建立怪兽,并给怪兽设置一个动做:告诉它们往左边移动。
 
将下面这个方法添加到MyScene.m中:
  1. - (void)addMonster { 
  2.  
  3.     // Create sprite 
  4.     SKSpriteNode * monster = [SKSpriteNode spriteNodeWithImageNamed:@"monster"]; 
  5.  
  6.     // Determine where to spawn the monster along the Y axis 
  7.     int minY = monster.size.height / 2; 
  8.     int maxY = self.frame.size.height - monster.size.height / 2; 
  9.     int rangeY = maxY - minY; 
  10.     int actualY = (arc4random() % rangeY) + minY; 
  11.  
  12.     // Create the monster slightly off-screen along the right edge, 
  13.     // and along a random position along the Y axis as calculated above 
  14.     monster.position = CGPointMake(self.frame.size.width + monster.size.width/2, actualY); 
  15.     [self addChild:monster]; 
  16.  
  17.     // Determine speed of the monster 
  18.     int minDuration = 2.0; 
  19.     int maxDuration = 4.0; 
  20.     int rangeDuration = maxDuration - minDuration; 
  21.     int actualDuration = (arc4random() % rangeDuration) + minDuration; 
  22.  
  23.     // Create the actions 
  24.     SKAction * actionMove = [SKAction moveTo:CGPointMake(-monster.size.width/2, actualY) duration:actualDuration]; 
  25.     SKAction * actionMoveDone = [SKAction removeFromParent]; 
  26.     [monster runAction:[SKAction sequence:@[actionMove, actionMoveDone]]]; 
  27.  
 
在上面,我尽可能让代码看起来容易理解。首先是经过一个简单的计算,肯定怪兽出现的位置,并将该位置设置给怪兽,而后将其添加到场景中。
 
接着是添加动做(actions)。跟Cocos2D同样,Sprite Kit一样提供了不少方便的内置动做,例如移动动做、旋转动做、淡入淡出动做、动画动做等。在这里咱们只须要在怪兽上使用3中动做便可:
moveTo:duration:使用这个动做能够把怪兽从屏幕外边移动到左边。移动过程当中,咱们能够指定移动持续的时间,上面的代码中,指定为2-4秒之间的一个随机数。
removeFromParent:在Sprite Kit中,可使用该方法,方便的将某个node从parent中移除,能有效的从场景中删除某个对象。此处,将再也不须要显示的怪兽从场景中移除。这个功能很是的重要,不然当有源源不断的怪兽出如今场景中时,会耗尽设备的全部资源。
sequence:sequence动做能够一次性就把一系列动做串联起来按照必定顺序执行。经过该方法咱们就能让moveTo:方法先执行,当完成以后,在执行removeFromParent:动做。
 
最后,咱们须要作的事情就是调用上面这个方法addMonster,以实际的建立出怪兽!为了更加好玩,下面咱们来让怪兽随着时间持续的出如今屏幕中。
 
在Sprite Kit中,并不能像Cocos2D同样,能够配置每隔X秒就回调一下update方法。一样也不支持将从上次更新到目前为止的时间差传入方法中。(很是使人吃惊!)。
 
不过,咱们能够经过一小段代码来仿造这种行为。首先在MyScene.m的private interface中添加以下属性:
  1. @property (nonatomic) NSTimeInterval lastSpawnTimeInterval; 
  2. @property (nonatomic) NSTimeInterval lastUpdateTimeInterval; 
 
经过lastSpawnTimeInterval能够记录着最近出现怪兽时的时间,而lastUpdateTimeInterval能够记录着上次更新时的时间。
 
接着,咱们写一个方法,该方法在画面每一帧更新的时候都会被调用。记住,该方法不会被自动调用——须要另外写一个方法来调用它:
  1. - (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast { 
  2.  
  3.     self.lastSpawnTimeInterval += timeSinceLast; 
  4.     if (self.lastSpawnTimeInterval > 1) { 
  5.         self.lastSpawnTimeInterval = 0; 
  6.         [self addMonster]; 
  7.     } 
 
上面的代码中简单的将上次更新(update调用)的时间追加到self.lastSpawnTimeInterval中。一旦该时间大于1秒,就在场景中新增一个怪兽,并将lastSpawnTimeInterval重置。
 
最后,添加以下方法来调用上面的方法:
  1. - (void)update:(NSTimeInterval)currentTime { 
  2.     // Handle time delta. 
  3.     // If we drop below 60fps, we still want everything to move the same distance. 
  4.     CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval; 
  5.     self.lastUpdateTimeInterval = currentTime; 
  6.     if (timeSinceLast > 1) { // more than a second since last update 
  7.         timeSinceLast = 1.0 / 60.0; 
  8.         self.lastUpdateTimeInterval = currentTime; 
  9.     } 
  10.  
  11.     [self updateWithTimeSinceLastUpdate:timeSinceLast]; 
  12.  
Sprite Kit在显示每帧时都会调用上面的update:方法。
 
上面的代码实际上是来自苹果提供的Adventure示例中。该方法会传入当前的时间,在其中,会作一些计算,以肯定出上一帧更新的时间。注意,在代码中作了一些合理性的检查,以免从上一帧更新到如今已通过去了大量时间,而且将间隔重置为1/60秒,避免出现奇怪的行为。
 
如今编译并运行程序,能够看到许多怪兽从左边移动到屏幕右边并消失。
发射炮弹
如今咱们开始给忍者添加一些动做,首先从发射炮弹开始!实际上有多种方法来实现炮弹的发射,不过,在这里要实现的方法时当用户tap屏幕时,从忍者的方位到tap的方位发射一颗炮弹。
 
因为本文是针对初级开发者,因此在这里我使用moveTo:动做来实现,不过这须要作一点点的数学运算——由于moveTo:方法须要指定炮弹 的目的地,可是又不能直接使用touch point(由于touch point仅仅表明须要发射的方向)。实际上咱们须要让炮弹穿过touch point,直到炮弹在屏幕中消失。
 
以下图,演示了上面的相关内容:
如图所示,咱们能够经过origin point到touch point获得一个小的三角形。咱们要作的就是根据这个小三角形的比例建立出一个大的三角形——而你知道你想要的一个端点是离开屏幕的地方。
 
为了作这个计算,若是有一些基本的矢量方法可供调用(例如矢量的加减法),那么会很是有帮助,但很不幸的时Sprite Kit并无提供相关方法,因此,咱们必须本身实现。
 
不过很幸运的时这很是容易实现。将下面的方法添加到文件的顶部(implementation以前):
  1. static inline CGPoint rwAdd(CGPoint a, CGPoint b) { 
  2.     return CGPointMake(a.x + b.x, a.y + b.y); 
  3.  
  4. static inline CGPoint rwSub(CGPoint a, CGPoint b) { 
  5.     return CGPointMake(a.x - b.x, a.y - b.y); 
  6.  
  7. static inline CGPoint rwMult(CGPoint a, float b) { 
  8.     return CGPointMake(a.x * b, a.y * b); 
  9.  
  10. static inline float rwLength(CGPoint a) { 
  11.     return sqrtf(a.x * a.x + a.y * a.y); 
  12.  
  13. // Makes a vector have a length of 1 
  14. static inline CGPoint rwNormalize(CGPoint a) { 
  15.     float length = rwLength(a); 
  16.     return CGPointMake(a.x / length, a.y / length); 
 
上面实现了一些标准的矢量函数。若是你看得不是太明白,请看这里关于矢量方法的解释。
 
接着,在文件中添加一个新的方法:
  1. -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 
  2.  
  3.     // 1 - Choose one of the touches to work with 
  4.     UITouch * touch = [touches anyObject]; 
  5.     CGPoint location = [touch locationInNode:self]; 
  6.  
  7.     // 2 - Set up initial location of projectile 
  8.     SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:@"projectile"]; 
  9.     projectile.position = self.player.position; 
  10.  
  11.     // 3- Determine offset of location to projectile 
  12.     CGPoint offset = rwSub(location, projectile.position); 
  13.  
  14.     // 4 - Bail out if you are shooting down or backwards 
  15.     if (offset.x <= 0) return; 
  16.  
  17.     // 5 - OK to add now - we've double checked position 
  18.     [self addChild:projectile]; 
  19.  
  20.     // 6 - Get the direction of where to shoot 
  21.     CGPoint direction = rwNormalize(offset); 
  22.  
  23.     // 7 - Make it shoot far enough to be guaranteed off screen 
  24.     CGPoint shootAmount = rwMult(direction, 1000); 
  25.  
  26.     // 8 - Add the shoot amount to the current position        
  27.     CGPoint realDest = rwAdd(shootAmount, projectile.position); 
  28.  
  29.     // 9 - Create the actions 
  30.     float velocity = 480.0/1.0; 
  31.     float realMoveDuration = self.size.width / velocity; 
  32.     SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration]; 
  33.     SKAction * actionMoveDone = [SKAction removeFromParent]; 
  34.     [projectile runAction:[SKAction sequence:@[actionMove, actionMoveDone]]]; 
  35.  
上面的代码中作了不少事情,咱们来详细看看。
 
1.SpriteKit为咱们作了很棒的一件事情就是它提供了一个UITouch的category,该category中有 locationInNode:和previousLocationInNode:方法。这两个方法能够帮助咱们定位到在SKNode内部坐标系中 touch的坐标位置。这样一来,咱们就能够寻获得在场景坐标系中touch的位置。
 
2.而后建立一个炮弹,并将其放置到忍者的地方,以当作其开始位置。注意,如今尚未将其添加到场景中,由于还须要先作一个合理性的检查——该游戏不容许忍者向后发射。
 
3.接着利用touch位置减去炮弹的当前位置,这样就能得到一个从当前位置到touch位置的矢量。
 
4.若是X值小于0,就意味着忍者将要向后发射,因为在这里的游戏中是不容许的(真实中的忍者是不回头的!),因此就return。
 
5.不然,将能够将炮弹添加到场景中。
 
6.调用方法rwNormalize,将offset转换为一个单位矢量(长度为1)。这样作可让在相同方向上,根据肯定的长度来构建一个矢量更加容易(由于1 * length = length)。
 
7.在单位矢量的方向上乘以1000。为何是1000呢?由于着确定足够超过屏幕边缘了 。
 
8.将上一步中计算获得的位置与炮弹的位置相加,以得到炮弹最终结束的位置。
 
9.最后,参照以前构建怪物时的方法,建立moveTo:和removeFromParent:两个actions。
 
编译并容许程序,如今忍者能够发射炮弹了!
 
碰撞检测和物理特性: 概述
至此咱们已经可让炮弹任意的发射了——如今咱们要让忍者利用炮弹来消灭这些怪物。下面就添加一些代码来给炮弹与怪物相交作检测。
 
Sprite Kit内置了一个物理引擎,这很是的棒!该物理引擎不只能够模拟现实运动,还能进行碰撞检测。
 
下面咱们就在游戏中使用Sprite Kit的物理引擎来检测炮弹与怪物的碰撞。首先,咱们来看看须要作些神马事情:
1.物理世界的配置。物理世界是一个模拟的空间,用来进行物理计算。默认状况下,在场景(scene)中已经建立好了一个,咱们能够对其作一些属性配置,例如重力感应。
 
2.为精灵(sprite)建立对应的物体(physics bodies)。在Sprite Kit中,为了碰撞检测,咱们能够为每一个精灵建立一个相应的形状,并设置一些属性,这就称为物体(physics body)。注意:图文的形状不必定跟精灵的外形如出一辙。通常状况,这个形状都是简单的、大概的(而不用精确到像素级别)——毕竟这已经足以够大多数游 戏使用了。
 
3.将精灵分类。在物体(physics body)上能够设置的一个属性是category,该属性是一个位掩码(bitmask)。经过该属性能够将精灵分类。在本文的游戏中,有两个类别—— 一类是炮弹,另外一类则是怪物。设置以后,当两种物体相互碰撞时,就能够很容易的经过类别对精灵作出相应的处理。
 
4.. 设 置一个contact(触点) delegate。还记得上面提到的物理世界吗?咱们能够在物理世界上设置一个contact delegate,经过该delegate,当两个物体碰撞时,能够收到通知。收到通知后,咱们能够经过代码检查物体的类别,若是是怪物和炮弹,那么就作 出相应的动做!
 
上面大体介绍了一下游戏策略,下面就来看看如何实现!
 
碰撞检测和物理特性: 实现
首先在MyScene.m文件顶部添加以下两个常量:
  1. static const uint32_t projectileCategory     =  0x1 << 0; 
  2. static const uint32_t monsterCategory        =  0x1 << 1; 
 
上面设置了两个类别,记住须要用位(bit)的方式表达——一个用于炮弹,另外一个则是怪物。
 
注意:看到上面的语法你可能感到奇怪。在Sprite Kit中category是一个32位整数,当作一个位掩码(bitmask)。这种表达方法比较奇特:在一个32位整数中的每一位表示一种类别(所以最 多也就只能有32类)。在这里,第一位表示炮弹,下一位表示怪兽。
 
接着,在initWithSize中,将下面的代码添加到位置:添加player到场景涉及代码的后面。
  1. self.physicsWorld.gravity = CGVectorMake(0,0); 
  2. self.physicsWorld.contactDelegate = self; 
 
上面的代码将物理世界的重力感应设置为0,并将场景设置位物理世界的代理(当有两个物体碰撞时,会受到通知)。
 
在addMonster方法中,将以下代码添加建立怪兽相关代码后面:
  1. monster.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:monster.size]; // 1 
  2. monster.physicsBody.dynamic = YES; // 2 
  3. monster.physicsBody.categoryBitMask = monsterCategory; // 3 
  4. monster.physicsBody.contactTestBitMask = projectileCategory; // 4 
  5. monster.physicsBody.collisionBitMask = 0; // 5 
 
来看看上面代码意思:
为怪兽建立一个对应的物体。此处,物体被定义为一个与怪兽相同尺寸的矩形(这样与怪兽形状比较接近)。
 
将怪兽设置位dynamic。这意味着物理引擎将再也不控制这个怪兽的运动——咱们本身已经写好相关运动的代码了。
 
将categoryBitMask设置为以前定义好的monsterCategory。
 
contactTestBitMask表示与什么类型对象碰撞时,应该通知contact代理。在这里选择炮弹类型。
 
collisionBitMask表示物理引擎须要处理的碰撞事件。在此处咱们不但愿炮弹和怪物被相互弹开——因此再次将其设置为0。
 
接着在touchesEnded:withEvent:方法中设置炮弹位置的代码后面添加以下代码。
  1. projectile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:projectile.size.width/2]; 
  2. projectile.physicsBody.dynamic = YES; 
  3. projectile.physicsBody.categoryBitMask = projectileCategory; 
  4. projectile.physicsBody.contactTestBitMask = monsterCategory; 
  5. projectile.physicsBody.collisionBitMask = 0; 
  6. projectile.physicsBody.usesPreciseCollisionDetection = YES; 
 
在 上面的代码中跟以前的相似,只不过有些不一样,咱们来看看: 1. 为了更好的效果,炮弹的形状是圆形的。 2. usesPreciseCollisionDetection属性设置为YES。这对于快速移动的物体很是重要(例如炮弹),若是不这样设置的话,有可能 快速移动的两个物体会直接相互穿过去,而不会检测到碰撞的发生。
 
接着,添加以下方法,当炮弹与怪物发生碰撞时,会被调用。注意这个方法是不会被自动调用,稍后会看到咱们如何调用它。
  1. - (void)projectile:(SKSpriteNode *)projectile didCollideWithMonster:(SKSpriteNode *)monster { 
  2.     NSLog(@"Hit"); 
  3.     [projectile removeFromParent]; 
  4.     [monster removeFromParent]; 
 
当怪物和炮弹发生碰撞,上面的代码会将他们从场景中移除。很简单吧!
 
下面该实现contact delegate方法了。将以下方法添加到文件中:
  1. - (void)didBeginContact:(SKPhysicsContact *)contact 
  2.     // 1 
  3.     SKPhysicsBody *firstBody, *secondBody; 
  4.  
  5.     if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) 
  6.     { 
  7.         firstBody = contact.bodyA; 
  8.         secondBody = contact.bodyB; 
  9.     } 
  10.     else 
  11.     { 
  12.         firstBody = contact.bodyB; 
  13.         secondBody = contact.bodyA; 
  14.     } 
  15.  
  16.     // 2 
  17.     if ((firstBody.categoryBitMask & projectileCategory) != 0 && 
  18.         (secondBody.categoryBitMask & monsterCategory) != 0) 
  19.     { 
  20.         [self projectile:(SKSpriteNode *) firstBody.node didCollideWithMonster:(SKSpriteNode *) secondBody.node]; 
  21.     } 
 
还记得以前给物理世界设置的contactDelegate吗?当两个物体发生碰撞以后,就会调用上面的方法。
 
在上面的方法中,能够分为两部分来理解:
该方法会传递给你发生碰撞的两个物体,可是并不必定符合特定的顺序(如炮弹在前,或者炮弹在后)。因此这里的代码是经过物体的category bit mask来对其进行排序,以便后续作出正确的判断。注意,这里的代码来自苹果提供的Adventure示例。
 
最后,检测一下这两个碰撞的物体是否就是炮弹和怪物,若是是的话就调用以前的方法。
 
最后一步,为了编译器没有警告,确保private interface 中添加一下SKPhysicsContactDelegate:
  1. @interface MyScene () <SKPhysicsContactDelegate> 
如今编译并运行程序,能够发现,当炮弹与怪物接触时,他们就会消失!
 
收尾
如今,本文的游戏快完成了。接下来咱们就来为游戏添加音效和音乐,以及一些简单的游戏逻辑吧。
 
苹果提供的Sprite Kit里面并无音频引擎(Cocos2D中是有的),不过咱们能够经过action来播放音效,而且可使用AVFoundation播放后台音乐。
 
在工程中我已经准备好了一些音效和很酷的后台音乐,在本文开头已经将resources添加到工程中了,如今只须要播放它们便可!
 
首先在ViewController.m文件顶部添加以下import:
  1. @import AVFoundation; 
上 面的语法是iOS 7中新的modules功能 —— 只须要使用新的关键字@import,就能够框架的头文件和库文件添加到工程中,这功能很是方便。要了解更多相关内容,请看到iOS 7 by Tutorials中的第十章内容中的:What’s New with Objective-C and Foundation。
 
接着添加一个新的属性和private interface:
  1. @interface ViewController () 
  2. @property (nonatomic) AVAudioPlayer * backgroundMusicPlayer; 
  3. @end 
 
接着将下面的代码添加到viewWillLayoutSubviews方法中(在[super viewWillLayoutSubviews]后面):
  1. NSError *error; 
  2. NSURL * backgroundMusicURL = [[NSBundle mainBundle] URLForResource:@"background-music-aac" withExtension:@"caf"]; 
  3. self.backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error]; 
  4. self.backgroundMusicPlayer.numberOfLoops = -1; 
  5. [self.backgroundMusicPlayer prepareToPlay]; 
  6. [self.backgroundMusicPlayer play]; 
 
上面的代码会开始无限循环的播放后台音乐。
 
下面咱们来看看如何处理音效。切换到MyScene.m文件中,并将下面这行代码添加到touchesEnded:withEvent:方法的顶部:
  1. [self runAction:[SKAction playSoundFileNamed:@"pew-pew-lei.caf" waitForCompletion:NO]]; 
如上,一行代码就能够播放音效了,很简单吧!
 
下面,咱们建立一个新的建立和layer,用来显示你赢了(You Win)或你输了(You Lose)。用模板iOS\Cocoa Touch\Objective-C class建立一个新的文件,将其命名为GameOverScene,并让其继承自SKScene,而后点击Next和Create。
 
接着用以下代码替换GameOverScene.h中的内容:
  1. #import <SpriteKit/SpriteKit.h> 
  2.  
  3. @interface GameOverScene : SKScene 
  4.  
  5. -(id)initWithSize:(CGSize)size won:(BOOL)won; 
  6.  
  7. @end 
 
在上面的代码中导入了Sprite Kit头文件,并声明了一个特定的初始化方法,该方法的第一个参数用来定位显示的位置,第二个参数won用来判断用户是否赢了。
 
接着用下面的代码替换GameOverLayer.m中的内容:
  1. #import "GameOverScene.h" 
  2. #import "MyScene.h" 
  3.  
  4. @implementation GameOverScene 
  5.  
  6. -(id)initWithSize:(CGSize)size won:(BOOL)won { 
  7.     if (self = [super initWithSize:size]) { 
  8.  
  9.         // 1 
  10.         self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0]; 
  11.  
  12.         // 2 
  13.         NSString * message; 
  14.         if (won) { 
  15.             message = @"You Won!"; 
  16.         } else { 
  17.             message = @"You Lose :["; 
  18.         } 
  19.  
  20.         // 3 
  21.         SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"]; 
  22.         label.text = message; 
  23.         label.fontSize = 40; 
  24.         label.fontColor = [SKColor blackColor]; 
  25.         label.position = CGPointMake(self.size.width/2, self.size.height/2); 
  26.         [self addChild:label]; 
  27.  
  28.         // 4 
  29.         [self runAction: 
  30.             [SKAction sequence:@[ 
  31.                 [SKAction waitForDuration:3.0], 
  32.                 [SKAction runBlock:^{ 
  33.                     // 5 
  34.                     SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5]; 
  35.                     SKScene * myScene = [[MyScene alloc] initWithSize:self.size]; 
  36.                     [self.view presentScene:myScene transition: reveal]; 
  37.                 }] 
  38.             ]] 
  39.         ]; 
  40.  
  41.     } 
  42.     return self; 
  43.  
  44. @end 
 
上面的代码能够分为4部份内容,咱们来分别看看:
 
将背景色设置为白色(与主场景同样颜色)。
 
根据won参数,将信息设置为”You Won”或”You Lose”。
 
这里的代码是利用Sprite Kit将一个文本标签显示到屏幕中。如代码所示,只须要选择一个字体,并设置少许的参数便可,也很是简单。
 
设置并运行有个有两个action的sequence。为了看起来方便,此处我将它们放到一块(而不是为每一个action建立单独的一个变量)。首先是等待3秒,而后是利用runBlockaction来运行一些代码。
 
演示了在Sprite Kit中如何过渡到新的场景。首先能够选择任意的一种不一样的动画过渡效果,用于场景的显示,在这里选择了翻转效果(持续0.5秒)。而后是建立一个想要显 示的场景,接着使用self.view的方法presentScene:transition:来显示出场景。
 
OK,万事俱备,只欠东风了!如今只须要在主场景中,适当的状况下加载game over scene就能够了。
 
首先,在MyScene.m中导入新的场景:
  1. #import "GameOverScene.h" 
 
而后,在addMonster中,用下面的代码替换最后一行在怪物上运行action的代码:
  1. SKAction * loseAction = [SKAction runBlock:^{ 
  2.     SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5]; 
  3.     SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:NO]; 
  4.     [self.view presentScene:gameOverScene transition: reveal]; 
  5. }]; 
  6. [monster runAction:[SKAction sequence:@[actionMove, loseAction, actionMoveDone]]]; 
 
上面建立了一个”lose action”,当怪物离开屏幕时,显示game over场景。
 
在这里为何loseAction要在actionMoveDone以前运行呢? 缘由在于若是将一个精灵从场景中移除了,那么它就不在处于场景的层次结构中了,也就不会有action了。因此须要过渡到lose场景以后,才能将精灵移 除。不过,实际上actionMoveDone永远都不会被调用——由于此时已通过渡到新的场景中了,留在这里就是为了达到教学的目的。
 
如今,须要处理一下赢了的状况。在private interface中添加一个新的属性:
  1. @property (nonatomic) int monstersDestroyed; 
 
而后将以下代码添加到projectile:didCollideWithMonster:的底部:
  1. self.monstersDestroyed++; 
  2. if (self.monstersDestroyed > 30) { 
  3.     SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5]; 
  4.     SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:YES]; 
  5.     [self.view presentScene:gameOverScene transition: reveal]; 
 
编译并运行程序,尝试一下赢了和输了会看到的画面!
 
何去何从?
至此Sprite Kit教程:初学者结束!这里能够下到 完整的代码
 
但愿本文能帮助你学习Sprite Kit,并写出你本身的游戏!
 
若是你但愿学习更多相关Sprite Kit内容,能够看看这本书: iOS Games by Tutorials。本书会告诉你须要知道的内容——从物理,到tile map,以及特定的系统,甚至是制做本身的关卡编辑器。

CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各类精彩的 研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocos Studio开发工具包的最新动态及培训信息。关注微信能够第一时间了解最新产品和服务动态,微信在手,天下我有!微信

相关文章
相关标签/搜索