基于SpriteKit+Swift開(kāi)發(fā)打竹塊游戲
譯文一、 簡(jiǎn)介
SpriteKit是蘋(píng)果公司推出的跑在iOS和OS X上的游戲開(kāi)發(fā)框架。這個(gè)工具不僅提供了強(qiáng)有力的圖形功能,而且還包括一個(gè)易于使用的物理引擎。最重要的是,你可以使用你熟悉的工具 ——Swift,Xcode和Interface Builder完成所有的工作!你可以用SpriteKit做很多的事情;但是,想了解它是如何工作的最佳方法就是使用它開(kāi)發(fā)一個(gè)簡(jiǎn)單的游戲。
在本系列教程(兩部分)中,你將要學(xué)習(xí)如何使用SpriteKit來(lái)開(kāi)發(fā)一款Breakout游戲。其中,加入了完整的碰撞檢測(cè)技術(shù),使用物理效果控制小球彈跳,通過(guò)觸摸來(lái)拖動(dòng)擋板,游戲狀態(tài)控制等。
二、 開(kāi)始
作為初始準(zhǔn)備,建議你一定要先下載本教程對(duì)應(yīng)的初始項(xiàng)目。此項(xiàng)目是使用標(biāo)準(zhǔn)的Xcode游戲模板創(chuàng)建的。所有的資源和狀態(tài)類(lèi)都已經(jīng)被導(dǎo)入到項(xiàng)目中,這樣可以節(jié)省您的一點(diǎn)時(shí)間。隨著進(jìn)一步閱讀,你會(huì)了解更多的游戲狀態(tài)。
你不妨先花點(diǎn)時(shí)間來(lái)熟悉一下整個(gè)項(xiàng)目。為此運(yùn)行命令“Build”和”Run“,你會(huì)看到一個(gè)橫向模式的灰色屏幕。請(qǐng)參考下圖。
三、 Sprite Kit Visual Editor簡(jiǎn)介
讓我們從配置場(chǎng)景文件開(kāi)始工作吧。為此,請(qǐng)打開(kāi)GameScene.sks文件。這是一個(gè)已鏈接到你的Sprite Kit場(chǎng)景的可視化編輯器,你可以從游戲的 GameScene.swift文件中訪問(wèn)其中已有的每一個(gè)元素。
首先,你將調(diào)整場(chǎng)景的大小,使其適合您在本教程中介紹的目標(biāo)屏幕:一個(gè)iPhone 6屏幕。為此,你可以在位于Xcode窗口右上角的Attributes inspector的Scene部分完成這一操作。如果你看不到Attributes inspector,您可以通過(guò) View\Utilities\Show Attributes inspector來(lái)訪問(wèn)它。請(qǐng)將場(chǎng)景的大小設(shè)置為 568 × 320,如下面的屏幕快照所示。
[注意]如果您的資源庫(kù)中包含了為適應(yīng)多屏幕縮放因子(即 1 x,2x,3 x等)準(zhǔn)備的圖片等資源的話,Sprite Kit將自動(dòng)在當(dāng)前運(yùn)行的設(shè)備使用正確的資源文件。
現(xiàn)在,我們來(lái)考慮游戲的背景。如下面的屏幕快照所示,從Xcode窗口的右下角的對(duì)象庫(kù)面板上拖出一個(gè)Color Sprite。如果你看不到對(duì)象庫(kù)面板,那么可以從菜單欄選擇View\Utilities\Show Object Library。
請(qǐng)使用屬性檢查器將位置更改為284,160,把它的紋理設(shè)置為bg。
現(xiàn)在,你可以生成并運(yùn)行一下游戲工程,欣賞一下你的游戲的背景顯示情況。
一旦你建立了擁有背景的橫屏場(chǎng)景,那么接下來(lái)就可以往其中添加小球了!仍然在GameScene.sks文件中,將一個(gè)新的Color Sprite拖動(dòng)到場(chǎng)景中。然后,把它的名稱(chēng)改為ball,紋理設(shè)置為ball,位置修改為284,220。然后,再把它的Z位置屬性值也設(shè)置為2,以確保小球出現(xiàn)在背景上。
現(xiàn)在,生成和運(yùn)行你的項(xiàng)目,你會(huì)看到小球已經(jīng)出現(xiàn)在屏幕上,如圖所示。
然而,到目前為止,我們的游戲還不能動(dòng)起來(lái),這是因?yàn)槲覀冞€沒(méi)有添加物理部分呢。
四、 物理引擎
在Sprite Kit中,你需要工作在兩種環(huán)境中:你在屏幕上看到的圖形世界和物理世界,這決定了對(duì)象的移動(dòng)和交互的方式。
在使用Sprite Kit物理引擎時(shí),你需要做的第一件事是根據(jù)你的游戲的需要改變世界。世界對(duì)象是在使用Sprite Kit時(shí)管理所有的對(duì)象和物理模擬的主要對(duì)象。它還設(shè)置了物理機(jī)構(gòu)加入到世界對(duì)象中需要的重力屬性。默認(rèn)的重力值是-9.81,因此類(lèi)似于地球的實(shí)際重力值。所以,只要你在世界中添加一個(gè)物體,它就會(huì)往下落。
一旦配置了世界對(duì)象,你就可以往此世界對(duì)象中添加根據(jù)物理原則與它進(jìn)行交互的東西。為此,最通常的方法是創(chuàng)建一個(gè)精靈 (圖形) 并設(shè)置它的物理body。物體的body屬性與世界決定了物體的移動(dòng)方式。
Body可以是受物理力量影響的動(dòng)態(tài)對(duì)象(如球,星星,小鳥(niǎo)......),或者是不受物理力量影響的靜態(tài)對(duì)象(平臺(tái)、墻......)。當(dāng)創(chuàng)建一個(gè)Body時(shí),你可以設(shè)置各種屬性,如形狀、密度、摩擦力,等等。這些屬性將嚴(yán)重影響B(tài)ody在世界范圍內(nèi)的行為。
當(dāng)定義一個(gè)Body時(shí),你可能會(huì)擔(dān)心其大小和密度的單位。在內(nèi)部,Sprite Kit使用公制(SI單位)。但是,在你的游戲中你通常不需要擔(dān)心實(shí)際的力量和質(zhì)量,只要你使用一致的值就行。
一旦你在世界中添加了所有的Body,Sprite Kit就會(huì)接管過(guò)控制權(quán),并進(jìn)行仿真。
為了設(shè)置第一個(gè)物理Body,你需要選擇剛剛添加的小球節(jié)點(diǎn)并從屬性檢查器的Physics Definition一節(jié)中選擇Body Type下的Bounding Circle并設(shè)置以下屬性值︰
- 取消勾選“Allows Rotation”
- 設(shè)置Friction為0
- 設(shè)置Restitution為1
- 將linear Damping(線性阻尼)設(shè)置為0
- 設(shè)置角阻尼(Angular Damping)為0
給小球添加物理特性
在這里,你創(chuàng)建了一個(gè)基于體積的物理Body,其形式為圓圈,具有與小球精靈完全相同的尺寸。這個(gè)物理Body受外力或沖動(dòng)的影響,并能夠與其他物體發(fā)生碰撞。
下面具體介紹一下它的屬性。
- Allows Rotation:指定是否允許旋轉(zhuǎn)。在本例中,你不希望小球旋轉(zhuǎn)。
- Friction:這個(gè)屬性也很簡(jiǎn)單,在我們的例子中要除去所有的摩擦。
- Restitution:是指對(duì)象的彈力。其值設(shè)置為1意味著,當(dāng)小球與物體碰撞時(shí)將保持原來(lái)完整的彈性。簡(jiǎn)言之,這意味著:小球會(huì)以與最初同等的作用力彈回來(lái)。
- Linear Damping(線性阻尼):通過(guò)減少物體的線性速度來(lái)模擬流體或空氣摩擦。在本例游戲中,小球移動(dòng)時(shí)不應(yīng)該減速。所以,在上面你需要設(shè)置阻尼為0。
- Angular Damping(角阻尼):除了角速度外,它與線性阻尼是相同的。當(dāng)你不允許球旋轉(zhuǎn)時(shí)將此值設(shè)置為可選的。
[注意]通常情況下,最好是讓物理Body與玩家看到的極其相似。對(duì)于小球來(lái)說(shuō),我們已經(jīng)做到完美的匹配。然而,當(dāng)你需要使用更復(fù)雜的形狀時(shí),要格外小心,因?yàn)楹軓?fù)雜的Body意味著高性能的系統(tǒng)資源消耗。自從IOS 8和Xcode 6以來(lái),Sprite Kit支持alpha蒙版Body類(lèi)型(alpha masks body types),這將自動(dòng)地把精靈的形狀用作其物理Body的形狀,但仍然要小心使用,因?yàn)檫@也可以降低系統(tǒng)性能。
現(xiàn)在,我們?cè)僖槐樯刹⑦\(yùn)行工程。如果你反應(yīng)足夠迅速,你應(yīng)該看到小球從場(chǎng)景中落下,最后消失在屏幕的底部,如圖所示。
這種現(xiàn)象的出現(xiàn)存在兩個(gè)原因︰首先,場(chǎng)景的默認(rèn)重力模擬了地球重力——沿x軸方向值為0而沿y軸方向是-9.8。第二,你的場(chǎng)景的物理世界是沒(méi)有界限的,尚無(wú)法用作封閉小球的籠子?,F(xiàn)在,讓我們著手解決這個(gè)問(wèn)題!
五、 把小球關(guān)起來(lái)
把小球關(guān)起來(lái)的效果
現(xiàn)在,請(qǐng)打開(kāi)文件GameScene.swift并把以下代碼行添加到didMoveToView(_:)方法的最后,用來(lái)創(chuàng)建一種圍繞屏幕的無(wú)形的障礙︰
- // 1
- let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
- // 2
- borderBody.friction = 0
- // 3
- self.physicsBody = borderBody
讓我們分析一下這行代碼︰
(1)創(chuàng)建一個(gè)基于邊緣(edge-based)的Body。與你添加到小球上的基于體積(volume-based)的Body相比,基于邊緣的Body并沒(méi)有質(zhì)量或體積,并且不受外力或沖量的影響。
(2)我們把摩擦力設(shè)置為0,這樣,小球與邊界障礙發(fā)生碰撞時(shí)其運(yùn)動(dòng)就不會(huì)減慢。相反,你想產(chǎn)生一種完美的效果,此時(shí)小球沿著它撞擊的屏障以相同的角度離開(kāi)。
(3)你可以設(shè)置為每個(gè)節(jié)點(diǎn)設(shè)置一個(gè)物理Body。然后,將它添加到場(chǎng)景中。注︰SKPhysicsBody的坐標(biāo)是相對(duì)于節(jié)點(diǎn)位置的。
再次運(yùn)行你的項(xiàng)目,你現(xiàn)在應(yīng)該看到像以前一樣的小球下落情景,但是現(xiàn)在當(dāng)它降落到籠子的底部邊緣時(shí)它會(huì)回彈回來(lái)。因?yàn)槟銖呐c籠子和環(huán)境的接觸(Contact)中去除了摩擦力,并且設(shè)置小球的變形為完全彈性形變,因此,小球會(huì)永遠(yuǎn)地那樣反彈運(yùn)動(dòng)下去。
為了讓小球運(yùn)動(dòng)效果更為圓滿(mǎn),讓我們刪除重力并施加單脈沖,這樣它就會(huì)沿著屏幕永遠(yuǎn)彈起彈落并運(yùn)動(dòng)下去。
六、 永久性彈性運(yùn)動(dòng)
現(xiàn)在,我們讓小球滾動(dòng)起來(lái)(實(shí)際上還是彈起)。在文件GameScene.swift中在緊鄰上面添加的代碼行的后面添加以下代碼:
- physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
- let ball = childNodeWithName(BallCategoryName) as! SKSpriteNode
- ball.physicsBody!.applyImpulse(CGVector(dx: 2.0, dy: -2.0))
這段新代碼首先從場(chǎng)景中刪除所有的重力,然后從場(chǎng)景的子節(jié)點(diǎn)上檢索到小球并應(yīng)用脈沖效果。脈沖能夠把一種立即生效的力施加到物理Body上,從而讓它朝著一個(gè)特定的方向(在本例中,沿對(duì)角線方向向右)運(yùn)動(dòng)。一旦小球設(shè)置為運(yùn)動(dòng),它會(huì)簡(jiǎn)單地在屏幕上反彈起來(lái),這是因?yàn)槟鷦偺砑恿似琳喜糠郑?o:p>
現(xiàn)在,是時(shí)候再去試試了!當(dāng)你編譯并運(yùn)行該項(xiàng)目時(shí),您應(yīng)該看到一個(gè)小球不斷跳躍在屏幕上——酷極了!
七、 添加擋板
如果沒(méi)有擋板,則不能稱(chēng)其為一款打竹塊游戲,是不是?
現(xiàn)在,打開(kāi)GameScene.sks文件來(lái)使用Visual Editor生成擋板(還有它的同伴物理Body),方式差不多就像你在場(chǎng)景的底部中間位置放置一個(gè)Color Sprite一樣,然后設(shè)置下列屬性值:
- Name = paddle
- Texture = paddle.png
- Position = 284,30
- Z Position = 3
- Body Type > Bounding rectangle
- 取消勾選Dynamic
- Friction: 0
- Restitution: 1
顯然,這里大部分的選項(xiàng)與前面創(chuàng)建小球時(shí)使用的選項(xiàng)是類(lèi)似的。然而,這一次你使用了Bounding rectangle來(lái)形成物理Body,因而它將更好地匹配矩形的擋板。
這里通過(guò)關(guān)閉Dynamic選項(xiàng),設(shè)置擋板是靜態(tài)的。這將確保擋板不會(huì)受外力和沖量的影響。你很快會(huì)看到為什么這很重要。
如果現(xiàn)在你生成和運(yùn)行一下項(xiàng)目,你會(huì)看到擋板出現(xiàn)在場(chǎng)景中,而小球在碰到擋板時(shí)會(huì)彈起(如果你能等待一段足夠長(zhǎng)時(shí)間的話)。請(qǐng)參考下圖。
到目前為止,一切比較順利!接下來(lái),我們要使玩家能夠移動(dòng)擋板。
八、 移動(dòng)擋板
移動(dòng)擋板需要檢測(cè)接觸相關(guān)信息。為此,我們?cè)贕ameScene類(lèi)中執(zhí)行下面的觸摸處理方法︰
- override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
- override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
- override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
但是,在此之前,你還需要添加一個(gè)屬性。轉(zhuǎn)到GameScene.swift文件,然后向類(lèi)中添加以下屬性︰
- var isFingerOnPaddle = false
這個(gè)屬性負(fù)責(zé)存儲(chǔ)是否玩家點(diǎn)按了擋板這一信息。你會(huì)需要它來(lái)執(zhí)行拖動(dòng)擋板相關(guān)操作。
現(xiàn)在,請(qǐng)?jiān)贕ameScene.swift文件的touchesBegan(_:withEvent:)方法中添加如下代碼︰
- override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
- let touch = touches.first
- let touchtouchLocation = touch!.locationInNode(self)
- if let body = physicsWorld.bodyAtPoint(touchLocation) {
- if body.node!.name == PaddleCategoryName {
- print("Began touch on paddle")
- isFingerOnPaddle = true
- }
- }
- }
上面的代碼獲取觸摸信息并使用它來(lái)查找場(chǎng)景中觸摸位置。下一步,使用 bodyAtPoint(_:)方法查找在該位置與節(jié)點(diǎn)(如果有的話)相關(guān)聯(lián)的物理Body。
最后,檢查觸摸位置是否存在一個(gè)節(jié)點(diǎn);如果存在的話,判斷該節(jié)點(diǎn)是否是擋板。這正是較早時(shí)創(chuàng)建對(duì)象名稱(chēng)發(fā)揮作用的時(shí)候——你可以通過(guò)檢查名稱(chēng)來(lái)檢查特定對(duì)象。如果觸摸位置處的對(duì)象是擋板,那么會(huì)有一條日志消息發(fā)送到控制臺(tái),同時(shí)isFingerOnPaddle被設(shè)置為true。
現(xiàn)在,你可以建立并重新運(yùn)行該項(xiàng)目。當(dāng)你點(diǎn)擊擋板時(shí),您應(yīng)該看到在控制臺(tái)中的日志消息。
接下來(lái),添加如下所示代碼:
- override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
- // 1
- if isFingerOnPaddle {
- // 2
- let touch = touches.first
- let touchtouchLocation = touch!.locationInNode(self)
- let previousLocation = touch!.previousLocationInNode(self)
- // 3
- let paddle = childNodeWithName(PaddleCategoryName) as! SKSpriteNode
- // 4
- var paddlepaddleX = paddle.position.x + (touchLocation.x - previousLocation.x)
- // 5
- paddleX = max(paddleX, paddle.size.width/2)
- paddleX = min(paddleX, size.width - paddle.size.width/2)
- // 6
- paddle.position = CGPoint(x: paddleX, y: paddle.position.y)
- }
- }
這是擋板運(yùn)動(dòng)主要的邏輯所在。
(1)檢查是否有玩家在觸摸擋板。
(2)如果是,那么你需要更新?lián)醢宓奈恢?,?dāng)然具體方式要取決于玩家移動(dòng)手指的方式。要做到這一點(diǎn),你需要得到當(dāng)前觸摸位置和上一次觸摸的位置。
(3)獲取擋板的SKSpriteNode。
(4)使用當(dāng)前位置加上新位置和上一次觸摸位置的差來(lái)計(jì)算擋板x坐標(biāo)值。
(5)在重新定位擋板前,限定一下其x坐標(biāo)位置,這樣擋板就不會(huì)走出屏幕的左右側(cè)。
(6)將擋板的位置設(shè)置為你剛剛計(jì)算的位置。
有關(guān)觸摸處理剩下的唯一事情是要做一些清理工作,這是在方法 touchesEnded(_:withEvent:)中實(shí)現(xiàn)的,如下所示︰
- override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
- isFingerOnPaddle = false
- }
在這里,你將isFingerOnPaddle屬性設(shè)置為false。這可以確保,當(dāng)玩家把他們的手指離開(kāi)屏幕然后再點(diǎn)按擋板時(shí),擋板不會(huì)跳到以前的觸摸位置。
完美!現(xiàn)在再次生成和運(yùn)行項(xiàng)目時(shí),你會(huì)發(fā)現(xiàn)小球彈跳在屏幕周?chē)夷憧梢允褂脫醢鍋?lái)影響它的運(yùn)動(dòng)了。
現(xiàn)在,已經(jīng)感覺(jué)不錯(cuò)了吧!
九、 接觸
到目前為止,你已經(jīng)實(shí)現(xiàn)了基本的小球彈跳和擋板移動(dòng)來(lái)控制小球。雖然這已經(jīng)是很有趣的,但是,要想使其真正成為一款游戲,你還需要一種方法來(lái)控制玩家贏取與輸?shù)粢淮斡螒?。?dāng)小球觸及屏幕的底部而不是擋板時(shí)玩家應(yīng)該失敗。但你如何使用Sprite Kit來(lái)檢測(cè)這種情況呢?
Sprite Kit可以檢測(cè)到兩個(gè)物理物體之間的接觸。然而,為了使其正常工作,您需要按照幾個(gè)步驟來(lái)設(shè)置某種方式。在此僅給出簡(jiǎn)短的描述。稍后會(huì)解釋每個(gè)步驟的更多細(xì)節(jié)。描述如下:
- 設(shè)置物理Body位掩碼:在你的游戲中,你可能有幾種不同類(lèi)型的物理Body——例如,你可能擁有玩家、敵人、子彈、獎(jiǎng)勵(lì)項(xiàng)等。為了唯一地標(biāo)識(shí)這些不同類(lèi)型的物理Body,你需要使用幾位掩碼來(lái)配置每個(gè)物理Body。這些掩碼包括:
- categoryBitMask:這位掩碼標(biāo)識(shí)一個(gè)Body隸屬的類(lèi)別。你可以使用類(lèi)別來(lái)定義一個(gè)Body與其他Body的互動(dòng)。CategoryBitMask是一個(gè)32位整數(shù),其中每一位代表一個(gè)類(lèi)別。所以,你的游戲中可以使用多達(dá)32個(gè)自定義類(lèi)別。這應(yīng)該足夠應(yīng)對(duì)大多數(shù)游戲中為每個(gè)對(duì)象類(lèi)型設(shè)置一個(gè)單獨(dú)類(lèi)別的要求了。對(duì)于更復(fù)雜的游戲,請(qǐng)記住,每個(gè)Body可以隸屬多個(gè)類(lèi)別這一技巧。所以,通過(guò)精心設(shè)計(jì)類(lèi)別,你甚至可以克服32個(gè)類(lèi)別的局限性。
- contactTestBitMask:在這個(gè)掩碼中設(shè)置位能夠?qū)崿F(xiàn)當(dāng)一個(gè)Body接觸到分配給該特定類(lèi)別的另一個(gè)Body時(shí)Sprite Kit通知代理。默認(rèn)情況下,所有的位都被清除掉——任何對(duì)象之間的接觸都不會(huì)通知你。為了獲得最佳性能,你應(yīng)該只設(shè)置你真正感興趣的用于相互作用的接觸掩碼。
- collisionBitMask:借助這個(gè)掩碼,您可以定義哪些Body可以與當(dāng)前物理Body碰撞。例如,你可以使用此技術(shù)來(lái)避免當(dāng)一個(gè)非常沉重的Body遇到一個(gè)比它輕得多的物體時(shí)的碰撞計(jì)算,因?yàn)檫@只會(huì)給沉重的Body的速度帶來(lái)微不足道的變化影響。但是,你也可以使用它讓兩個(gè)Body穿透對(duì)方。
- 設(shè)置并實(shí)現(xiàn)接觸委托(delegate,也有的翻譯為“代理”):接觸委托實(shí)際上是SKPhysicsWorld的一個(gè)屬性。當(dāng)兩個(gè)使用了contactTestBitMasks的Body開(kāi)始和結(jié)束碰撞會(huì)通知這個(gè)委托。
十、 3,2,1接觸算法
首先,我們來(lái)創(chuàng)建描述不同類(lèi)別的常數(shù)。為此,只需在GameScene.swift文件中添加下列常數(shù)定義:
- let BallCategory : UInt32 = 0x1 << 0
- let BottomCategory : UInt32 = 0x1 << 1
- let BlockCategory : UInt32 = 0x1 << 2
- let PaddleCategory : UInt32 = 0x1 << 3
- let BorderCategory : UInt32 = 0x1 << 4
上面定義了五個(gè)類(lèi)別。這里使用的辦法是:將最后一位設(shè)置為1,所有其他位設(shè)置為零。然后使用<<運(yùn)算符向左移動(dòng)這個(gè)位。因此,每個(gè)類(lèi)別常數(shù)只有一位設(shè)置為 1 而且在二進(jìn)制數(shù)中的這個(gè)1的位置對(duì)于上面四個(gè)類(lèi)別來(lái)說(shuō)都是唯一的。
現(xiàn)在,你只需要上述類(lèi)別來(lái)描述屏幕和小球;但是,你還應(yīng)當(dāng)使用其他一些辦法來(lái)解釋游戲運(yùn)行邏輯。
一旦建立了上面這些常數(shù),現(xiàn)在就可以創(chuàng)建橫跨屏幕底部的物理Body了。
[建議]各位讀者根據(jù)本文前面介紹的原則試著使用自己的方法來(lái)解決創(chuàng)建圍繞屏幕邊緣障礙有關(guān)的問(wèn)題。
現(xiàn)在,我們來(lái)討論創(chuàng)建接觸有關(guān)編程的核心問(wèn)題。首先,通過(guò)將下面的代碼添加到 didMoveToView(_:)方法中為游戲?qū)ο笤O(shè)置categoryBitMasks掩碼:
- let paddle = childNodeWithName(PaddleCategoryName) as! SKSpriteNode
- bottom.physicsBody!.categoryBitMask = BottomCategory
- ball.physicsBody!.categoryBitMask = BallCategory
- paddle.physicsBody!.categoryBitMask = PaddleCategory
- borderBody.categoryBitMask = BorderCategory
此代碼簡(jiǎn)單地把較早前創(chuàng)建的常數(shù)賦值給相應(yīng)的物理Body的categoryBitMask掩碼。
現(xiàn)在,通過(guò)添加下面一行代碼到didMoveToView(_:)方法中來(lái)設(shè)置contactTestBitMask掩碼:
ball.physicsBody!.contactTestBitMask = BottomCategory
現(xiàn)在,你只想要在小球接觸屏幕底部時(shí)被通知。
下一步,我們來(lái)為所有的物理接觸創(chuàng)建GameScene類(lèi)中的委托。
為此,僅需將下面這一行:
- class GameScene: SKScene {
修改成如下形式:
- class GameScene: SKScene, SKPhysicsContactDelegate {
這就可以了:GameScene的身份現(xiàn)在是SKPhysicsContactDelegate(因?yàn)樗袷豐KPhysicsContactDelegate協(xié)議),它將會(huì)接收到所有配置的物理Body的碰撞通知。
現(xiàn)在,您需要將GameScene設(shè)置為physicsWorld中的委派。所以,將下面一行代碼添加到方法didMoveToView(_:)中,正好位于語(yǔ)句physicsWorld.gravity = CGVector (dx: 0.0,dy: 0.0)的下面:
- physicsWorld.contactDelegate = self
最后,您需要執(zhí)行didBeginContact(_:)來(lái)處理碰撞問(wèn)題。為此,只需要將以下方法添加到GameScene.swift中:
- func didBeginContact(contact: SKPhysicsContact) {
- // 1
- var firstBody: SKPhysicsBody
- var secondBody: SKPhysicsBody
- // 2
- if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
- firstBody = contact.bodyA
- secondBody = contact.bodyB
- } else {
- firstBody = contact.bodyB
- secondBody = contact.bodyA
- }
- // 3
- if firstBody.categoryBitMask == BallCategory && secondBody.categoryBitMask == BottomCategory {
- print("Hit bottom. First contact has been made.")
- }
- }
讓我們分析一下上面的方法:
(1)創(chuàng)建兩個(gè)本地變量來(lái)存放參與碰撞的兩個(gè)物理Body。
(2)檢查這兩個(gè)碰撞的物理Body來(lái)看一下其中哪一個(gè)使用了較低的 categoryBitmask掩碼。然后,將它們存儲(chǔ)到本地變量;這樣,對(duì)應(yīng)于較低類(lèi)別的Body總是存儲(chǔ)在firstBody變量中。當(dāng)分析具體類(lèi)別之間的接觸時(shí)這將節(jié)省你不少的努力。
(3)得益于以前實(shí)現(xiàn)的排序操作,現(xiàn)在你只需要檢查是否firstBody屬于BallCategory類(lèi)別以及是否secondBody屬于BottomCategory類(lèi)別,以便弄明白小球已碰到了屏幕底部——正如你已經(jīng)知道的,如果firstBody屬于類(lèi)別BottomCategory ,則secondBody不可能屬于BallCategory類(lèi)別(因?yàn)?nbsp;BottomCategory 比 BallCategory 有更高的位掩碼)。在本示例中,我們僅僅輸出一條簡(jiǎn)單的日志消息。
現(xiàn)在,請(qǐng)?jiān)俅谓⒑瓦\(yùn)行你的游戲。如果一切正常,每當(dāng)小球錯(cuò)過(guò)擋板并點(diǎn)擊屏幕底部時(shí)你應(yīng)該看到在控制臺(tái)中的日志消息。就你下圖一樣:
還好吧!現(xiàn)在最艱難的部分已經(jīng)完成了。最后,剩下的就是添加竹塊和游戲邏輯,你會(huì)在本系列的下篇中了解到這些。