作者:PMST
VERSION:V1.0
NOTICE:目前第一版以敘述內(nèi)容為主,之后對其中一些細(xì)節(jié)知識點(diǎn)進(jìn)行講解。
更新時間:每周日
本教程參考自RayWenderlich的視頻教程How To Make a Game Like Flappy Bird Series (Swift)。本教程中,你將從無到有親自開發(fā)一個基于SpriteKit框架的Flappy bird小游戲??傮w難度不大,但要求你掌握Swift基礎(chǔ)語法與SpriteKit框架知識。此外,教程中所有素材均來自Raywenderlich,鼓勵學(xué)習(xí)交流,但請勿用于商業(yè)用途。
友情幫助: 為了方便大家快速上手項(xiàng)目,我在github中上傳了起始項(xiàng)目文件供大家下載,請點(diǎn)擊這里下載。
01.項(xiàng)目文件介紹
首先請打開項(xiàng)目,先介紹項(xiàng)目已有文件,你將看到如下目錄:

主要講解以下一些重要的文件:
-
Resource文件夾:資源文件放置處
Art:以atlas圖冊方式管理素材文件。
SKTUtiles:采用Extension對一些類進(jìn)行拓展,添加一些有用的方法或?qū)傩浴?/p>
Sounds:游戲聲音素材
GameScene.swift:Flappy游戲比較簡單,因此一個游戲場景足以,有關(guān)于場景內(nèi)容設(shè)置、交互等均在該場景中設(shè)置。
GameViewController.swift:視圖控制器,包含一個視圖view,當(dāng)然這個視圖比較特殊:為SKView,用于呈現(xiàn)場景Scene。
02.呈現(xiàn)視圖
選中GameViewController.swift文件,先前提及視圖控制器中的SKView,其職責(zé)在于呈現(xiàn)游戲場景Scene。不過現(xiàn)在空文件中神馬都沒有,我們將重寫viewWillLayoutSubviews()方法呈現(xiàn)場景。定位到GameViewController類,添加以下代碼:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// 1.對view進(jìn)行父類向子類的變形,結(jié)果是一個可選類型 因此需要解包
if let skView = self.view as? SKView{
// 倘若skView中沒有場景Scene,需要重新創(chuàng)建創(chuàng)建一個
if skView.scene == nil{
/*== 創(chuàng)建場景代碼 ==*/
// 2.獲得高寬比例
let aspectRatio = skView.bounds.size.height / skView.bounds.size.width
// 3.new一個場景實(shí)例 這里注意場景的width始終為320 至于高是通過width * aspectRatio獲得
let scene = GameScene(size:CGSizeMake(320, 320 * aspectRatio))
// 4.設(shè)置一些調(diào)試參數(shù)
skView.showsFPS = true // 顯示幀數(shù)
skView.showsNodeCount = true // 顯示當(dāng)前場景下節(jié)點(diǎn)個數(shù)
skView.showsPhysics = true // 顯示物理體
skView.ignoresSiblingOrder = true // 忽略節(jié)點(diǎn)添加順序
// 5.設(shè)置場景呈現(xiàn)模式
scene.scaleMode = .AspectFill
// 6.呈現(xiàn)場景
skView.presentScene(scene)
}
}
}
這里需要注意2、3處,固定了游戲場景的寬度Width = 320,高度則通過Width乘以高寬比相乘得到,對于iPhone4s iPhone5/5s這些寬為320的設(shè)備來說自然沒什么影響,但是對于iPhone6/6Pluse設(shè)備,相當(dāng)于將設(shè)備寬高同時縮小相同倍數(shù),直至寬為320時停止;再通過設(shè)置scaleMode為AspectFill(更多ScaleMode,請點(diǎn)擊這里了解)呈現(xiàn)視圖。
對于4來說,我們需要了解游戲運(yùn)行時每秒的幀數(shù)、當(dāng)前場景中節(jié)點(diǎn)個數(shù)、顯示節(jié)點(diǎn)的物理體等,因此通過設(shè)置這些參數(shù)能幫助我們更好的調(diào)試。
OK,點(diǎn)擊運(yùn)行項(xiàng)目,模擬器運(yùn)行結(jié)果一片漆黑,不過右下角顯示node=1 60.0fps,表明當(dāng)前場景中顯示了一個節(jié)點(diǎn),幀數(shù)為60左右。
Question:什么都還沒添加,視圖中怎么會有一個節(jié)點(diǎn)Node了呢?
Answer:場景Scene類為SKScene,繼承自SKNode,因此當(dāng)skView呈現(xiàn)場景時,自然就將一個節(jié)點(diǎn)置于其中了。
03場景內(nèi)容的填充
定位到GameScene.swift文件,可以看到文件中已經(jīng)聲明了一個GameScene類,當(dāng)然類中我們還未實(shí)現(xiàn)任何東西,因此這是運(yùn)行項(xiàng)目呈現(xiàn)出來的場景是漆黑一片。是時候一步步配置游戲場景了!
首先,定位到GameScene類中,在類中頂部添加如下三個變量,如下:
class GameScene:SKScene:{
let worldNode = SKNode()
var playableStart:CGFloat = 0
var playableHeight:CGFloat = 0
//...文件其他內(nèi)容
}
如上實(shí)例化了一個節(jié)點(diǎn)命名為worldNode,原因在于之后游戲中所有的節(jié)點(diǎn)都將添加至這個節(jié)點(diǎn)中,方便管理。此外游戲中場景分為Background和Ground兩部分,前者是背景,鳥可以在該區(qū)域中上下飛行;后者地面,小鳥僅限于跌落至上面。具體劃分請看下圖:

