【个人第一款游戏类App(《跑酷好基友》 英文名:BothLive) 登陆App Store(一)】使用iOS7推出的Sprite Kit框架制做一款2D跑酷小游戏

从本篇文章开始,我将陆续用至少三篇文章介绍一下我我的的第一款上线App Store的游戏:“跑酷好基友”,英文名BothLive。从游戏制做、社交分享、App上传审核,以及版本更新迭代(若是有)几个方面来介绍。目前,这只是一个很是很是easy的超轻量级游戏。html

 

说来也颇有意思,本人一直从事iOS应用客户端的开发,对于iOS游戏制做历来也没花时间和心思。可是一个偶然的机会:2014年3月份公司派我去南京晓庄学院作一场开发讲座,讲座中须要向同窗们演示一个小游戏的开发过程,因而我便利用iOS7推出的全新的Sprite Kit游戏引擎,制做了一个简单的游戏动画(小人在走动的场景)成功地作了演示。ios

回来之后我以为Sprite Kit是个有意思的框架,既然都接触了它,为什么不接着玩下去?正巧今年(2014年)夏天,微信上掀起了一阵“弱智小游戏分享大比拼”的小高潮,其实颇有点相似当年“校内网”(今“人人网”)小游戏风靡的感受。因而我便萌生了一个利用Sprite Kit移植(或者说山寨)一个网页小游戏到iOS端的想法。能够说是纯玩票!本篇文章将介绍Sprite Kit,以及全部的开发要点。git

首先附上App下载连接:https://itunes.apple.com/us/app/pao-ku-hao-ji-you/id914554369?mt=8 欢迎朋友们下载试玩,彻底免费哦!github

同时,他也是开源项目,供你们参考:https://github.com/pigpigdaddy/BothLive数组

这是一款跑酷类游戏,玩家同时控制上下两个小人,跳跃避开前方到来的不一样障碍,若是上下其中任何一个小人在跳跃时撞击到障碍物则游戏结束,计时器会记下游戏持续时间,并在结束后提示是否分享到微信。微信

 

接着切入正题,我将分为四个小节来介绍:app

1:背景框架

