SpriteKit框架詳細解析(七) —— 基于SpriteKit的類Cut the Rope游戲簡單示例(一)

版本記錄

版本號 時間
V1.0 2019.10.23 星期三

前言

SpriteKit框架使用優(yōu)化的動畫系統(tǒng)糊秆,物理模擬和事件處理支持創(chuàng)建基于2D精靈的游戲痘番。接下來這幾篇我們就詳細的解析一下這個框架汞舱。相關(guān)代碼已經(jīng)傳至GitHub - 刀客傳奇昂芜,感興趣的可以閱讀另外幾篇文章泌神。
1. SpriteKit框架詳細解析(一) —— 基本概覽(一)
2. SpriteKit框架詳細解析(二) —— 一個簡單的動畫實例(一)
3. SpriteKit框架詳細解析(三) —— 創(chuàng)建一個簡單的2D游戲(一)
4. SpriteKit框架詳細解析(四) —— 創(chuàng)建一個簡單的2D游戲(二)
5. SpriteKit框架詳細解析(五) —— 基于SpriteKit的游戲編程的三角函數(shù)(一)
6. SpriteKit框架詳細解析(六) —— 基于SpriteKit的游戲編程的三角函數(shù)(二)

開始

題外話:分開半個月你就定親了母市,你定你的親患久,我奮斗我的未來墙杯,互不打擾,這是我最后為你做的一件事畸冲!可能昨晚是最后一次想起你流淚了~~邑闲,愿我以后還會覺得人間值得苫耸!

首先看下主要內(nèi)容

主要內(nèi)容:本篇主要講述了褪子,學(xué)習(xí)如何在Swift中使用SpriteKit構(gòu)建像《Cut the Rope》這樣的游戲嫌褪,并提供動畫笼痛,聲音效果和物理效果缨伊!下面是翻譯地址倘核。

下面看下寫作環(huán)境

Swift 5, iOS 13, Xcode 11

Cut The Rope 是一款流行的物理驅(qū)動游戲活尊,玩家可以通過割斷懸吊糖果的繩索來喂食一個名為Om Nom的小怪物蛹锰。 在適當?shù)臅r間和地點切成薄片铜犬,Om Nom會得到美味的點心癣猾。

在充分考慮Om Nom的情況下纷宇,這款游戲的真正明星是其模擬物理效果:繩索擺動像捶,重力拉動和糖果搖搖欲墜拓春,就像您在現(xiàn)實生活中所期望的那樣硼莽。

您可以使用Apple的2D游戲框架SpriteKit的物理引擎來構(gòu)建類似的體驗。 在本Cut the Rope with SpriteKit教程中矾瑰,您將使用名為Snip The Vine的游戲來做到這一點殴穴。

Snip The Vine中采幌,您將向鱷魚喂食可愛的小菠蘿休傍。在Xcode中打開該項目磨取,以快速了解其結(jié)構(gòu)凫岖。

您會在幾個文件夾中找到項目文件哥放。 在本教程中甥雕,您將使用包含主要代碼文件的Classes社露。 隨意瀏覽其他文件夾呵哨,如下所示:

在整個教程中拒炎,您將使用Constants.swift中的值击你,因此在深入之前請花一些時間來熟悉該文件丁侄。


Adding the Crocodile to the Scene

請注意石景,這條鱷魚非吵蹦酰活潑往史,請始終保持手指安全距離椎例!

鱷魚由SKSpriteNode展示订歪。 您需要為您的游戲邏輯保留對鱷魚的引用撒犀。 您還需要為鱷魚精靈設(shè)置一個物理物體(physics body)或舞,以檢測并處理與其他物體的接觸映凳。

GameScene.swift中,將以下屬性添加到類的頂部:

private var crocodile: SKSpriteNode!
private var prize: SKSpriteNode!

這些屬性將存儲對鱷魚和獎品(菠蘿)的引用矫渔。

GameScene.swift中找到setUpCrocodile()并添加以下代碼:

crocodile = SKSpriteNode(imageNamed: ImageName.crocMouthClosed)
crocodile.position = CGPoint(x: size.width * 0.75, y: size.height * 0.312)
crocodile.zPosition = Layer.crocodile
crocodile.physicsBody = SKPhysicsBody(
  texture: SKTexture(imageNamed: ImageName.crocMask),
  size: crocodile.size)
crocodile.physicsBody?.categoryBitMask = PhysicsCategory.crocodile
crocodile.physicsBody?.collisionBitMask = 0
crocodile.physicsBody?.contactTestBitMask = PhysicsCategory.prize
crocodile.physicsBody?.isDynamic = false
    
