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開發工具包的最新動態及培訓信息。關注微信能夠第一時間瞭解最新產品和服務動態,微信在手,天下我有!微信

相關文章
相關標籤/搜索