一般咱们知道iOS上作2D游戏的好比cocos2D,不过今天我用的不是它,而是WWDC2013上伴随iOS7一道而来的Spirte Kit框架。节选OneVcat大神的介绍:“Sprite的中文译名就是精灵,在游戏开发中,精灵指的是以图像方式呈如今屏幕上的一个图像。这个图像也许能够移动,用户能够与其交互,也有可能仅只是游戏的一个静止的背景图。塔防游戏中敌方源源不断涌来的每一个小兵都是一个精灵,我方防护塔发出的炮弹也是精灵。能够说精灵构成了游戏的绝大部分主体视觉内容,而一个2D引擎的主要工做,就是高效地组织,管理和渲染这些精灵。SpriteKit是在iOS7 SDK中Apple新加入的一个2D游戏引擎框架,在SpriteKit出现以前,iOS开发平台上已经出现了像cocos2d这样的比较成熟的2D引擎解决方案。SpriteKit展示出的是Apple将Xcode和iOS/Mac SDK打形成游戏引擎的野心,可是同时也确实与IDE有着更好的集成,减小了开发者的工做。”(http://onevcat.com/2013/06/sprite-kit-start/dom

那么对应我这款“跑酷好基友”游戏,“精灵”也就是“Sprite”则是上下跑动的两个小人,以及不断出现的障碍物。ide

 

2:建立一个Sprite Kit项目

很简单,在XCode中新建项目-->选择Sprite Kit模板-->填写项目名称等信息-->肯定便可:(截图自XCode 5.1)

成功建立后咱们将获得一个与singleView application结构很类似的项目,包含Appdelegate、ViewController以及一个叫MyScene的类。查看ViewController的viewDidLoad函数你会发现是以下结构:

 1 - (void)viewDidLoad
 2 {
 3     [super viewDidLoad];
 4 
 5     // Configure the view.
 6     SKView * skView = (SKView *)self.view;
 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 }

SKView:继承自UIView,这里做为viewController的view,他的做用是负责全部的动画和渲染;

SKScene:继承自SKEffectNode(:SKNode),做用是全部的“精灵”都会被放在一个SKScene实例上,再被加载在SKView上。一个SKView只能用拥有一个SKScene,可是你能够建立多个SKScene,在SKView上切换显示他们。

在模板帮咱们实现好的MyScene中,咱们能够看到一些操做,好比建立了一些文字用于显示、实现了SKScene的-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event函数,并建立了一些东西,咱们能够猜想这是在实现玩家交互时的一些操做,下一节将介绍他们。

 

3:建立小精灵

3.0:要点介绍

查看SpriteKit.h咱们会发现:

1 #import <SpriteKit/SKScene.h>
2 #import <SpriteKit/SKNode.h>
3 #import <SpriteKit/SKSpriteNode.h>
4 #import <SpriteKit/SKEmitterNode.h>
5 #import <SpriteKit/SKShapeNode.h>
6 #import <SpriteKit/SKEffectNode.h>
7 #import <SpriteKit/SKLabelNode.h>
8 #import <SpriteKit/SKVideoNode.h>
9 #import <SpriteKit/SKCropNode.h>

没错,很是多的Node。(详细官方介绍请看这里:https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/OtherNodeClasses/OtherNodeClasses.html#//apple_ref/doc/uid/TP40013043-CH10-SW1

SKNode:他是你在Sprite Kit中用到的最基本单元,上面各类Node(包括咱们的SKScene)都是继承自SKNode。查看SKNode的接口文件,你会发现它具备:位置、缩放、速度等多种属性,也有添加、删除、运行各类“Action”等诸多方法(函数)。

聪明的你必定猜到了,咱们的跑酷小人,就是一个SKNode,并且准确的说,是一个SKSpriteNode(SKSpriteNode拥有更多SKNode所没有的方法,而这些方法正是我游戏中跑酷小人所须要的)。

SKTexture:SKTexture是一个能够从图片文件读取的类型,咱们能够经过SKTexture来读取图片从而建立一个SKSpriteNode,或者用于给SKSpriteNode建立动做时切换图片,好比咱们这里就用了一个小人的图片去建立了一个SKSpriteNode。这样,在未给这个SKSpriteNode制定任何Action(动做)时,SKSpriteNode就是一个静态的小人。

SKTextureAtlas:Atlas顾名思义就是“图集”的意思,使用SKTextureAtlas从bundle中读取多张图片,这些图片会以某种“格式”(不清楚是否为一个个SKTexture)存储在SKTextureAtlas实例中,每次你须要经过图片名称就能够从其中取出一个SKTexture类型的实例,这样你就能够用这个SKTexture,建立或修改SKSpriteNode了。另外,由于SKTextureAtlas自己内部并不能直接获取到存有SKTexture元素的数组(只能获取到SKTexture名称数组),因此一般咱们又会将SKTextureAtlas读取到的图片所有以SKTexture形式逐一取出来,存放在本身定义的数组中,便于后期使用Action(动做)操做时使用。

到此,我介绍了SKView、SKScene、SKSpriteNode、SKTextureAtlas、SKTexture,我将使用这五元虎将构成咱们的静态界面。

3.1,建立SKView

我将storyboard删除,而后使用本身建立的SKView。

1 @property (nonatomic, strong)SKView *skView;
1 self.skView = [[SKView alloc] initWithFrame:self.view.bounds];
2 [self.view addSubview:self.skView];

3.2,建立SKScene

沿用官方模板中的MyScene类(里面的内容彻底替换,稍后再作说明)

1 @property (nonatomic, strong)MyScene *scene;
1 self.scene = [MyScene sceneWithSize:self.skView.bounds.size];
2 self.scene.scaleMode = SKSceneScaleModeAspectFill;
3 self.scene.delegate = self;
4     
5 // Present the scene.
6 [self.skView presentScene:self.scene];

3.3,建立静态的小精灵(用SKTextureAtlas、SKTexture建立SKSpriteNode)

 有一些游戏(动画)开发经验的都应该知道,不少时候,一个物体的动做是能够分解为一帧一帧的图片的,而SKTextureAtlas读取的正是由美术设计师作出来的多幅连贯的图片。例如说一个跑的动做,我这里分解为20幅图片,并按顺序编号命名,最后加入到项目中:

注意!文件夹的命名方式,应以“.atlas”结尾。

正如3.0中我所写到的,咱们要用SKTextureAtlas读取图片,再以SKTexture格式保存在自定义数组中,并用其中一个SKTexture初始化SKSpriteNode。因而:

1 @property (nonatomic, strong) SKSpriteNode *upBaby;
2 @property (nonatomic, strong) NSMutableArray *babyRunFrames
 1     //
 2     SKTextureAtlas *babyRunAnimatedAtlas = [SKTextureAtlas atlasNamed:@"babyRun"];
 3     /* 建立存放每一帧图片的数组 */
 4     self.babyRunFrames = [NSMutableArray array];
 5     /* 将每一帧图片加进数组 */
 6     for (int i=1; i <= babyRunAnimatedAtlas.textureNames.count; i++) {
 7         NSString *textureName = [NSString stringWithFormat:@"r%d@2x.png", i];
 8         SKTexture *temp = [babyRunAnimatedAtlas textureNamed:textureName];
 9         [self.babyRunFrames addObject:temp];
10     }
1     SKTexture *temp = self.babyRunFrames[0];
2     self.upBaby = [SKSpriteNode spriteNodeWithTexture:temp];
3     self.upBaby.position = CGPointMake(BABY_X_POSITION, self.size.height/2+30);
4     [self addChild:self.upBaby];

最后,将建立好的SKSpriteNode,用addChild方法加到SKScene中。(请暂时忽略摆放的位置:position,稍后将作说明)

 

4,让小精灵动起来

4.1:SKAction

Sprite Kit中管理小精灵动做的类是SKAction。

打开SKAction.h文件你立刻就会发现有不少不少种“Action”,并且通俗易懂,例如:

1 + (SKAction *)moveByX:(CGFloat)deltaX y:(CGFloat)deltaY duration:(NSTimeInterval)sec;
1 + (SKAction *)rotateByAngle:(CGFloat)radians duration:(NSTimeInterval)sec;
1 + (SKAction *)resizeByWidth:(CGFloat)width height:(CGFloat)height duration:(NSTimeInterval)duration;
1 + (SKAction *)scaleBy:(CGFloat)scale duration:(NSTimeInterval)sec;
1 + (SKAction *)repeatAction:(SKAction *)action count:(NSUInteger)count;

太棒了,是否是立刻就能用了?等等,在此以前,咱们至少应该先来看一下如何运行这些Action吧。

显而易见,运行的主体必定是SKSpriteNode,并且支持的方法以下:

1 - (void)runAction:(SKAction *)action;
2 - (void)runAction:(SKAction *)action completion:(void (^)())block;
3 - (void)runAction:(SKAction *)action withKey:(NSString *)key;

也就是说,SKSpriteNode能够用这些方法,来“运行”建立好的SKAction。

而更重要的是,运行的方式也分为两种,一种是“序列式”,一种是“混合式”:

1 + (SKAction *)sequence:(NSArray *)actions;
2 
3 + (SKAction *)group:(NSArray *)actions;

通俗易懂,very nice!

4.2 为SKSpriteNode添加Action!

咱们须要让小精灵SKSpriteNode在刚一出现的时候,就开始“跑步”。所以,咱们要向它添加跑步的Action!

如今来分析一下。跑,实际上是原地进行的一种动做,由于其实是障碍物从远方移动过来,所以咱们用:

1 [SKAction animateWithTextures:self.babyRunFrames
2                           timePerFrame:0.05f
3                                      resize:NO
4                                     restore:YES]

来从数组中获取每个动做的图片(SKTexture),设置他们每0.05秒切换到下一张,以此来建立一个SKAction。

再用repeatActionForever,来代表这是一个无限循环的Action:

1 SKAction *runAction = [SKAction repeatActionForever:
2                            [SKAction animateWithTextures:self.babyRunFrames
3                                             timePerFrame:0.05f
4                                                   resize:NO
5                                                  restore:YES]];
6 [self.upBaby runAction:runAction withKey:BABY_ACTION_RUN];

最后调用SKSpriteNode的runAction方法,来启动这个动做。此时“好基友”的其中一位,就开始在原地跑步了!

除了“原地跑”,咱们应该还有“向上跳并下落”的动做,这里就要稍微复杂一些了。由于能够分析得出来,向上跳、向下落,是一个小精灵位置改变的过程,与此同时,“跳”的时候,自己小精灵应该也是有一系列动做的。所以,这里我用到了上面提到的两个“组合式动做”的方法:group和sequence。

另外,锦上添花的是,为了看起来天然,我让小精灵在调到顶端最高点时,hold住一个很小的时间间隔!用到了:

1 [SKAction waitForDuration:0.08];

这样一个方法。最终以下:

 1     // 删除跑的动画
 2     [self.upBaby removeAllActions];
 3     
 4     /* 跳的动画 */
 5     SKAction *jumpAction = [SKAction animateWithTextures:self.babyJumpFrames
 6                                             timePerFrame:0.04f
 7                                                   resize:NO
 8                                                  restore:YES];
 9     float duration = 0.8;
10     /* 向上移动的动画 */
11     SKAction *moveUpAction = [SKAction moveTo:CGPointMake(self.upBaby.position.x, self.upBaby.position.y+BABY_JUMP_HEIGHT) duration:duration/2-0.04];
12     SKAction *holdAction = [SKAction waitForDuration:0.08];
13     SKAction *moveDownAction = [SKAction moveTo:CGPointMake(self.upBaby.position.x, self.upBaby.position.y) duration:duration/2-0.04];
14     
15     [self.upBaby runAction:[SKAction group:@[jumpAction, [SKAction sequence:@[moveUpAction, holdAction, moveDownAction]]]] completion:^{
16         self.isUpBabyAction = NO;
17         /* 跑的动画 */
18         SKAction *runAction = [SKAction repeatActionForever:
19                                [SKAction animateWithTextures:self.babyRunFrames
20                                                 timePerFrame:0.05f
21                                                       resize:NO
22                                                      restore:YES]];
23         [self.upBaby runAction:runAction withKey:BABY_ACTION_RUN];
24     }];

首先,我删除了以前全部的动做(在这里就是“跑”的动做);而后我建立了四个动做:1,跳的动做、2,向上位移的动做、3,hold住0.08秒的动做、4,向下的动做;

接着,经过group和sequence将他们组合在一块儿;最后,在作完这一系列动做后的完成回调里,回复原先的跑的动做!!

4.3,接受用户点击事件

咱们的需求,是玩家能够点击屏幕上任意一个地方,让小精灵跳起来。所以这里咱们能够在SKScene(继承自UIResponder)的方法

1 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;

中,处理点击事件,让小精灵跳起来(使用4.2中的跳的action)。很是简单的操做,我在这里就不详细说明了,详见文章开头连接的源代码。

 

5,碰撞检测

5.1 建立障碍物

咱们的障碍物是一堵堵高矮不一的墙,思路是将这一堵堵墙设置为SKSpriteNode,并经过一个“定时”操做,不断地从右侧出现,移动到左侧。

 1 - (void)addObstacle:(NSString *)upOrDown{
 2     // 建立怪物Sprite
 3     int i = (arc4random() % 8) + 1;
 4     SKTexture *temp = self.obstacleFrames[i-1];
 5     SKSpriteNode *obstacle = [SKSpriteNode spriteNodeWithTexture:temp];
 6     if (!self.obstacleNodes) {
 7         self.obstacleNodes = [NSMutableArray array];
 8     }
 9     [self.obstacleNodes addObject:obstacle];
10     
11     CGFloat yPoint = 0.0f;
12     
13     if ([upOrDown intValue] == 1) {
14         //
15         yPoint = self.frame.size.height/2+obstacle.size.height/2+15;
16     }else{
17         yPoint = obstacle.size.height/2+15;
18     }
19     obstacle.position = CGPointMake(self.frame.size.width + obstacle.size.width/2, yPoint);
20     [self addChild:obstacle];
21     
22     // Create the actions
23     SKAction * actionMove = [SKAction moveTo:CGPointMake(-obstacle.size.width/2, yPoint) duration:1.3];
24     SKAction * actionMoveDone = [SKAction removeFromParent];
25     __weak MyScene *blockSelf = self;
26     [obstacle runAction:[SKAction sequence:@[actionMove, actionMoveDone]] completion:^{
27         [blockSelf.obstacleNodes removeObject:obstacle];
28         [obstacle removeFromParent];
29     }];
30     
31     obstacle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:obstacle.size]; // 1
32     obstacle.physicsBody.dynamic = YES; // 2
33     obstacle.physicsBody.categoryBitMask = obstacleCategory; // 3
34     obstacle.physicsBody.contactTestBitMask = babyCategory; // 4
35     obstacle.physicsBody.collisionBitMask = 0; // 5
36 }

为了让游戏中障碍物的出现显得变化无穷,我经过获取随机数来从存有障碍物SKTexture随机的拿到某个SKTexture,用它建立SKSpriteNode障碍物,并经过设置SKAction,让障碍物从右侧移动进来直到左侧移出屏幕,并在完成这一动做后,删除这一SKSpriteNode,以及它上面的动做。

5.2,定时刷新

Sprite Kit会在没一帧都调用

1 - (void)update:(NSTimeInterval)currentTime

这个方法。这里偷了个懒,1.0版本中的刷新方法使用了http://www.raywenderlich.com/zh-hans/51919/sprite-kit-%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B 中的刷新原理,原文也说了,这是苹果官方范例游戏Adventure中的写法,读者能够借鉴一下。其实我也有另外一种刷新方式,打算在1.1版本中尝试一下。

1.0版本的刷新方法,详情请看源代码吧,我在文章最开始已经作了github连接。

5.3,设置碰撞相关属性

首先设置两个种类,分别是两位“好基友”和一系列的障碍物:

1 static const uint32_t obstacleCategory    =  0x1 << 0;
2 static const uint32_t babyCategory        =  0x1 << 1;

接着设置没有重力的物理体系,以及回调对象:

1 self.physicsWorld.gravity = CGVectorMake(0,0);
2 self.physicsWorld.contactDelegate = self;

为“好基友”设置相关碰撞属性:

1     self.upBaby.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.upBaby.size.width/2];
2     self.upBaby.physicsBody.dynamic = YES;
3     self.upBaby.physicsBody.categoryBitMask = babyCategory;
4     self.upBaby.physicsBody.contactTestBitMask = obstacleCategory;
5     self.upBaby.physicsBody.collisionBitMask = 0;
6     self.upBaby.physicsBody.usesPreciseCollisionDetection = YES;

