Sprite Kit教程:初學者

發表於2013-10-09

在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.為了給player(例如忍者)宣告一個私有變數,在這裡建立了一個私有的interface,之後可以把這個私有變數新增到場景中。
2.在這裡列印出了場景的size,至於什麼原因很快你就會看到了。
3.在Sprite Kit中設定一個場景的背景色非常簡單——只需要設定backgroundColor屬性,在這裡將其設定位白色。
4.在Sprite Kit場景中新增一個精靈同樣非常簡單,只需要使用spriteNodeWithImageNamed方法,並把一副圖片的名稱傳遞進去就可以建立一個精靈。接著設定一下精靈的位置,然後呼叫addChild方法將該精靈新增到場景中。在程式碼中將忍者的位置設定為(100, 100),該位置是從螢幕的左下角到右上角計算的。

編譯並執行,看看效果如何…

呀!螢幕是白色的,並沒有看到忍者。這是為什麼呢?你可能在想設計之初就是這樣的,實際上這裡有一個問題。

如果你觀察一下控制檯輸出的內容,會看到如下內容

可能你會認為場景的寬度是320,高度則是568——實際上剛好相反!

我們來看看具體發生了什麼:定位到ViewController.m的viewDidLoad方法:

上面的程式碼中利用view的邊界size建立了場景。不過請注意,當viewDidLoad被呼叫的時候,在這之前view已經被新增到view層次結構中了,因此它還沒有響應出佈局的改變。所以view的邊界可能還不正確,進而在viewDidLoad中並不是開啟場景的最佳時機。

提醒:要想了解更多相關內容,請看由Rob Mayoff帶來的最佳解釋。

解決方法就是將開啟場景程式碼的過程再靠後一點。用下面的程式碼替換viewDidLoad:

編譯並執行程式,可以看到,忍者已經顯示在螢幕中了!

如上圖所示,可以看到座標系已經正確了,如果想要把忍者的位置設定為其中間靠左,那麼在MyScene.m中用下面的程式碼來替換設定忍者位置相關的程式碼:

移動怪獸
接下來,我們希望在場景中新增一些怪獸,讓忍者進行攻擊。為了讓遊戲更有趣一點,希望怪獸能夠移動——否則沒有太大的挑戰!OK,我們就在螢幕的右邊,離屏的方式建立怪獸,並給怪獸設定一個動作:告訴它們往左邊移動。

將下面這個方法新增到MyScene.m中:

在上面,我儘量讓程式碼看起來容易理解。首先是通過一個簡單的計算,確定怪獸出現的位置,並將該位置設定給怪獸,然後將其新增到場景中。

接著是新增動作(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中新增如下屬性:

通過lastSpawnTimeInterval可以記錄著最近出現怪獸時的時間,而lastUpdateTimeInterval可以記錄著上次更新時的時間。

接著,我們寫一個方法,該方法在畫面每一幀更新的時候都會被呼叫。記住,該方法不會被自動呼叫——需要另外寫一個方法來呼叫它:

上面的程式碼中簡單的將上次更新(update呼叫)的時間追加到self.lastSpawnTimeInterval中。一旦該時間大於1秒,就在場景中新增一個怪獸,並將lastSpawnTimeInterval重置。

最後,新增如下方法來呼叫上面的方法:

Sprite Kit在顯示每幀時都會呼叫上面的update:方法。

上面的程式碼其實是來自蘋果提供的Adventure示例中。該方法會傳入當前的時間,在其中,會做一些計算,以確定出上一幀更新的時間。注意,在程式碼中做了一些合理性的檢查,以避免從上一幀更新到現在已經過去了大量時間,並且將間隔重置為1/60秒,避免出現奇怪的行為。

現在編譯並執行程式,可以看到許多怪獸從左邊移動到螢幕右邊並消失。

發射炮彈

現在我們開始給忍者新增一些動作,首先從發射炮彈開始!實際上有多種方法來實現炮彈的發射,不過,在這裡要實現的方法時當使用者tap螢幕時,從忍者的方位到tap的方位發射一顆炮彈。

由於本文是針對初級開發者,所以在這裡我使用moveTo:動作來實現,不過這需要做一點點的數學運算——因為moveTo:方法需要指定炮彈的目的地,但是又不能直接使用touch point(因為touch point僅僅代表需要發射的方向)。實際上我們需要讓炮彈穿過touch point,直到炮彈在螢幕中消失。

如下圖,演示了上面的相關內容:

如圖所示,我們可以通過origin point到touch point得到一個小的三角形。我們要做的就是根據這個小三角形的比例建立出一個大的三角形——而你知道你想要的一個端點是離開螢幕的地方。

為了做這個計算,如果有一些基本的向量方法可供呼叫(例如向量的加減法),那麼會非常有幫助,但很不幸的時Sprite Kit並沒有提供相關方法,所以,我們必須自己實現。

不過很幸運的時這非常容易實現。將下面的方法新增到檔案的頂部(implementation之前):

上面實現了一些標準的向量函式。如果你看得不是太明白,請看這裡關於向量方法的解釋。

接著,在檔案中新增一個新的方法:

上面的程式碼中做了很多事情,我們來詳細看看。

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。

編譯並允許程式,現在忍者可以發射炮彈了!

相關文章