addChild(crocodile)
    
animateCrocodile()

使用此代碼庙洼,您可以創(chuàng)建鱷魚節(jié)點并設(shè)置其positionzPosition

鱷魚有一個SKPhysicsBody石咬,這意味著它可以與世界上其他對象進行交互鬼悠。當您要檢測菠蘿何時落入嘴中時厦章,這將在以后有用汗侵。

您不希望鱷魚被撞倒或從屏幕底部掉下晰韵!為避免這種情況雪猪,請將isDynamic設(shè)置為false,以防止物理力影響它官觅。

categoryBitMask定義了物體所屬的物理類別-在這種情況下為PhysicsCategory.crocodile休涤。您將collaringBitMask設(shè)置為0,是因為您不希望鱷魚從其他物體反彈捷凄。您需要知道的是“獎勵”物體何時與鱷魚接觸纵势,因此您可以相應(yīng)地設(shè)置contactTestBitMask软舌。

您可能會注意到佛点,鱷魚的物體主體是使用SKTexture對象初始化的。您可以簡單地將.crocMouthOpen用作物體紋理阅虫,但是該圖像包括鱷魚的整個身體,而蒙版紋理僅包括鱷魚的頭和嘴购城。鱷魚的尾巴不能吃菠蘿!

現(xiàn)在侮攀,您將向鱷魚添加“等待”動畫。找到animateCrocodile()并添加以下代碼:

let duration = Double.random(in: 2...4)
let open = SKAction.setTexture(SKTexture(imageNamed: ImageName.crocMouthOpen))
let wait = SKAction.wait(forDuration: duration)
let close = SKAction.setTexture(SKTexture(imageNamed: ImageName.crocMouthClosed))
let sequence = SKAction.sequence([wait, open, wait, close])
    
crocodile.run(.repeatForever(sequence))

除了使小鱷魚非常焦慮外侨糟,此代碼還創(chuàng)建了一些動作來更改鱷魚節(jié)點的紋理,以便在閉合的嘴和張開的嘴之間交替颤殴。

SKAction.sequence(_ :)構(gòu)造函數(shù)從數(shù)組創(chuàng)建一系列動作涵但。 在這種情況下,紋理動作與2到4秒之間的隨機選擇的延遲時間順序結(jié)合澈侠。

sequence action封裝在repeatForever(_ :)中哨啃,因此它將在關(guān)卡持續(xù)時間內(nèi)重復(fù)執(zhí)行审姓。 然后邑跪,鱷魚節(jié)點運行它。

就這些轴踱! 建立并運行,然后看到這個兇猛的爬行動物咬死了他的下巴雳灵!

您有風(fēng)景,也有鱷魚-現(xiàn)在您需要可愛的小動物菠蘿躲撰。


Adding the Prize

打開GameScene.swift并找到setUpPrize()。 添加以下內(nèi)容:

prize = SKSpriteNode(imageNamed: ImageName.prize)
prize.position = CGPoint(x: size.width * 0.5, y: size.height * 0.7)
prize.zPosition = Layer.prize
prize.physicsBody = SKPhysicsBody(circleOfRadius: prize.size.height / 2)
prize.physicsBody?.categoryBitMask = PhysicsCategory.prize
prize.physicsBody?.collisionBitMask = 0
prize.physicsBody?.density = 0.5

addChild(prize)

類似于鱷魚谆棱,菠蘿節(jié)點也使用physics body。 最大的不同是皆警,菠蘿應(yīng)該掉下來并彈跳,而鱷魚只是坐在那里意推,不耐煩地等待。 因此腻窒,將isDynamic設(shè)置為其默認值true。 您還可以降低菠蘿的density柔逼,使其更自由地擺動。


Working With Physics

在開始墜落菠蘿之前维咸,最好先配置物理世界physics world腰湾。 在GameScene.swift中找到setUpPhysics()并添加以下三行:

physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0.0, dy: -9.8)
physicsWorld.speed = 1.0

這將設(shè)置物理世界physics worldcontactDelegategravityspeed附井。 重力指定了應(yīng)用于世界物理物體的重力加速度,而速度則調(diào)節(jié)了模擬執(zhí)行的速度。 您希望在此處將兩個屬性都設(shè)置為其默認值意蛀。

您會發(fā)現(xiàn)您不必遵循SKPhysicsContactDelegate秀姐,因為GameScene底部已經(jīng)有一個擴展程序來實現(xiàn)它若贮。 暫時不要管它省有; 您稍后再使用。

構(gòu)建并再次運行谴麦。 您應(yīng)該看到菠蘿駛過鱷魚蠢沿,掉入水中。