1,为“小人”sprite建立物理外形;

2,将“小人”物理外形的dynamic(动态)属性置为YES。这表示它的移动不会被物理引擎所控制;

3,把“小人”物理外形的种类掩码设为刚刚定义的babyCategory;

4,当发生碰撞时,当前小人对象会通知它contactTestBitMask 这个属性所表明的category。这里应该把障碍物的种类掩码obstacleCategory

赋给它;

5,collisionBitMask这个属性表示哪些种类的对象与当前小人对象相碰撞时物理引擎要让其有所反应(好比回弹效果)。你并不想让小人和障碍物彼此之间发生回弹,设置这个属性为0吧。固然这在其余游戏里是可能的。

6,usesPreciseCollisionDetection属性设置为YES。这对于快速移动的物体很是重要(例如炮弹),若是不这样设置的话,有可能快速移动的两个物体会直接相互穿过去,而不会检测到碰撞的发生。

(以上注释借鉴了:http://www.raywenderlich.com/zh-hans/51919/sprite-kit-%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B

另外,在5.1中,咱们也对障碍物进行了响应的碰撞属性设置。

最后,一旦发生碰撞,就会经过回调反映出来:

1 - (void)didBeginContact:(SKPhysicsContact *)contact

从中咱们能够分析出具体是哪个“小人”与哪个障碍物发生了碰撞,并做出“游戏结束”的通知。

 

参考资料:

http://onevcat.com/2013/06/sprite-kit-start/
http://blog.csdn.net/kobbbb/article/details/9093601
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Introduction/Introduction.html
http://www.raywenderlich.com/zh-hans/51919/sprite-kit-%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B
http://beyondvincent.com/blog/2013/10/12/114-spritekit-tutorial-for-beginners-3/

相关文章
相关标签/搜索