
轉自破船之家,原文: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,並用下面的內容替換之:
- #import "MyScene.h"
- // 1
- @interface MyScene ()
- @property (nonatomic) SKSpriteNode * player;
- @end
- @implementation MyScene
- -(id)initWithSize:(CGSize)size {
- if (self = [super initWithSize:size]) {
- // 2
- NSLog(@"Size: %@", NSStringFromCGSize(size));
- // 3
- self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
- // 4
- self.player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];
- self.player.position = CGPointMake(100, 100);
- [self addChild:self.player];
- }
- return self;
- }
- @end
咱們來看看上面的代碼。
1.爲了給player(例如忍者)聲明一個私有變量,在這裏建立了一個私有的interface,以後能夠把這個私有變量添加到場景中。
2.在這裏打印出了場景的size,至於什麼緣由很快你就會看到了。
3.在Sprite Kit中設置一個場景的背景色很是簡單——只須要設置backgroundColor屬性,在這裏將其設置位白色。
4.在Sprite Kit場景中添加一個精靈一樣很是簡單,只須要使用spriteNodeWithImageNamed方法,並把一副圖片的名稱傳遞進去就能夠建立一個精 靈。接着設置一下精靈的位置,而後調用addChild方法將該精靈添加到場景中。在代碼中將忍者的位置設置爲(100, 100),該位置是從屏幕的左下角到右上角計算的。
編譯並運行,看看效果如何…

呀!屏幕是白色的,並無看到忍者。這是爲何呢?你可能在想設計之初就是這樣的,實際上這裏有一個問題。
若是你觀察一下控制檯輸出的內容,會看到以下內容
- SpriteKitSimpleGame[3139:907] Size: {320, 568}
可能你會認爲場景的寬度是320,高度則是568——實際上恰好相反!
咱們來看看具體發生了什麼:定位到ViewController.m的viewDidLoad方法:
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- // Configure the view.
- SKView * skView = (SKView *)self.view;
- skView.showsFPS = YES;
- skView.showsNodeCount = YES;
- // Create and configure the scene.
- SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
- scene.scaleMode = SKSceneScaleModeAspectFill;
- // Present the scene.
- [skView presentScene:scene];
- }
上面的代碼中利用view的邊界size建立了場景。不過請注意,當viewDidLoad被調用的時候,在這以前view已經被添加到 view層次結構中了,所以它尚未響應出佈局的改變。因此view的邊界可能還不正確,進而在viewDidLoad中並非開啓場景的最佳時機。
提醒:要想了解更多相關內容,請看由Rob Mayoff帶來的最佳解釋。
解決方法就是將開啓場景代碼的過程再靠後一點。用下面的代碼替換viewDidLoad:
- - (void)viewWillLayoutSubviews
- {
- [super viewWillLayoutSubviews];
- // Configure the view.
- SKView * skView = (SKView *)self.view;
- if (!skView.scene) {
- skView.showsFPS = YES;
- skView.showsNodeCount = YES;
- // Create and configure the scene.
- SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
- scene.scaleMode = SKSceneScaleModeAspectFill;
- // Present the scene.
- [skView presentScene:scene];
- }
- }
編譯並運行程序,能夠看到,忍者已經顯示在屏幕中了!

