系列:用Swift作個(gè)游戲
作者:pmst(1345614869)
微博:PPPPPPMST
前文已經(jīng)為各個(gè)精靈新增了Physics Body,設(shè)置了三個(gè)掩碼:
- categoryBitMask表明了分屬類別。
- collisionBitMask告知能與哪些物體碰撞。
- contactTestBitMask則告知能與哪些物體接觸恤溶。
現(xiàn)在遺留的問題是如何檢測(cè)碰撞?難道是在update()方法進(jìn)行檢測(cè):遍歷所有的節(jié)點(diǎn)僻他,通過判斷節(jié)點(diǎn)的位置是否有交集嗎鸟赫?天吶迅脐!這也太麻煩了泊窘。確實(shí)参淹,如果通過自己實(shí)時(shí)檢測(cè)實(shí)在過于勞累,何不讓Sprite Kit來(lái)幫你代勞访得,每當(dāng)物體之間發(fā)生碰撞了龙亲,立馬通知你來(lái)處理事件。Bingo:芬帧鳄炉! 顯然這里要用協(xié)議+代理了,設(shè)置場(chǎng)景為代理搜骡,每當(dāng)Sprite Kit檢測(cè)到碰撞事件發(fā)生拂盯,就通知GameScene來(lái)處理,當(dāng)前哪里事情都是在協(xié)議(Protocol)中聲明了记靡。
01.游戲狀態(tài)
在正式開始今天的碰撞檢測(cè)課程之前谈竿,談?wù)勅绾蝿澐钟螒蚋鲿r(shí)的狀態(tài),僅以Flappy bird游戲?yàn)槔停?jiǎn)單劃分如下:
- MaiMenu空凸。開始一次游戲、查看排名以及游戲幫助寸痢。
- Tutorial呀洲。考慮到新手對(duì)于新游戲的上手啼止,在選擇進(jìn)行一次新游戲時(shí)道逗,展示玩法教程顯然是一個(gè)明確且友好的措施。
- Play献烦。正處于游戲的狀態(tài)滓窍。
- Falling。Player因?yàn)椴恍⌒呐龅秸系K物失敗下落時(shí)刻巩那。注意:接觸障礙物吏夯,失敗掉落才算!
- ShowingScore此蜈。顯示得分。
- GameeOver锦亦。告知游戲結(jié)束舶替。
為此請(qǐng)打開Lecture05的完成工程令境,打開GameScene.swift文件杠园,新增游戲狀態(tài)的枚舉聲明到enum Layer{}
下方:
enum GameState{
case MainMenu
case Tutorial
case Play
case Falling
case ShowingScore
case GameOver
}
當(dāng)然,我們還需要聲明一個(gè)變量用于存儲(chǔ)游戲場(chǎng)景的狀態(tài)舔庶,請(qǐng)找到GameScene類中let sombrero = SKSpriteNode(imageNamed: "Sombrero")
這條代碼抛蚁,在下方新增三個(gè)新變量:
//1
var hitGround = false
//2
var hitObstacle = false
//3
var gameState: GameState = .Play
- 標(biāo)識(shí)符,記錄Player是否掉落至地面惕橙。
- 標(biāo)識(shí)符瞧甩,記錄Player是否碰撞了仙人掌。
- 游戲狀態(tài)弥鹦,默認(rèn)是正在玩肚逸。
02.碰撞檢測(cè)
正如前面提及的協(xié)議+代理方式檢測(cè)物體之間的碰撞情況。首先請(qǐng)使得類GameScene遵循SKPhysicsContactDelegate
協(xié)議:
class GameScene: SKScene,SKPhysicsContactDelegate{...}
接著在didMoveToView()方法中設(shè)置代理為self
彬坏,找到physicsWorld.gravity = CGVector(dx: 0, dy: 0)
這行代碼朦促,添加該行代碼physicsWorld.contactDelegate = self
。
SKPhysicsContactDelegate
協(xié)議中定義了兩個(gè)可選方法,分別是:
optional public func didBeginContact(contact: SKPhysicsContact)
optional public func didEndContact(contact: SKPhysicsContact)
分別用于反饋兩個(gè)物體開始接觸栓始、結(jié)束接觸兩個(gè)時(shí)刻务冕。本文采用第一個(gè)方法用戶處理物體接觸事件。
func didBeginContact(contact: SKPhysicsContact) {
let other = contact.bodyA.categoryBitMask == PhysicsCategory.Player ? contact.bodyB : contact.bodyA
if other.categoryBitMask == PhysicsCategory.Ground {
hitGround = true
}
if other.categoryBitMask == PhysicsCategory.Obstacle {
hitObstacle = true
}
}
contact
包含了接觸的所有信息幻赚,其中bodyA和bodyB代表兩個(gè)碰撞的物體禀忆,顯然發(fā)生碰撞的結(jié)果只有兩種可能:1.Player和地面;2.Player和障礙物落恼÷嵬耍可惜我們無(wú)法確實(shí)bodyA就是Player,亦或是bodyB就是它。這是有不確定性的佳谦,我們需要通過categoryBitMask
來(lái)區(qū)分“陣營(yíng)”戴涝。一旦確定哪個(gè)是Player之后,我們就能取到與之發(fā)生接觸的other吠昭,通過判斷其類別來(lái)分別置為標(biāo)志位喊括。
一旦標(biāo)志位設(shè)置之后,我們需要在update()方法中進(jìn)行處理了矢棚!
03.根據(jù)游戲狀態(tài)來(lái)處理事件
請(qǐng)定位到update()
方法郑什,修改其中的內(nèi)容:
override func update(currentTime: CFTimeInterval) {
if lastUpdateTime > 0 {
dt = currentTime - lastUpdateTime
} else {
dt = 0
}
lastUpdateTime = currentTime
switch gameState {
case .MainMenu:
break
case .Tutorial:
break
case .Play:
updateForeground()
updatePlayer()
//1
checkHitObstacle() //Play狀態(tài)下檢測(cè)是否碰撞了障礙物
//2
checkHitGround() //Play狀態(tài)下檢測(cè)是否碰撞了地面
break
case .Falling:
updatePlayer()
//3
checkHitGround() //Falling狀態(tài)下檢測(cè)是否掉落至地面 此時(shí)已經(jīng)失敗了
break
case .ShowingScore:
break
case .GameOver:
break
}
}
其中1,2蒲肋,3中三個(gè)方法均是通過狀態(tài)標(biāo)志位來(lái)處理碰撞事件蘑拯,請(qǐng)?zhí)砑?code>checkHitObstacle()以及checkHitGround()
方法到updateForeground()
方法下方:
// 與障礙物發(fā)生碰撞
func checkHitObstacle() {
if hitObstacle {
hitObstacle = false
switchToFalling()
}
}
// 掉落至地面
func checkHitGround() {
if hitGround {
hitGround = false
playerVelocity = CGPoint.zero
player.zRotation = CGFloat(-90).degreesToRadians()
player.position = CGPoint(x: player.position.x, y: playableStart + player.size.width/2)
runAction(hitGroundAction)
switchToShowScore()
}
}
// MARK: - Game States
// 由Play狀態(tài)變?yōu)镕alling狀態(tài)
func switchToFalling() {
gameState = .Falling
runAction(SKAction.sequence([
whackAction,
SKAction.waitForDuration(0.1),
fallingAction
]))
player.removeAllActions()
stopSpawning()
}
// 顯示分?jǐn)?shù)狀態(tài)
func switchToShowScore() {
gameState = .ShowingScore
player.removeAllActions()
stopSpawning()
}
// 重新開始一次游戲
func switchToNewGame() {
runAction(popAction)
let newScene = GameScene(size: size)
let transition = SKTransition.fadeWithColor(SKColor.blackColor(), duration: 0.5)
view?.presentScene(newScene, transition: transition)
}
完成后自然你發(fā)現(xiàn)stopSpawning()
方法并未實(shí)現(xiàn)钝满,因?yàn)槲掖蛩愫煤弥v講這個(gè)。早前在didMoveToView()
方法中調(diào)用startSpawning()
源源不斷地產(chǎn)生障礙物申窘,但是一旦游戲結(jié)束弯蚜,我們所要做的事情有兩個(gè):1.停止繼續(xù)產(chǎn)生障礙物;2.已經(jīng)在場(chǎng)景中的障礙物停止移動(dòng)剃法。那么如何制定某個(gè)動(dòng)作Action停止呢碎捺?答案是先為這個(gè)動(dòng)作命名(簡(jiǎn)單來(lái)說(shuō)設(shè)置一個(gè)Key而已),然后用removeActionForKey()
來(lái)移除贷洲。
OK,找到startSpawning()
方法收厨,將runAction(overallSequence)
替換成runAction(overallSequence, withKey: "spawn")
;定位到spawnObstacle()
方法优构,分別設(shè)置bottomObstacle和topObstacle精靈的名字,方便之后找到它們并進(jìn)行操作:
...
bottomObstacle.name = "BottomObstacle"
worldNode.addChild(bottomObstacle)
...
topObstacle.name = "TopObstacle"
worldNode.addChild(topObstacle)
...
現(xiàn)在來(lái)實(shí)現(xiàn)stopSpawning()
方法,在startSpawning()
下方添加就好:
func stopSpawning() {
removeActionForKey("spawn")
worldNode.enumerateChildNodesWithName("TopObstacle", usingBlock: { node, stop in
node.removeAllActions()
})
worldNode.enumerateChildNodesWithName("BottomObstacle", usingBlock: { node, stop in
node.removeAllActions()
})
}
點(diǎn)擊運(yùn)行诵叁,我擦!還沒來(lái)得及點(diǎn)就掉地上了......好吧钦椭,只能在游戲進(jìn)入一瞬間先讓Player向上蹦跶下拧额。添加flapPlayer()
到didMoveToView()
方法的最下方。
點(diǎn)擊運(yùn)行彪腔,Nice!!Player順利穿過了障礙侥锦,不小心碰到了障礙物,再點(diǎn)擊漫仆,等等!怎么還能動(dòng)...好吧捎拯,看來(lái)touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
點(diǎn)擊事件中我們并未根據(jù)游戲狀態(tài)來(lái)處理,是時(shí)候修改了盲厌。
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
switch gameState {
case .MainMenu:
break
case .Tutorial:
break
case .Play:
flapPlayer()
break
case .Falling:
break
case .ShowingScore:
switchToNewGame()
break
case .GameOver:
break
}
}
點(diǎn)擊運(yùn)行署照,失敗重新開始游戲...等等貌似還有問題,怎么點(diǎn)擊想重新開始游戲會(huì)突然掉落到地面上...好吧吗浩,請(qǐng)看lecture02中的時(shí)間間隔圖建芙,匆忙的你找找原因,試試解決吧懂扼。