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

“”

阅读器

Sprite Kit

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

目录
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的优点:
1、它是内置到iOS中的,因此并不需要下载额外的库或者其它一些外部依赖。并且它是由苹果开发的,所以对于它的支持和更新我们可以放心。
2、它内置的工具支持纹理和粒子。
3、它可以让你做一些其它框架很难做到的事情,例如把视频当做精灵一样处理,或者使用很酷的图形效果和遮罩。
Sprite Kit的缺点:
1、如果使用了Sprite Kit,那么你将被iOS生态圈所绑架,导致你无法很容易对你开发的游戏移植到Android上。
2、Sprite Kit现在还处于初始阶段,此时提供的功能还没有别的框架丰富,例如Cocos2D。最缺的东西应该是暂不支持写自定义的OpenGL代码。
Sprite Kit vs Cocos2D-iPhone vs Cocos2D-X vs Unity
此时,你可能在想“我该选择使用哪个2D框架呢?”这取决于你的实际情况,下面是我的一些想法:
1、如果你是一个初学者,并且只关注于iOS,那么就使用内置的Sprite Kit吧,它非常容易学习,并且完全可以把工作做好。
2、如果需要写自己的OpenGL代码,那么还是使用Cocos2D,或者其它框架吧,目前Sprite Kit并不支持自定义OpenGL代码。
3、如果要进行跨平台开发,那么选择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. // 1
  3. @interface MyScene ()
  4. @property (nonatomic) SKSpriteNode * player;
  5. @end
  6. @implementation MyScene
  7. -(id)initWithSize:(CGSize)size {
  8. if (self = [super initWithSize:size]) {
  9. // 2
  10. NSLog(@"Size: %@", NSStringFromCGSize(size));
  11. // 3
  12. self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
  13. // 4
  14. self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];
  15. self.player.position = CGPointMake(100, 100);
  16. [self addChild:self.player];
  17. }
  18. return self;
  19. }
  20. @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. {
  3. [super viewDidLoad];
  4. // Configure the view.
  5. SKView * skView = (SKView *)self.view;
  6. skView.showsFPS = YES;
  7. skView.showsNodeCount = YES;
  8. // Create and configure the scene.
  9. SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
  10. scene.scaleMode = SKSceneScaleModeAspectFill;
  11. // Present the scene.
  12. [skView presentScene:scene];
  13. }
上面的代码中利用view的边界size创建了场景。不过请注意,当viewDidLoad被调用的时候,在这之前view已经被添加到view层次结构中了,因此它还没有响应出布局的改变。所以view的边界可能还不正确,进而在viewDidLoad中并不是开启场景的最佳时机。
提醒:要想了解更多相关内容,请看由Rob Mayoff带来的最佳解释。
解决方法就是将开启场景代码的过程再靠后一点。用下面的代码替换viewDidLoad:
  1. - (void)viewWillLayoutSubviews
  2. {
  3. [super viewWillLayoutSubviews];
  4. // Configure the view.
  5. SKView * skView = (SKView *)self.view;
  6. if (!skView.scene) {
  7. skView.showsFPS = YES;
  8. skView.showsNodeCount = YES;
  9. // Create and configure the scene.
  10. SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
  11. scene.scaleMode = SKSceneScaleModeAspectFill;
  12. // Present the scene.
  13. [skView presentScene:scene];
  14. }
  15. }
编译并运行程序,可以看到,忍者已经显示在屏幕中了!
如上图所示,可以看到坐标系已经正确了,如果想要把忍者的位置设置为其中间靠左,那么在MyScene.m中用下面的代码来替换设置忍者位置相关的代码:
  1. self.player.position = CGPointMake(self.player.size.width/2, self.frame.size.height/2);
移动怪兽
接下来,我们希望在场景中添加一些怪兽,让忍者进行攻击。为了让游戏更有趣一点,希望怪兽能够移动——否则没有太大的挑战!OK,我们就在屏幕的右边,离屏的方式创建怪兽,并给怪兽设置一个动作:告诉它们往左边移动。
将下面这个方法添加到MyScene.m中:
  1. - (void)addMonster {
  2. // Create sprite
  3. SKSpriteNode * monster = [SKSpriteNode spriteNodeWithImageNamed:@"monster"];
  4. // Determine where to spawn the monster along the Y axis
  5. int minY = monster.size.height / 2;
  6. int maxY = self.frame.size.height - monster.size.height / 2;
  7. int rangeY = maxY - minY;
  8. int actualY = (arc4random() % rangeY) + minY;
  9. // Create the monster slightly off-screen along the right edge,
  10. // and along a random position along the Y axis as calculated above
  11. monster.position = CGPointMake(self.frame.size.width + monster.size.width/2, actualY);
  12. [self addChild:monster];
  13. // Determine speed of the monster
  14. int minDuration = 2.0;
  15. int maxDuration = 4.0;
  16. int rangeDuration = maxDuration - minDuration;
  17. int actualDuration = (arc4random() % rangeDuration) + minDuration;
  18. // Create the actions
  19. SKAction * actionMove = [SKAction moveTo:CGPointMake(-monster.size.width/2, actualY) duration:actualDuration];
  20. SKAction * actionMoveDone = [SKAction removeFromParent];
  21. [monster runAction:[SKAction sequence:@[actionMove, actionMoveDone]]];
  22. }