如上圖所示,能夠看到座標系已經正確了,若是想要把忍者的位置設置爲其中間靠左,那麼在MyScene.m中用下面的代碼來替換設置忍者位置相關的代碼:
- self.player.position = CGPointMake(self.player.size.width/2, self.frame.size.height/2);
移動怪獸
接下來,咱們但願在場景中添加一些怪獸,讓忍者進行攻擊。爲了讓遊戲更有趣一點,但願怪獸可以移動——不然沒有太大的挑戰!OK,咱們就在屏幕的右邊,離屏的方式建立怪獸,並給怪獸設置一個動做:告訴它們往左邊移動。
將下面這個方法添加到MyScene.m中:
- - (void)addMonster {
- // Create sprite
- SKSpriteNode * monster = [SKSpriteNode spriteNodeWithImageNamed:@"monster"];
- // Determine where to spawn the monster along the Y axis
- int minY = monster.size.height / 2;
- int maxY = self.frame.size.height - monster.size.height / 2;
- int rangeY = maxY - minY;
- int actualY = (arc4random() % rangeY) + minY;
- // Create the monster slightly off-screen along the right edge,
- // and along a random position along the Y axis as calculated above
- monster.position = CGPointMake(self.frame.size.width + monster.size.width/2, actualY);
- [self addChild:monster];
- // Determine speed of the monster
- int minDuration = 2.0;
- int maxDuration = 4.0;
- int rangeDuration = maxDuration - minDuration;
- int actualDuration = (arc4random() % rangeDuration) + minDuration;
- // Create the actions
- SKAction * actionMove = [SKAction moveTo:CGPointMake(-monster.size.width/2, actualY) duration:actualDuration];
- SKAction * actionMoveDone = [SKAction removeFromParent];
- [monster runAction:[SKAction sequence:@[actionMove, actionMoveDone]]];
- }
在上面,我儘可能讓代碼看起來容易理解。首先是經過一個簡單的計算,肯定怪獸出現的位置,並將該位置設置給怪獸,而後將其添加到場景中。
接着是添加動做(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中添加以下屬性:
- @property (nonatomic) NSTimeInterval lastSpawnTimeInterval;
- @property (nonatomic) NSTimeInterval lastUpdateTimeInterval;
經過lastSpawnTimeInterval能夠記錄着最近出現怪獸時的時間,而lastUpdateTimeInterval能夠記錄着上次更新時的時間。
接着,咱們寫一個方法,該方法在畫面每一幀更新的時候都會被調用。記住,該方法不會被自動調用——須要另外寫一個方法來調用它:
- - (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast {
- self.lastSpawnTimeInterval += timeSinceLast;
- if (self.lastSpawnTimeInterval > 1) {
- self.lastSpawnTimeInterval = 0;
- [self addMonster];
- }
- }
上面的代碼中簡單的將上次更新(update調用)的時間追加到self.lastSpawnTimeInterval中。一旦該時間大於1秒,就在場景中新增一個怪獸,並將lastSpawnTimeInterval重置。
最後,添加以下方法來調用上面的方法:
- - (void)update:(NSTimeInterval)currentTime {
- // Handle time delta.
- // If we drop below 60fps, we still want everything to move the same distance.
- CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;
- self.lastUpdateTimeInterval = currentTime;
- if (timeSinceLast > 1) { // more than a second since last update
- timeSinceLast = 1.0 / 60.0;
- self.lastUpdateTimeInterval = currentTime;
- }
- [self updateWithTimeSinceLastUpdate:timeSinceLast];
- }
Sprite Kit在顯示每幀時都會調用上面的update:方法。
上面的代碼實際上是來自蘋果提供的Adventure示例中。該方法會傳入當前的時間,在其中,會作一些計算,以肯定出上一幀更新的時間。注意,在代碼中作了一些合理性的檢查,以免從上一幀更新到如今已通過去了大量時間,而且將間隔重置爲1/60秒,避免出現奇怪的行爲。
如今編譯並運行程序,能夠看到許多怪獸從左邊移動到屏幕右邊並消失。

發射炮彈
如今咱們開始給忍者添加一些動做,首先從發射炮彈開始!實際上有多種方法來實現炮彈的發射,不過,在這裏要實現的方法時當用戶tap屏幕時,從忍者的方位到tap的方位發射一顆炮彈。
因爲本文是針對初級開發者,因此在這裏我使用moveTo:動做來實現,不過這須要作一點點的數學運算——由於moveTo:方法須要指定炮彈 的目的地,可是又不能直接使用touch point(由於touch point僅僅表明須要發射的方向)。實際上咱們須要讓炮彈穿過touch point,直到炮彈在屏幕中消失。
以下圖,演示了上面的相關內容:

如圖所示,咱們能夠經過origin point到touch point獲得一個小的三角形。咱們要作的就是根據這個小三角形的比例建立出一個大的三角形——而你知道你想要的一個端點是離開屏幕的地方。
爲了作這個計算,若是有一些基本的矢量方法可供調用(例如矢量的加減法),那麼會很是有幫助,但很不幸的時Sprite Kit並無提供相關方法,因此,咱們必須本身實現。
不過很幸運的時這很是容易實現。將下面的方法添加到文件的頂部(implementation以前):
- static inline CGPoint rwAdd(CGPoint a, CGPoint b) {
- return CGPointMake(a.x + b.x, a.y + b.y);
- }
- static inline CGPoint rwSub(CGPoint a, CGPoint b) {
- return CGPointMake(a.x - b.x, a.y - b.y);
- }
- static inline CGPoint rwMult(CGPoint a, float b) {
- return CGPointMake(a.x * b, a.y * b);
- }
- static inline float rwLength(CGPoint a) {
- return sqrtf(a.x * a.x + a.y * a.y);
- }
- // Makes a vector have a length of 1
- static inline CGPoint rwNormalize(CGPoint a) {
- float length = rwLength(a);
- return CGPointMake(a.x / length, a.y / length);
- }
上面實現了一些標準的矢量函數。若是你看得不是太明白,請看這裏關於矢量方法的解釋。
接着,在文件中添加一個新的方法:
- -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
- // 1 - Choose one of the touches to work with
- UITouch * touch = [touches anyObject];
- CGPoint location = [touch locationInNode:self];
- // 2 - Set up initial location of projectile
- SKSpriteNode * projectile = [SKSpriteNode spriteNodeWithImageNamed:@"projectile"];
- projectile.position = self.player.position;
- // 3- Determine offset of location to projectile
- CGPoint offset = rwSub(location, projectile.position);
- // 4 - Bail out if you are shooting down or backwards
- if (offset.x <= 0) return;
- // 5 - OK to add now - we've double checked position
- [self addChild:projectile];
- // 6 - Get the direction of where to shoot
- CGPoint direction = rwNormalize(offset);
- // 7 - Make it shoot far enough to be guaranteed off screen
- CGPoint shootAmount = rwMult(direction, 1000);
- // 8 - Add the shoot amount to the current position
- CGPoint realDest = rwAdd(shootAmount, projectile.position);
- // 9 - Create the actions
- float velocity = 480.0/1.0;
- float realMoveDuration = self.size.width / velocity;
- SKAction * actionMove = [SKAction moveTo:realDest duration:realMoveDuration];
- SKAction * actionMoveDone = [SKAction removeFromParent];
- [projectile runAction:[SKAction sequence:@[actionMove, actionMoveDone]]];
- }
上面的代碼中作了不少事情,咱們來詳細看看。
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文件頂部添加以下兩個常量:
- static const uint32_t projectileCategory = 0x1 << 0;
- static const uint32_t monsterCategory = 0x1 << 1;
上面設置了兩個類別,記住須要用位(bit)的方式表達——一個用於炮彈,另外一個則是怪物。
注意:看到上面的語法你可能感到奇怪。在Sprite Kit中category是一個32位整數,當作一個位掩碼(bitmask)。這種表達方法比較奇特:在一個32位整數中的每一位表示一種類別(所以最 多也就只能有32類)。在這裏,第一位表示炮彈,下一位表示怪獸。
接着,在initWithSize中,將下面的代碼添加到位置:添加player到場景涉及代碼的後面。
- self.physicsWorld.gravity = CGVectorMake(0,0);
- self.physicsWorld.contactDelegate = self;
上面的代碼將物理世界的重力感應設置爲0,並將場景設置位物理世界的代理(當有兩個物體碰撞時,會受到通知)。
在addMonster方法中,將以下代碼添加建立怪獸相關代碼後面:
- monster.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:monster.size]; // 1
- monster.physicsBody.dynamic = YES; // 2
- monster.physicsBody.categoryBitMask = monsterCategory; // 3
- monster.physicsBody.contactTestBitMask = projectileCategory; // 4
- monster.physicsBody.collisionBitMask = 0; // 5
來看看上面代碼意思:
爲怪獸建立一個對應的物體。此處,物體被定義爲一個與怪獸相同尺寸的矩形(這樣與怪獸形狀比較接近)。
將怪獸設置位dynamic。這意味着物理引擎將再也不控制這個怪獸的運動——咱們本身已經寫好相關運動的代碼了。
將categoryBitMask設置爲以前定義好的monsterCategory。
contactTestBitMask表示與什麼類型對象碰撞時,應該通知contact代理。在這裏選擇炮彈類型。
collisionBitMask表示物理引擎須要處理的碰撞事件。在此處咱們不但願炮彈和怪物被相互彈開——因此再次將其設置爲0。
接着在touchesEnded:withEvent:方法中設置炮彈位置的代碼後面添加以下代碼。
- projectile.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:projectile.size.width/2];
- projectile.physicsBody.dynamic = YES;
- projectile.physicsBody.categoryBitMask = projectileCategory;
- projectile.physicsBody.contactTestBitMask = monsterCategory;
- projectile.physicsBody.collisionBitMask = 0;
- projectile.physicsBody.usesPreciseCollisionDetection = YES;
在 上面的代碼中跟以前的相似,只不過有些不一樣,咱們來看看: 1. 爲了更好的效果,炮彈的形狀是圓形的。 2. usesPreciseCollisionDetection屬性設置爲YES。這對於快速移動的物體很是重要(例如炮彈),若是不這樣設置的話,有可能 快速移動的兩個物體會直接相互穿過去,而不會檢測到碰撞的發生。
接着,添加以下方法,當炮彈與怪物發生碰撞時,會被調用。注意這個方法是不會被自動調用,稍後會看到咱們如何調用它。
- - (void)projectile:(SKSpriteNode *)projectile didCollideWithMonster:(SKSpriteNode *)monster {
- NSLog(@"Hit");
- [projectile removeFromParent];
- [monster removeFromParent];
- }
當怪物和炮彈發生碰撞,上面的代碼會將他們從場景中移除。很簡單吧!
下面該實現contact delegate方法了。將以下方法添加到文件中:
- - (void)didBeginContact:(SKPhysicsContact *)contact
- {
- // 1
- SKPhysicsBody *firstBody, *secondBody;
- if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
- {
- firstBody = contact.bodyA;
- secondBody = contact.bodyB;
- }
- else
- {
- firstBody = contact.bodyB;
- secondBody = contact.bodyA;
- }
- // 2
- if ((firstBody.categoryBitMask & projectileCategory) != 0 &&
- (secondBody.categoryBitMask & monsterCategory) != 0)
- {
- [self projectile:(SKSpriteNode *) firstBody.node didCollideWithMonster:(SKSpriteNode *) secondBody.node];
- }
- }
還記得以前給物理世界設置的contactDelegate嗎?當兩個物體發生碰撞以後,就會調用上面的方法。
在上面的方法中,能夠分爲兩部分來理解:
該方法會傳遞給你發生碰撞的兩個物體,可是並不必定符合特定的順序(如炮彈在前,或者炮彈在後)。因此這裏的代碼是經過物體的category bit mask來對其進行排序,以便後續作出正確的判斷。注意,這裏的代碼來自蘋果提供的Adventure示例。
最後,檢測一下這兩個碰撞的物體是否就是炮彈和怪物,若是是的話就調用以前的方法。
最後一步,爲了編譯器沒有警告,確保private interface 中添加一下SKPhysicsContactDelegate:
- @interface MyScene () <SKPhysicsContactDelegate>
如今編譯並運行程序,能夠發現,當炮彈與怪物接觸時,他們就會消失!
收尾
如今,本文的遊戲快完成了。接下來咱們就來爲遊戲添加音效和音樂,以及一些簡單的遊戲邏輯吧。
蘋果提供的Sprite Kit裏面並無音頻引擎(Cocos2D中是有的),不過咱們能夠經過action來播放音效,而且可使用AVFoundation播放後臺音樂。
在工程中我已經準備好了一些音效和很酷的後臺音樂,在本文開頭已經將resources添加到工程中了,如今只須要播放它們便可!
首先在ViewController.m文件頂部添加以下import:
- @import AVFoundation;
上 面的語法是iOS 7中新的modules功能 —— 只須要使用新的關鍵字@import,就能夠框架的頭文件和庫文件添加到工程中,這功能很是方便。要了解更多相關內容,請看到iOS 7 by Tutorials中的第十章內容中的:What’s New with Objective-C and Foundation。
接着添加一個新的屬性和private interface:
- @interface ViewController ()
- @property (nonatomic) AVAudioPlayer * backgroundMusicPlayer;
- @end
接着將下面的代碼添加到viewWillLayoutSubviews方法中(在[super viewWillLayoutSubviews]後面):
- NSError *error;
- NSURL * backgroundMusicURL = [[NSBundle mainBundle] URLForResource:@"background-music-aac" withExtension:@"caf"];
- self.backgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:&error];
- self.backgroundMusicPlayer.numberOfLoops = -1;
- [self.backgroundMusicPlayer prepareToPlay];
- [self.backgroundMusicPlayer play];
上面的代碼會開始無限循環的播放後臺音樂。
下面咱們來看看如何處理音效。切換到MyScene.m文件中,並將下面這行代碼添加到touchesEnded:withEvent:方法的頂部:
- [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中的內容:
- #import <SpriteKit/SpriteKit.h>
- @interface GameOverScene : SKScene
- -(id)initWithSize:(CGSize)size won:(BOOL)won;
- @end
在上面的代碼中導入了Sprite Kit頭文件,並聲明瞭一個特定的初始化方法,該方法的第一個參數用來定位顯示的位置,第二個參數won用來判斷用戶是否贏了。
接着用下面的代碼替換GameOverLayer.m中的內容:
- #import "GameOverScene.h"
- #import "MyScene.h"
- @implementation GameOverScene
- -(id)initWithSize:(CGSize)size won:(BOOL)won {
- if (self = [super initWithSize:size]) {
- // 1
- self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
- // 2
- NSString * message;
- if (won) {
- message = @"You Won!";
- } else {
- message = @"You Lose :[";
- }
- // 3
- SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
- label.text = message;
- label.fontSize = 40;
- label.fontColor = [SKColor blackColor];
- label.position = CGPointMake(self.size.width/2, self.size.height/2);
- [self addChild:label];
- // 4
- [self runAction:
- [SKAction sequence:@[
- [SKAction waitForDuration:3.0],
- [SKAction runBlock:^{
- // 5
- SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
- SKScene * myScene = [[MyScene alloc] initWithSize:self.size];
- [self.view presentScene:myScene transition: reveal];
- }]
- ]]
- ];
- }
- return self;
- }
- @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中導入新的場景:
- #import "GameOverScene.h"
而後,在addMonster中,用下面的代碼替換最後一行在怪物上運行action的代碼:
- SKAction * loseAction = [SKAction runBlock:^{
- SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
- SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:NO];
- [self.view presentScene:gameOverScene transition: reveal];
- }];
- [monster runAction:[SKAction sequence:@[actionMove, loseAction, actionMoveDone]]];
上面建立了一個」lose action」,當怪物離開屏幕時,顯示game over場景。
在這裏爲何loseAction要在actionMoveDone以前運行呢? 緣由在於若是將一個精靈從場景中移除了,那麼它就不在處於場景的層次結構中了,也就不會有action了。因此須要過渡到lose場景以後,才能將精靈移 除。不過,實際上actionMoveDone永遠都不會被調用——由於此時已通過渡到新的場景中了,留在這裏就是爲了達到教學的目的。
如今,須要處理一下贏了的狀況。在private interface中添加一個新的屬性:
- @property (nonatomic) int monstersDestroyed;
而後將以下代碼添加到projectile:didCollideWithMonster:的底部:
- self.monstersDestroyed++;
- if (self.monstersDestroyed > 30) {
- SKTransition *reveal = [SKTransition flipHorizontalWithDuration:0.5];
- SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:YES];
- [self.view presentScene:gameOverScene transition: reveal];
- }
編譯並運行程序,嘗試一下贏了和輸了會看到的畫面!
何去何從?
至此Sprite Kit教程:初學者結束!這裏能夠下到
完整的代碼
。
但願本文能幫助你學習Sprite Kit,並寫出你本身的遊戲!
若是你但願學習更多相關Sprite Kit內容,能夠看看這本書:
iOS Games by Tutorials。本書會告訴你須要知道的內容——從物理,到tile map,以及特定的系統,甚至是製做本身的關卡編輯器。