版本記錄
版本號 | 時間 |
---|---|
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è)置其position
和zPosition
。
鱷魚有一個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 world
的contactDelegate
,gravity
和speed
附井。 重力指定了應(yīng)用于世界物理物體的重力加速度,而速度則調(diào)節(jié)了模擬執(zhí)行的速度。 您希望在此處將兩個屬性都設(shè)置為其默認值意蛀。
您會發(fā)現(xiàn)您不必遵循SKPhysicsContactDelegate
秀姐,因為GameScene
底部已經(jīng)有一個擴展程序來實現(xiàn)它若贮。 暫時不要管它省有; 您稍后再使用。
構(gòu)建并再次運行谴麦。 您應(yīng)該看到菠蘿駛過鱷魚蠢沿,掉入水中。
是時候添加藤蔓了匾效。
Adding Vines
SpriteKit
的物理物體模擬了剛性物體……但是藤蔓彎曲了。為了解決這個問題弧轧,您需要將每個藤蔓實施為具有柔性接頭的分段陣列雪侥,類似于鏈。
每個藤蔓具有三個重要屬性:
-
anchorPoint:
CGPoint
精绎,指示藤蔓末端連接到樹的位置速缨。 -
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ù)組,每個字典包含一個relAnchorPoint
和length
:
接下來,將以下代碼添加到方法中:
// 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) 對于每個藤蔓旭斥,初始化一個新的
VineNode
。length
指定藤中的節(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] = []
由于您尚未初始化length
和anchorPoint
屬性堪旧,因此會出現(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 body
的sprite
钥屈。 這些線段是矩形的,因此您可以使用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)
}
該代碼的工作方式如下:首先,它獲取每次觸摸的當前位置和先前位置荷并。 接下來合砂,使用非常方便的SKScene
的enumerateBodies(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
將充當PhysicsWorld
的contactDelegate
竞思。 您還配置了croc
的contactTestBitMask
,以便SpriteKit
在與獎品相交時通知钞护。 那是極好的預(yù)見盖喷!
現(xiàn)在,在GameScene.swift
中难咕,您需要實現(xiàn)SKPhysicsContactDelegate
的didBegin(_ :)
课梳,只要它檢測到兩個適當遮罩的物體之間的交集距辆,就會觸發(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]))
上面的代碼使用SKView
的presentScene(_: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.swift
的BackgroundMusic
初始化一個新的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)
此代碼使用SKAction
的playSoundFileNamed(_: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.swift
,CanCutMultipleVinesAtOnce
中的值使事情變得棘手唾糯。
在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)注~~~