用Swift做個(gè)游戲Lecture06 —— 碰撞的檢測(cè)

系列:用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)滓窍。
  • FallingPlayer因?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
  1. 標(biāo)識(shí)符,記錄Player是否掉落至地面惕橙。
  2. 標(biāo)識(shí)符瞧甩,記錄Player是否碰撞了仙人掌。
  3. 游戲狀態(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包含了接觸的所有信息幻赚,其中bodyAbodyB代表兩個(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è)置bottomObstacletopObstacle精靈的名字,方便之后找到它們并進(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í)間間隔圖建芙,匆忙的你找找原因,試試解決吧懂扼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末禁荸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子阀湿,更是在濱河造成了極大的恐慌赶熟,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陷嘴,死亡現(xiàn)場(chǎng)離奇詭異映砖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)灾挨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門邑退,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)竹宋,“玉大人,你說(shuō)我怎么就攤上這事地技◎谄撸” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵莫矗,是天一觀的道長(zhǎng)飒硅。 經(jīng)常有香客問我,道長(zhǎng)趣苏,這世上最難降的妖魔是什么狡相? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任梯轻,我火速辦了婚禮食磕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喳挑。我一直安慰自己彬伦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布伊诵。 她就那樣靜靜地躺著单绑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪曹宴。 梳的紋絲不亂的頭發(fā)上搂橙,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音笛坦,去河邊找鬼区转。 笑死,一個(gè)胖子當(dāng)著我的面吹牛版扩,可吹牛的內(nèi)容都是我干的废离。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼礁芦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜻韭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起柿扣,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肖方,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后未状,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俯画,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年娩践,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了活翩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烹骨。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖材泄,靈堂內(nèi)的尸體忽然破棺而出沮焕,到底是詐尸還是另有隱情,我是刑警寧澤拉宗,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布峦树,位于F島的核電站,受9級(jí)特大地震影響旦事,放射性物質(zhì)發(fā)生泄漏魁巩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一姐浮、第九天 我趴在偏房一處隱蔽的房頂上張望谷遂。 院中可真熱鬧,春花似錦卖鲤、人聲如沸肾扰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)集晚。三九已至,卻和暖如春区匣,著一層夾襖步出監(jiān)牢的瞬間偷拔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工亏钩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留莲绰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓铸屉,卻偏偏與公主長(zhǎng)得像钉蒲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子彻坛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容