在上面,我尽量让代码看起来容易理解。首先是通过一个简单的计算,确定怪兽出现的位置,并将该位置设置给怪兽,然后将其添加到场景中。
接着是添加动作(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. self.lastSpawnTimeInterval += timeSinceLast;
  3. if (self.lastSpawnTimeInterval > 1) {
  4. self.lastSpawnTimeInterval = 0;
  5. [self addMonster];
  6. }
  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. [self updateWithTimeSinceLastUpdate:timeSinceLast];
  11. }
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);
  17. }
上面实现了一些标准的矢量函数。如果你看得不是太明白,请看这里关于矢量方法的解释。
接着,在文件中添加一个新的方法:
  1. -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  2. // 1 - Choose one of the touches to work with
  3. UITouch * touch = [touches anyObject];
  4. CGPoint location = [touch locationInNode:self];
  5. // 2 - Set up initial location of projectile
  6. SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:@"projectile"];
  7. projectile.position = self.player.position;
  8. // 3- Determine offset of location to projectile
  9. CGPoint offset = rwSub(location, projectile.position);
  10. // 4 - Bail out if you are shooting down or backwards
  11. if (offset.x <= 0) return;
  12. // 5 - OK to add now - we've double checked position
  13. [self addChild:projectile];
  14. // 6 - Get the direction of where to shoot
  15. CGPoint direction = rwNormalize(offset);
  16. // 7 - Make it shoot far enough to be guaranteed off screen
  17. CGPoint shootAmount = rwMult(direction, 1000);
  18. // 8 - Add the shoot amount to the current position
  19. CGPoint realDest = rwAdd(shootAmount, projectile.position);
  20. // 9 - Create the actions
  21. float velocity = 480.0/1.0;
  22. float realMoveDuration = self.size.width / velocity;
  23. SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];
  24. SKAction * actionMoveDone = [SKAction removeFromParent];
  25. [projectile runAction:[SKAction sequence:@[actionMove, actionMoveDone]]];
  26. }
上面的代码中做了很多事情,我们来详细看看。
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];
  5. }
当怪物和炮弹发生碰撞,上面的代码会将他们从场景中移除。很简单吧!
下面该实现contact delegate方法了。将如下方法添加到文件中:
  1. - (void)didBeginContact:(SKPhysicsContact *)contact
  2. {
  3. // 1
  4. SKPhysicsBody *firstBody, *secondBody;
  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. // 2
  16. if ((firstBody.categoryBitMask & projectileCategory) != 0 &&
  17. (secondBody.categoryBitMask & monsterCategory) != 0)
  18. {
  19. [self projectile:(SKSpriteNode *) firstBody.node didCollideWithMonster:(SKSpriteNode *) secondBody.node];
  20. }
  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. @interface GameOverScene : SKScene
  3. -(id)initWithSize:(CGSize)size won:(BOOL)won;
  4. @end
在上面的代码中导入了Sprite Kit头文件,并声明了一个特定的初始化方法,该方法的第一个参数用来定位显示的位置,第二个参数won用来判断用户是否赢了。
接着用下面的代码替换GameOverLayer.m中的内容:
  1. #import "GameOverScene.h"
  2. #import "MyScene.h"
  3. @implementation GameOverScene
  4. -(id)initWithSize:(CGSize)size won:(BOOL)won {
  5. if (self = [super initWithSize:size]) {
  6. // 1
  7. self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
  8. // 2
  9. NSString * message;
  10. if (won) {
  11. message = @"You Won!";
  12. } else {
  13. message = @"You Lose :[";
  14. }
  15. // 3
  16. SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
  17. label.text = message;
  18. label.fontSize = 40;
  19. label.fontColor = [SKColor blackColor];
  20. label.position = CGPointMake(self.size.width/2, self.size.height/2);
  21. [self addChild:label];
  22. // 4
  23. [self runAction:
  24. [SKAction sequence:@[
  25. [SKAction waitForDuration:3.0],
  26. [SKAction runBlock:^{
  27. // 5
  28. SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
  29. SKScene * myScene = [[MyScene alloc] initWithSize:self.size];
  30. [self.view presentScene:myScene transition: reveal];
  31. }]
  32. ]]
  33. ];
  34. }
  35. return self;
  36. }
  37. @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];
  6. }
编译并运行程序,尝试一下赢了和输了会看到的画面!
何去何从?
至此Sprite Kit教程:初学者结束!这里可以下到完整的代码。
希望本文能帮助你学习Sprite Kit,并写出你自己的游戏!
如果你希望学习更多相关Sprite Kit内容,可以看看这本书:iOS Games by Tutorials。本书会告诉你需要知道的内容——从物理,到tile map,以及特定的系统,甚至是制作自己的关卡编辑器。
转载自:http://www.cocoachina.com/applenews/devnews/2013/0930/7087.html

