> 原文:http://www.jianshu.com/p/82697ebf5cad
> 作者:[PMST](http://www.jianshu.com/users/596f2ba91ce9/latest_articles)
## 01.添加游戲音樂
音樂主要有:Player揮動翅膀上升的聲音、撞擊障礙物的聲音、墜落至地面的聲音、過關得分的聲音等等。請打開項目看到Resource中的Sounds文件夾,包含了上述所有聲音,格式為`.wav`。
SpritKit提供`playSoundFileNamed(soundFile: , waitForCompletion wait: )->SKAction`方法用于實現音樂的播放,注意播放音樂也是一個*Action*動作。請定位到*GameScene.swift*文件,找到`GameScene`類中的`var playableHeight:CGFloat = 0`,在其下方添加如下代碼:
~~~
// MARK: 音樂Action
let dingAction = SKAction.playSoundFileNamed("ding.wav", waitForCompletion: false)
let flapAction = SKAction.playSoundFileNamed("flapping.wav", waitForCompletion: false)
let whackAction = SKAction.playSoundFileNamed("whack.wav", waitForCompletion: false)
let fallingAction = SKAction.playSoundFileNamed("falling.wav", waitForCompletion: false)
let hitGroundAction = SKAction.playSoundFileNamed("hitGround.wav", waitForCompletion: false)
let popAction = SKAction.playSoundFileNamed("pop.wav", waitForCompletion: false)
let coinAction = SKAction.playSoundFileNamed("coin.wav", waitForCompletion: false)
~~~
之后在需要音樂播放的時候調用這些已經定義的動作即可。
## 02.添加Player
通過課程一的代碼練習,添加一個*Player*只需實例化一個`SKSpriteNode`實例,紋理為*Bird0*這張照片。由于這個精靈之后將在各個函數調用,因此設定了全局變量,請在`var playableHeight: CGFloat = 0`下添加如下代碼實例化一個名為"Player"的精靈。如下:
~~~
let player = SKSpriteNode(imageNamed: "Bird0")
~~~
注意到此時我們并未添加該精靈到場景中的`worldNode`節點中,因此我們需要實現一個名為`setupPlayer()`的方法,代碼如下
~~~
func setupPlayer(){
player.position = CGPointMake(size.width * 0.2, playableHeight * 0.4 + playableStart)
player.zPosition = Layer.Player.rawValue
worldNode.addChild(player)
}
~~~
函數中僅設置了*position*以及*zPosition*屬性,而錨點*anchorPoint*并未設置,采用默認值(0.5,0.5)。找到`didMoveToView(view:)`中的`setupForeground()`這行代碼,將上述方法添加至其下方。
點擊運行程序,*Player*出現在場景之中。
## 03.update方法
不知道你有沒有玩過翻書動畫,先準備一個厚厚的小本子,然后在每一頁上描畫,最后通過快速翻閱組成最簡短的動畫。如下:

L02-Animation
前文談及右下角的*30fps*客官可曾記得?*fps*是*Frame Per Second*的縮寫,即每秒的幀數,而一幀為一個畫面。因此*30fps*意味著在一秒鐘時間內,**App**要渲染30次左右,平均每隔0.033333秒就要重新繪制一次畫面。而渲染(繪制)完畢立刻跳入`update(currentTime:)`方法中,大約間隔33.33毫秒左右,執行方法內的代碼。不妨你在該函數中設個斷點感受一下。
注意到左下角的幀數并不是始終保持在*30fps*,而是不斷在上下浮動變化。相鄰兩幀畫面之間的時間并不固定,可能是0.033秒,也可能是0.030秒。不妨測試打印下兩幀之間的時間差值,請在`player`下添加兩個全局變量:`lastUpdateTime`以及`dt`:
~~~
var lastUpdateTime :NSTimeInterval = 0 //記錄上次更新時間
var dt:NSTimeInterval = 0 //兩次時間差值
~~~
接著在`Update(currenTime:)`方法中添加如下方法:
~~~
override func update(currentTime: CFTimeInterval) {
if lastUpdateTime > 0{
dt = currentTime - lastUpdateTime
}else{
dt = 0
}
lastUpdateTime = currentTime
print("時間差值為:\(dt*1000) 毫秒")
}
~~~
可以看到打印結果(注意紅色框框處):

當應用剛啟動時,幀數并不穩定,導致時間間隔略大,不過之后基本穩定在33毫秒左右。
## 04.Player的下落公式
這里可能要涉及一些高中的物理知識。地球上的重力加速度為9.8g。物體在半空中靜止到下落,每隔dt時間。
* 速度`V = V1 + a * dt`,即**當前速度=初速度 + 加速度 * 時間間隔**。
* dt時間內,下落距離`d =V * dt`,這里采用**平均速度 * 時間差**得到下落距離。
游戲中設定且只有Y軸方向上的重力加速度`kGravity = -1500`,這個值是可調節的,我覺得恰到好處;此外每次玩家點擊屏幕,對*Player*要有一個向上的拉力,不妨設為`kImpulse = 400`;最后聲明一個變量`playerVelocity`追蹤當前*Player*的速度。請添加上述三個全局變量的聲明,現在*GameScene*類中的全局變量有以下這些:
~~~
// MARK: - 常量
let kGravity:CGFloat = -1500.0 //重力
let kImpulse:CGFloat = 400 //上升力
let worldNode = SKNode()
var playableStart:CGFloat = 0
var playableHeight:CGFloat = 0
let player = SKSpriteNode(imageNamed: "Bird0")
var lastUpdateTime :NSTimeInterval = 0
var dt:NSTimeInterval = 0
var playerVelocity = CGPoint.zero //速度 注意變量類型為一個點
//...其他內容
~~~
請在*GameScene*類中添加一個方法,將先前公式用swift實現更新*player*的*position*。
~~~
func updatePlayer(){
// 只有Y軸上的重力加速度為-1500
let gravity = CGPoint(x: 0, y: kGravity)
let gravityStep = gravity * CGFloat(dt) //計算dt時間下速度的增量
playerVelocity += gravityStep //計算當前速度
// 位置計算
let velocityStep = playerVelocity * CGFloat(dt) //計算dt時間中下落或上升距離
player.position += velocityStep //計算player的位置
// 倘若Player的Y坐標位置在地面上了就不能再下落了 直接設置其位置的y值為地面的表層坐標
if player.position.y - player.size.height/2 < playableStart {
player.position = CGPoint(x: player.position.x, y: playableStart + player.size.height/2)
}
}
~~~
將該方法添加至`update(currentTime)`方法中的最下面。意味著每隔33.3毫秒左右就要更新一次*Player*的位置。
點擊運行,*Player*自由落地至地面,不錯吧!
## 05.讓Player動起來
游戲中我們點擊一次屏幕,*Player*會獲得一個向上的牽引力,揮動翅膀向上飛一段距離,倘若之后沒有持續的力,則開始自由落體。怎么實現呢?實現機制不難,只需每次玩家點擊屏幕,使得*Player*獲得向上的速度,具體為先前設定的400即可。
因此,添加一個方法到*GameScene*類中,用于每次用戶點擊屏幕時調用,作用是讓*Player*獲得向上的速度!
~~~
func flapPlayer(){
// 發出一次煽動翅膀的聲音
runAction(flapAction)
// 重新設定player的速度!!
playerVelocity = CGPointMake(0, kImpulse)
}
~~~
正如前面談到的,方法中主要做兩件事:1.發出一次揮動翅膀的聲音。2.重新設定player的速度。
而用戶每次點擊都會調用`touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)`方法。不用我多說了吧,把`flapPlayer()`方法添加進去吧。
運行工程,*player*墜落,點擊幾下,哇靠,飛起來了!