從本篇文章開始,我將陸續用至少三篇文章介紹一下我我的的第一款上線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/