Sprite Kit教程相关推荐

  1. iOS Sprite Kit教程之滚动场景

    iOS Sprite Kit教程之滚动场景 滚动场景 在很多的游戏中,场景都不是静止的,而是滚动的,如在植物大战僵尸的游戏中,它的场景如图2.26所示. 图2.26  植物大战僵尸 在图2.26中,用 ...

  2. iOS Sprite Kit教程之场景的切换

    iOS Sprite Kit教程之场景的切换 Sprite Kit中切换场景 每一个场景都不是单独存在的.玩家可以从一个场景中切换到另外一个场景中.本小节,我们来讲解场景切换.在每一个游戏中都会使用到 ...

  3. iOS Sprite Kit教程之场景的设置

    iOS Sprite Kit教程之场景的设置 Sprite Kit中设置场景 在图2.8所示的效果中,可以看到新增的场景是没有任何内容的,本节将讲解对场景的三个设置,即颜色的设置.显示模式的设置以及测 ...

  4. iOS Sprite Kit教程之真机测试以及场景的添加与展示

    iOS Sprite Kit教程之真机测试以及场景的添加与展示 IOS实现真机测试 在进行真机测试之前,首先需要确保设备已经连在了Mac(或者Mac虚拟机)上,在第1.9.1小节开始,设备就一直连接在 ...

  5. iOS Sprite Kit教程之申请和下载证书

    iOS Sprite Kit教程之申请和下载证书 模拟器虽然可以实现真机上的一些功能,但是它是有局限的.例如,在模拟器上没有重力感应.相机机等.如果想要进行此方面的游戏的开发,进行程序测试时,模拟器显 ...

  6. iOS Sprite Kit教程之使用帮助文档以及调试程序

    iOS Sprite Kit教程之使用帮助文档以及调试程序 IOS中使用帮助文档 在编写代码的时候,可能会遇到很多的方法.如果开发者对这些方法的功能,以及参数不是很了解,就可以使用帮助文档.那么帮助文 ...

  7. iOS Sprite Kit教程之编写程序以及Xcode的介绍

    iOS Sprite Kit教程之编写程序以及Xcode的介绍 Xcode界面介绍 一个Xcode项目由很多的文件组成,例如代码文件.资源文件等.Xcode会帮助开发者对这些文件进行管理.所以,Xco ...

  8. ios游戏开发 Sprite Kit教程:初学者 2

    2019独角兽企业重金招聘Python工程师标准>>> 注:本文译自Sprite Kit Tutorial for Beginners 目录 Sprite Kit的优点和缺点 Spr ...

  9. Sprite Kit教程:动画和纹理图集 2

    2019独角兽企业重金招聘Python工程师标准>>> 注:本文译自Sprite Kit Tutorial: Animations and Texture Atlases 目录 创建 ...

最新文章

  1. 大型Web前端架构设计:面向抽象编程入门
  2. python中文件读写位置的作用-python配置文件的读写
  3. 强化学习(三)---马尔科夫决策过程
  4. markdown单元格快速合并(不用自己写html代码)
  5. 钉钉 ISV 应用开发的一些心得
  6. javascript-定时器演练-时钟-Date类
  7. 《R语言数据分析与挖掘实战》——3.2 数据特征分析
  8. XCode中的单元测试:编写测试类和方法(内容意译自苹果官方文档)
  9. android ar人脸贴图,ARCore与ARKit实现人脸贴纸、更换材质等动画效果
  10. Beacon Mountain 测试版 – 常见问题解答
  11. HDU6608 Fansblog【Miller_Rabin素性测试算法+威尔逊定理】
  12. Mac桌面动态壁纸Dynamic Wallpaper for Mac
  13. APEX光学分析设计软件
  14. Invenio 数字图书馆框架
  15. word:回车替换成空格
  16. ChatGpt:OpenAI 最近推出了一款聊天AI ——ChatGPT
  17. 用友U9sv服务打开时报错内存入口检查失败,因为可用内存(371662848 字节)少于总内存的 5%
  18. 自然语言处理之hmm(隐马尔可夫模型)
  19. 设备唯一标识方法(Unique Identifier):如何在Windows系统上获取设备的唯一标识
  20. 微信小程序开发实战11_4 微信支付退款流程

热门文章

  1. aspectj tomcat load-time waver
  2. 1 自定义无边框窗体
  3. 苹果手机使用技巧篇:教你完美使用好苹果手机的4个方法
  4. 谷歌无法加载pdf文档_如何从Google文档文档创建PDF
  5. Frida-dexdump使用,frida环境配置
  6. font color=red[置顶]/font
  7. matlab编写扫雷,MATLAB版本的扫雷小游戏
  8. Qlikview---集合分析
  9. 【Docker容器镜像加速器~阿里云镜像加速器】
  10. Last packet sent to the server was 2 ms ago 解决办法