是時候添加藤蔓了匾效。


Adding Vines

SpriteKit的物理物體模擬了剛性物體……但是藤蔓彎曲了。為了解決這個問題弧轧,您需要將每個藤蔓實施為具有柔性接頭的分段陣列雪侥,類似于鏈。

每個藤蔓具有三個重要屬性:

  • anchorPointCGPoint精绎,指示藤蔓末端連接到樹的位置速缨。
  • length:一個Int,代表藤蔓中的節(jié)數(shù)代乃。
  • name:一個String旬牲,用于標識給定段屬于哪個藤。

在本教程中搁吓,游戲只有一個級別原茅。但是在真實游戲中,您希望能夠輕松地創(chuàng)建新的關(guān)卡布局而無需編寫大量代碼堕仔。做到這一點的一種好方法是獨立于游戲邏輯指定關(guān)卡數(shù)據(jù)擂橘,也許是通過將其存儲在帶有屬性列表或JSON的數(shù)據(jù)文件中來進行。

由于您將從文件中加載藤蔓數(shù)據(jù)摩骨,因此代表它的自然結(jié)構(gòu)是可編碼Codable值的數(shù)組通贞,可以使用PlistDecoder從屬性列表中輕松讀取該值。屬性列表中的每個字典將代表一個VineData實例恼五,該實例已在入門項目中定義昌罩。

GameScene.swift中,找到setUpVines()并添加以下代碼:

let decoder = PropertyListDecoder()
guard
  let dataFile = Bundle.main.url(
    forResource: GameConfiguration.vineDataFile,
    withExtension: nil),
  let data = try? Data(contentsOf: dataFile),
  let vines = try? decoder.decode([VineData].self, from: data)
  else {
    return
}

從屬性列表文件加載vine數(shù)據(jù)灾馒。 看一下Resources / Data中的VineData.plist茎用。 您應(yīng)該看到該文件包含一個字典數(shù)組,每個字典包含一個relAnchorPointlength

接下來,將以下代碼添加到方法中:

// 1 add vines
for (i, vineData) in vines.enumerated() {
  let anchorPoint = CGPoint(
    x: vineData.relAnchorPoint.x * size.width,
    y: vineData.relAnchorPoint.y * size.height)
  let vine = VineNode(
    length: vineData.length, 
    anchorPoint: anchorPoint, 
    name: "\(i)")

  // 2 add to scene
  vine.addToScene(self)

  // 3 connect the other end of the vine to the prize
  vine.attachToPrize(prize)
}