其中,背景和地面均作為節(jié)點(diǎn)添加至worldNode節(jié)點(diǎn)中。請在didMoveToView(view:)方法中添加如下代碼:
override func didMoveToView(view: SKView) {
addChild(worldNode)
setupBackground()
setupForeground()
}
首先添加worldNode節(jié)點(diǎn)到場景中,接著setupBackground()和setupForeground()兩個方法分別設(shè)置背景和地面兩個節(jié)點(diǎn),當(dāng)然此時方法還未實(shí)現(xiàn)。
通常游戲包含多個節(jié)點(diǎn),為了細(xì)化節(jié)點(diǎn)的圖層關(guān)系,節(jié)點(diǎn)Node中設(shè)定了一個zPosition屬性用于標(biāo)識節(jié)點(diǎn)相距你的程度,越小越里面,越大越外面。顯然游戲中,背景至于最底部,其次是地面,最后才是Player那只鳥。為此我們將使用枚舉來說明層級關(guān)系,在GameScene類上方添加Layer的聲明:
enum Layer: CGFloat {
case Background
case Foreground
case Player
}
class GameScene:SKScene{}
干完這些,是時候補(bǔ)充剩下的兩個方法的實(shí)現(xiàn)了。首先添加setupBackground()方法至GameScene類中:
func setupBackground(){
// 1
let background = SKSpriteNode(imageNamed: "Background")
background.anchorPoint = CGPointMake(0.5, 1)
background.position = CGPointMake(size.width/2.0, size.height)
background.zPosition = Layer.Background.rawValue
worldNode.addChild(background)
// 2
playableStart = size.height - background.size.height
playableHeight = background.size.height
}
依葫蘆畫瓢實(shí)現(xiàn)setupForeground()方法:
func setupForeground() {
let foreground = SKSpriteNode(imageNamed: "Ground")
foreground.anchorPoint = CGPoint(x: 0, y: 1)
foreground.position = CGPoint(x: 0, y: playableStart)
foreground.zPosition = Layer.Foreground.rawValue
worldNode.addChild(foreground)
}
點(diǎn)擊運(yùn)行,你將看到如下畫面,Good Job! 你已經(jīng)完成了第一步,之后我們將添加Player以及障礙物到場景中。

倘若你覺得SpritKit的基礎(chǔ)知識不夠扎實(shí),不妨看看這個。