使用此代碼轨功,您:

  • 1) 對于每個藤蔓旭斥,初始化一個新的VineNodelength指定藤中的節(jié)段數(shù)夯辖。 relAnchorPoint確定相對于場景scene大小的藤蔓的錨點位置琉预。
  • 2) 最后董饰,使用addToScene(_ :)VineNode附加到場景`scene蒿褂。
  • 3) 然后,使用attachToPrize(_ :)將其附加到獎勵卒暂。

接下來啄栓,您將在VineNode中實現(xiàn)這些方法。


Defining the Vine Class

打開VineNode.swift也祠。 VineNode是從SKNode繼承的自定義類昙楚。 它本身沒有任何視覺外觀,而是充當代表藤蔓片段的SKSpriteNodes集合的容器诈嘿。

將以下屬性添加到類定義:

private let length: Int
private let anchorPoint: CGPoint
private var vineSegments: [SKNode] = []

由于您尚未初始化lengthanchorPoint屬性堪旧,因此會出現(xiàn)一些錯誤奖亚。 您已將它們聲明為非可選淳梦,但尚未分配值昔字。 通過使用以下替換init(length:anchorPoint:name :)的實現(xiàn)來解決此問題:

self.length = length
self.anchorPoint = anchorPoint

super.init()

self.name = name

非常簡單……但是由于某些原因作郭,仍然存在錯誤蜘醋。 您可能會注意到,還有第二個初始化方法斥季,init(coder :)。 您沒有在任何地方調(diào)用,那是為了什么酣倾?

因為SKNode實現(xiàn)了NSCoding舵揭,所以它繼承了required的初始化程序init(coder :)。 這意味著即使您沒有使用它躁锡,也必須在其中初始化非可選屬性午绳。

現(xiàn)在就做。 將init(coder :)的內(nèi)容替換為:

length = aDecoder.decodeInteger(forKey: "length")
anchorPoint = aDecoder.decodeCGPoint(forKey: "anchorPoint")

super.init(coder: aDecoder)

接下來映之,您需要實現(xiàn)addToScene(_ :)拦焚。 這是一種復(fù)雜的方法,因此您將分階段進行編寫杠输。 首先赎败,找到addToScene(_ :)并添加以下內(nèi)容:

// add vine to scene
zPosition = Layer.vine
scene.addChild(self)

將藤蔓添加到場景并設(shè)置其zPosition。 接下來蠢甲,將此代碼塊添加到相同的方法中:

// create vine holder
let vineHolder = SKSpriteNode(imageNamed: ImageName.vineHolder)
vineHolder.position = anchorPoint
vineHolder.zPosition = 1
    
addChild(vineHolder)
    
vineHolder.physicsBody = SKPhysicsBody(circleOfRadius: vineHolder.size.width / 2)
vineHolder.physicsBody?.isDynamic = false
vineHolder.physicsBody?.categoryBitMask = PhysicsCategory.vineHolder
vineHolder.physicsBody?.collisionBitMask = 0

這將創(chuàng)建藤蔓固定器僵刮,就像藤蔓懸掛的釘子一樣。 與鱷魚一樣鹦牛,該物體不是動態(tài)的搞糕,不會與其他物體碰撞。

藤蔓支架是圓形的曼追,因此請使用SKPhysicsBody(circleOfRadius :)初始化程序窍仰。 藤蔓支架的位置與您在創(chuàng)建VineNode時指定的anchorPoint匹配。

接下來拉鹃,您將創(chuàng)建藤蔓辈赋。 將以下代碼再次添加到同一方法的底部:

// add each of the vine parts
for i in 0..<length {
  let vineSegment = SKSpriteNode(imageNamed: ImageName.vineTexture)
  let offset = vineSegment.size.height * CGFloat(i + 1)
  vineSegment.position = CGPoint(x: anchorPoint.x, y: anchorPoint.y - offset)
  vineSegment.name = name
  
  vineSegments.append(vineSegment)
  addChild(vineSegment)
  
  vineSegment.physicsBody = SKPhysicsBody(rectangleOf: vineSegment.size)
  vineSegment.physicsBody?.categoryBitMask = PhysicsCategory.vine
  vineSegment.physicsBody?.collisionBitMask = PhysicsCategory.vineHolder
}

此循環(huán)創(chuàng)建一個藤蔓片段數(shù)組,其數(shù)目與創(chuàng)建VineNode時指定的長度相等膏燕。 每個段都是具有自己的physics bodysprite钥屈。 這些線段是矩形的,因此您可以使用SKPhysicsBody(rectangleOf :)指定物理物體的形狀坝辫。

與藤蔓架不同篷就,藤蔓藤節(jié)是動態(tài)的-它們可以移動并受到重力的影響。

構(gòu)建并運行以查看進度近忙。

哦哦竭业! 藤蔓藤段像切碎的意大利面條一樣從屏幕上掉下來!


Adding Joints to the Vines

問題是您還沒有將藤蔓分段結(jié)合在一起及舍。 要解決此問題未辆,您需要將此最后的代碼塊添加到addToScene(_ :)的底部:

// set up joint for vine holder
let joint = SKPhysicsJointPin.joint(
  withBodyA: vineHolder.physicsBody!,
  bodyB: vineSegments[0].physicsBody!,
  anchor: CGPoint(
    x: vineHolder.frame.midX,
    y: vineHolder.frame.midY))

scene.physicsWorld.add(joint)

// set up joints between vine parts
for i in 1..<length {
  let nodeA = vineSegments[i - 1]
  let nodeB = vineSegments[i]
  let joint = SKPhysicsJointPin.joint(
    withBodyA: nodeA.physicsBody!,
    bodyB: nodeB.physicsBody!,
    anchor: CGPoint(
      x: nodeA.frame.midX,
      y: nodeA.frame.minY))
  
  scene.physicsWorld.add(joint)
}

此代碼在線段之間建立物理連接,并將它們連接在一起锯玛。 您使用的關(guān)節(jié)類型是SKPhysicsJointPin咐柜。 這種關(guān)節(jié)類型的行為就像您將銷釘通過兩個節(jié)點錘擊一樣兼蜈,使它們可以圍繞銷釘樞轉(zhuǎn),但彼此之間的距離不能太遠拙友。

構(gòu)建并再次運行为狸。 您的藤蔓應(yīng)該從樹上垂下來。

最后一步是將藤蔓附著到菠蘿上遗契。 仍在VineNode.swift中辐棒,滾動到attachToPrize(_ :)。 添加以下代碼:

// align last segment of vine with prize
let lastNode = vineSegments.last!
lastNode.position = CGPoint(x: prize.position.x,
                            y: prize.position.y + prize.size.height * 0.1)
    
// set up connecting joint
let joint = SKPhysicsJointPin.joint(withBodyA: lastNode.physicsBody!,
                                    bodyB: prize.physicsBody!,
                                    anchor: lastNode.position)
    
prize.scene?.physicsWorld.add(joint)

該代碼獲取藤蔓的最后一段牍蜂,并將其定位在獎品中心的上方漾根。 您希望將其附加在此處,以便使獎品吊著下來捷兰。 如果它是死點立叛,則獎品將被平均加權(quán)并可能沿其軸旋轉(zhuǎn)负敏。 它還創(chuàng)建了另一個銷釘接頭贡茅,將藤蔓部分附加到獎品上。

構(gòu)建并運行其做。 如果所有關(guān)節(jié)和節(jié)點都正確設(shè)置顶考,則應(yīng)該看到類似于以下屏幕的屏幕:

好極了! 懸空的菠蘿–誰將菠蘿與樹綁在一起妖泄?


Snipping the Vines

您可能已經(jīng)注意到驹沿,您仍然不能剪那些藤! 接下來蹈胡,您將解決該小問題渊季。

在本部分中,您將使用允許您剪斷那些懸掛藤蔓的觸摸方法罚渐。 返回GameScene.swift却汉,找到touchesMoved(_:with :)并添加以下代碼:

for touch in touches {
  let startPoint = touch.location(in: self)
  let endPoint = touch.previousLocation(in: self)
  
  // check if vine cut
  scene?.physicsWorld.enumerateBodies(
    alongRayStart: startPoint, 
    end: endPoint, 
    using: { body, _, _, _ in
      self.checkIfVineCut(withBody: body)
  })
  
  // produce some nice particles
  showMoveParticles(touchPosition: startPoint)
}

該代碼的工作方式如下:首先,它獲取每次觸摸的當前位置和先前位置荷并。 接下來合砂,使用非常方便的SKSceneenumerateBodies(alongRayStart:end:using :)方法,循環(huán)遍歷這兩個點之間的所有場景源织。 對于遇到的每個body翩伪,它將調(diào)用checkIfVineCut(withBody :),您將在一分鐘內(nèi)編寫它谈息。

最后缘屹,代碼調(diào)用一個方法,該方法通過從Particle.sks加載SKEmitterNode來創(chuàng)建一個SKEmitterNode侠仇,并將其添加到場景中用戶觸摸的位置轻姿。 無論您將手指拖到哪里,都會產(chǎn)生一條漂亮的綠色煙霧痕跡。

現(xiàn)在踢代,向下滾動到checkIfVineCut(withBody :)并將此代碼塊添加到方法主體中:

let node = body.node!

// if it has a name it must be a vine node
if let name = node.name {
  // snip the vine
  node.removeFromParent()

  // fade out all nodes matching name
  enumerateChildNodes(withName: name, using: { node, _ in
    let fadeAway = SKAction.fadeOut(withDuration: 0.25)
    let removeNode = SKAction.removeFromParent()
    let sequence = SKAction.sequence([fadeAway, removeNode])
    node.run(sequence)
  })
}

上面的代碼首先檢查連接到physics body的節(jié)點是否具有名稱盲憎。請記住,場景中除了藤節(jié)以外還有其他節(jié)點胳挎,您當然不希望不小心將秋千或菠蘿切成薄片饼疙!但是,您僅命名了vine節(jié)點段慕爬,因此窑眯,如果該節(jié)點具有名稱,則可以確定它是vine的一部分医窿。

接下來磅甩,從場景中刪除該節(jié)點。刪除節(jié)點還會刪除其PhysicalBody并破壞與其連接的所有關(guān)節(jié)姥卢。您現(xiàn)在已經(jīng)正式切斷了藤卷要!

最后,使用場景的enumerateChildNodes(withName:using :)枚舉場景中所有名稱與掃到的節(jié)點名稱匹配的節(jié)點独榴。名稱匹配的唯一節(jié)點是同一藤蔓中的其他片段僧叉,因此您只需遍歷切片的任何藤蔓的片段。

對于每個節(jié)點棺榔,創(chuàng)建一個SKAction瓶堕,先淡出該節(jié)點,然后將其從場景中刪除症歇。效果是郎笆,切成薄片后,每棵藤都會褪出忘晤。

構(gòu)建并運行宛蚓。嘗試剪斷那些藤蔓–現(xiàn)在,您應(yīng)該可以滑動并切割所有三個藤蔓德频,并看到該獎品下降了苍息。甜菠蘿!


Handling Contact Between Bodies

編寫setUpPhysics()時壹置,您指定GameScene將充當PhysicsWorldcontactDelegate竞思。 您還配置了croccontactTestBitMask,以便SpriteKit在與獎品相交時通知钞护。 那是極好的預(yù)見盖喷!

現(xiàn)在,在GameScene.swift中难咕,您需要實現(xiàn)SKPhysicsContactDelegatedidBegin(_ :)课梳,只要它檢測到兩個適當遮罩的物體之間的交集距辆,就會觸發(fā)該事件。 該方法有一個stub-向下滾動以找到它并添加以下代碼:

if (contact.bodyA.node == crocodile && contact.bodyB.node == prize)
  || (contact.bodyA.node == prize && contact.bodyB.node == crocodile) {
  
  // shrink the pineapple away
  let shrink = SKAction.scale(to: 0, duration: 0.08)
  let removeNode = SKAction.removeFromParent()
  let sequence = SKAction.sequence([shrink, removeNode])
  prize.run(sequence)
}

該代碼檢查兩個相交的物體是否屬于鱷魚和獎品暮刃。 您不知道節(jié)點的順序跨算,因此您要檢查兩個組合。 如果測試通過椭懊,您將觸發(fā)一個簡單的動畫序列诸蚕,該序列將獎賞縮減為零,然后將其從場景中刪除氧猬。


Animate the Crocodile's Chomp

您還希望鱷魚在抓到菠蘿時剁碎背犯。 在您剛剛觸發(fā)了菠蘿收縮動畫的if語句中,添加以下額外的行:

runNomNomAnimation(withDelay: 0.15)

現(xiàn)在找到runNomNomAnimation(withDelay :)并添加以下代碼:

crocodile.removeAllActions()

let closeMouth = SKAction.setTexture(SKTexture(imageNamed: ImageName.crocMouthClosed))
let wait = SKAction.wait(forDuration: delay)
let openMouth = SKAction.setTexture(SKTexture(imageNamed: ImageName.crocMouthOpen))
let sequence = SKAction.sequence([closeMouth, wait, openMouth, wait, closeMouth])

crocodile.run(sequence)

上面的代碼使用removeAllActions()刪除當前在鱷魚節(jié)點上運行的所有動畫盅抚。 然后漠魏,它創(chuàng)建一個新的動畫序列,以關(guān)閉并張開鱷魚的嘴妄均,讓鱷魚crocodile運行該序列柱锹。

當獎品降落在鱷魚嘴中時,將觸發(fā)此新動畫丛晦,給人以鱷魚正在咀嚼它的印象奕纫。

在進行此操作時,在checkIfVineCut(withBody :)中的enumerateChildNodes調(diào)用下面添加以下行:

crocodile.removeAllActions()
crocodile.texture = SKTexture(imageNamed: ImageName.crocMouthOpen)
animateCrocodile()

這將確保您在剪藤時張開鱷魚的嘴烫沙,這樣獎品就有可能落在鱷魚的嘴里。

構(gòu)建并運行隙笆。

現(xiàn)在锌蓄,快樂的鱷魚如果將菠蘿放在嘴里,就會嚼碎菠蘿撑柔。 但是一旦發(fā)生這種情況瘸爽,游戲就會掛在那里。 接下來铅忿,您將解決該問題剪决。


Resetting the Game

接下來,當菠蘿掉落或鱷魚吃掉菠蘿時檀训,您將重置游戲柑潦。

GameScene.swift中,找到switchToNewGame(withTransition :)峻凫,然后添加以下代碼:

let delay = SKAction.wait(forDuration: 1)
let sceneChange = SKAction.run {
  let scene = GameScene(size: self.size)
  self.view?.presentScene(scene, transition: transition)
}

run(.sequence([delay, sceneChange]))

上面的代碼使用SKViewpresentScene(_:transition :)呈現(xiàn)下一個場景渗鬼。

在這種情況下,過渡到的場景是同一GameScene的新實例荧琼。 您還可以使用SKTransition類傳遞過渡效果譬胎。 您可以將過渡指定為方法的參數(shù)差牛,以便可以根據(jù)游戲的結(jié)果使用不同的過渡效果。

滾動回到didBegin(_ :)堰乔,然后在if語句內(nèi)的獎品收縮和NomNom動畫之后偏化,添加以下內(nèi)容:

// transition to next level
switchToNewGame(withTransition: .doorway(withDuration: 1.0))

這將使用.doorway(withDuration :)初始化程序調(diào)用switchToNewGame(withTransition :)來創(chuàng)建門口過渡。 這顯示了具有開門效果的下一關(guān)镐侯。 構(gòu)建并運行以查看效果夹孔。

很整潔吧?


Ending the Game

您可能會認為您需要在水中添加另一個physics body析孽,以便可以檢測到獎品是否撞擊它搭伤,但是如果菠蘿從屏幕側(cè)面飛出,那將無濟于事袜瞬。

一種更簡單怜俐,更好的方法是檢測菠蘿何時移到屏幕邊緣的底部以下,然后結(jié)束游戲邓尤。

SKScene提供了update(_ :)拍鲤,每幀調(diào)用一次。 在GameScene.swift中找到該方法汞扎,并添加以下邏輯:

if prize.position.y <= 0 {
  switchToNewGame(withTransition: .fade(withDuration: 1.0))
}

if語句檢查獎品的y坐標是否小于零(即屏幕底部)季稳。 如果是這樣,它將調(diào)用switchToNewGame(withTransition :)再次啟動級別澈魄,這次使用.fade(withDuration :)景鼠。

構(gòu)建并運行。

每當玩家獲勝或失敗時痹扇,您都應(yīng)該看到場景淡出并過渡到新場景铛漓。


Adding Sound and Music

現(xiàn)在,您將在incompetech.com上添加一首優(yōu)美的叢林歌曲鲫构,并從freesound.org中添加一些音效浓恶。

SpriteKit將為您處理聲音效果。 但是结笨,您將使用AVAudioPlayer在關(guān)卡過渡之間無縫播放背景音樂包晰。

1. Adding the Background Music

要開始添加音樂,請向GameScene.swift添加另一個屬性:

private static var backgroundMusicPlayer: AVAudioPlayer!

這聲明了一個type屬性炕吸,因此GameScene的所有實例都將能夠訪問相同的backgroundMusicPlayer伐憾。 找到setUpAudio()并添加以下代碼:

if GameScene.backgroundMusicPlayer == nil {
  let backgroundMusicURL = Bundle.main.url(
    forResource: SoundFile.backgroundMusic,
    withExtension: nil)
  
  do {
    let theme = try AVAudioPlayer(contentsOf: backgroundMusicURL!)
    GameScene.backgroundMusicPlayer = theme
  } catch {
    // couldn't load file :[
  }
  
  GameScene.backgroundMusicPlayer.numberOfLoops = -1
}

上面的代碼檢查backgroundMusicPlayer是否存在。 如果沒有算途,它將使用您之前添加到Constants.swiftBackgroundMusic初始化一個新的AVAudioPlayer塞耕。 然后,它將其轉(zhuǎn)換為URL并將其分配給屬性嘴瓤。 numberOfLoops值設(shè)置為-1扫外,表示歌曲應(yīng)無限循環(huán)莉钙。

接下來,將此代碼添加到setUpAudio()的底部:

if !GameScene.backgroundMusicPlayer.isPlaying {
  GameScene.backgroundMusicPlayer.play()
}

場景首次加載時筛谚,將啟動背景音樂磁玉。 然后它將無限期播放,直到應(yīng)用程序退出或其他方法在播放器上調(diào)用stop()為止驾讲。

您可以直接調(diào)用play()蚊伞,而無需先檢查播放器是否正在播放,但是通過這種方式吮铭,如果在關(guān)卡開始時已經(jīng)在播放音樂时迫,則音樂不會跳過或重新開始。

2. Adding the Sound Effects

在這里時谓晌,您還可以設(shè)置以后將要使用的所有聲音效果掠拳。 與音樂不同,您不想立即播放聲音效果纸肉。 相反溺欧,您將創(chuàng)建一些可重用的SKAction,這些稍后將播放聲音柏肪。

返回GameScene類定義的頂部姐刁,并添加以下屬性:

private var sliceSoundAction: SKAction!
private var splashSoundAction: SKAction!
private var nomNomSoundAction: SKAction!

現(xiàn)在返回setUpAudio()并將以下行添加到方法的底部:

sliceSoundAction = .playSoundFileNamed(
  SoundFile.slice,
  waitForCompletion: false)
splashSoundAction = .playSoundFileNamed(
  SoundFile.splash,
  waitForCompletion: false)
nomNomSoundAction = .playSoundFileNamed(
  SoundFile.nomNom,
  waitForCompletion: false)

此代碼使用SKActionplaySoundFileNamed(_:waitForCompletion :)初始化聲音操作。 現(xiàn)在烦味,該播放音效了聂使。

向上滾動到update(_ :)并在switchToNewGame(withTransition :)調(diào)用上方的if語句內(nèi)添加以下代碼:

run(splashSoundAction)

當菠蘿落入水中時,會發(fā)出飛濺的聲音拐叉。 接下來岩遗,找到didBegin(_ :)并在runNomNomAnimation(withDelay :)行的下方添加以下代碼:

run(nomNomSoundAction)

當鱷魚獲得獎品時,它將發(fā)出刺耳的聲音凤瘦。 最后,找到checkIfVineCut(withBody :)并在if let語句的底部添加以下代碼:

run(sliceSoundAction)

每當玩家剪斷藤蔓時案铺,就會播放輕拂的聲音蔬芥。

構(gòu)建并運行,當鱷魚吃菠蘿時控汉,享受那松脆的聲音笔诵!

3. Getting Rid of an Awkward Sound Effect

您發(fā)現(xiàn)bug了嗎? 如果您錯過croc姑子,則飛濺的聲音會播放多次乎婿。 這是因為在游戲過渡到下一個場景之前,您反復(fù)觸發(fā)“關(guān)卡完成”邏輯街佑。 若要更正此問題谢翎,請在類頂部添加一個新的state屬性:

private var isLevelOver = false

現(xiàn)在通過在每個代碼的頂部添加以下代碼來修改update(_ :)didBegin(_ :)

if isLevelOver {
  return
}

最后捍靠,在相同方法的其他if語句內(nèi),添加一些代碼以將isLevelOver狀態(tài)設(shè)置為true

isLevelOver = true

現(xiàn)在森逮,一旦游戲檢測到設(shè)置了isLevelOver榨婆,要么是因為菠蘿掉到地上,要么是因為鱷魚吃了飯褒侧,那么它將停止檢查游戲的勝利/失敗場景良风。 它不會繼續(xù)反復(fù)嘗試播放那些聲音效果。

構(gòu)建并運行闷供。 不再有笨拙的聲音效果烟央!


Adding Some Difficulty

玩了幾回合后,游戲似乎有些簡單歪脏。 您將很快到達可以在三個藤蔓上用適時的單個切片喂鱷魚的地步疑俭。

使用Constants.swiftCanCutMultipleVinesAtOnce中的值使事情變得棘手唾糯。

GameScene.swift中怠硼,在GameScene類定義的頂部添加最后一個屬性:

private var didCutVine = false

現(xiàn)在找到checkIfVineCut(withBody :)并在頂部添加以下if語句:

if didCutVine && !GameConfiguration.canCutMultipleVinesAtOnce {
  return
}

將此代碼添加到相同方法的底部的if語句中:

didCutVine = true

為了使事情保持在一起,找到touchesBegan(_:with :)移怯,然后添加以下行:

didCutVine = false

這樣香璃,只要用戶觸摸屏幕,就可以重置didCutVine舟误。

構(gòu)建并再次運行葡秒。

您應(yīng)該看到,現(xiàn)在每次滑動只能剪一棵藤蔓嵌溢。 要剪另一根眯牧,您必須抬起手指,然后再次滑動赖草。

后記

本篇主要講述了基于SpriteKit的類Cut the Rope游戲簡單示例学少,感興趣的給個贊或者關(guān)注~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市秧骑,隨后出現(xiàn)的幾起案子版确,更是在濱河造成了極大的恐慌,老刑警劉巖乎折,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绒疗,死亡現(xiàn)場離奇詭異,居然都是意外死亡骂澄,警方通過查閱死者的電腦和手機吓蘑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坟冲,“玉大人磨镶,你說我怎么就攤上這事溃蔫。” “怎么了棋嘲?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵酒唉,是天一觀的道長。 經(jīng)常有香客問我沸移,道長痪伦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任雹锣,我火速辦了婚禮网沾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蕊爵。我一直安慰自己辉哥,他們只是感情好,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布攒射。 她就那樣靜靜地躺著醋旦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪会放。 梳的紋絲不亂的頭發(fā)上饲齐,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音咧最,去河邊找鬼捂人。 笑死,一個胖子當著我的面吹牛矢沿,可吹牛的內(nèi)容都是我干的滥搭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼捣鲸,長吁一口氣:“原來是場噩夢啊……” “哼瑟匆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起栽惶,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤脓诡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后媒役,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡宪迟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年酣衷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片次泽。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡穿仪,死狀恐怖席爽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啊片,我是刑警寧澤只锻,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站紫谷,受9級特大地震影響齐饮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笤昨,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一祖驱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞒窒,春花似錦捺僻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽憨奸。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間探入,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工贬蛙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留长搀,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓匿辩,卻偏偏與公主長得像腰耙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铲球,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

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